#include "defs.h"
#include "trans.h"
#include "player.h"
#include "units.h"
#include "city.h"
#include "world.h"
#include "display.h"
#include "move.h"
#include "MsgQ.h"
#include "savefile.h"

#include <stdlib.h>
#ifndef MS_WIN
#include <netinet/in.h>
#else
#include <winsock.h>
#endif

static int fortColor = 0;

Unit::Unit(uchar OwnerId, ulong Id, int Veteran, ulong CityId, int X, int Y,
	   uchar Type)
{
  id = Id;
  flags = 0;
  type = Type;
  if (Veteran) MakeVeteran();
  ownerId = OwnerId;
  cityId = CityId;
  carriedBy = 0;

  x = X;
  y = Y;
  currOrder = ' ';

  players[ownerId]->units.Insert(id);
  world->Units(x, y).Insert(id);
}

// restore from save file
Unit::Unit()
{
  id = ReadULong();
  cityId = ReadULong();
  carriedBy = ReadULong();
  flags = ReadUChar();
  ownerId = ReadUChar();
  x = ReadUShort();
  y = ReadUShort();
  type = ReadUChar();
  currOrder = ReadUChar();
  orderProgress = ReadUShort(); 
  int n = ReadUShort();
  while (n-- > 0)
    carrying.Insert(ReadULong());

  trans->SetPtr(id, this);

  players[ownerId]->units.Insert(id);
  if (cityId != 0)
    trans->TransCity(cityId)->units.Insert(id);
  if (carriedBy == 0)
    world->Units(x, y).Insert(id);
}

// this kills the unit
// show a nice spectacular destruction pic if you like
Unit::~Unit()
{
  players[ownerId]->units.Delete(id);
  if (carriedBy == 0)
    world->Units(x, y).Delete(id);
  else
    trans->TransUnit(carriedBy)->Carrying().Delete(id);
  while (carrying) // delete carried units
    delete trans->TransUnit(carrying.RemoveHead());
  if (ownerId == playerId && carriedBy == 0)
    world->HideSquare(x, y, Visibility());
  if (cityId != 0) {
    City *city = trans->TransCity(cityId);
    if (ownerId == playerId && type == SETTLER)
      --city->foodSupport;
    city->units.Delete(id);
  }
  DrawIfVisible();
}

void Unit::DrawIfVisible()
{
  if (world->Visible(x, y) && display->Visible(x, y)) {
    int sx, sy;
    display->TranslateScreen(x, y, sx, sy);
    world->Draw(x, y, 1, 1, sx, sy);
  }
}

long Unit::CompiledPic()
{
  long *compiledPics = CompiledPics();
  if (compiledPics == 0) {
    compiledPics = new long[numPlayers];
    for (int i = 0; i < numPlayers; ++i) compiledPics[i] = 0;
    SetCompiledPics(compiledPics);
  }
  if (compiledPics[ownerId] == 0) {
    Debug('u', "compiling pixmap for %d\n", ownerId);
    compiledPics[ownerId] = players[ownerId]->CompilePic(Picture());
  }
  return compiledPics[ownerId];
}

void Unit::Draw(int atx, int aty)
{
  screen->DrawPixmap(atx, aty, CompiledPic());
  if (Fortified()) {
    if (fortColor == 0)
      fortColor = screen->AllocColor("SlateGray");
    int w, h;
    screen->GetPixmapInfo(CompiledPic(), w, h);
    screen->Rect(atx, aty, w-1, h, fortColor);
    screen->Rect(atx, aty+h-2, w-1, 1, fortColor);
  }
}

void Unit::ShowMoveTo(int x1, int y1, int x2, int y2, int newx, int newy)
{
  int xinc = x2-x1, yinc = y2-y1;
  int top = y1 < y2 ? y : newy, left = x1 < x2 ? x : newx;
  int ht = yinc == 0 ? 1 : 2, wd = xinc == 0 ? 1 : 2;
  int sleftx, stopy;
  display->TranslateScreen(left, top, sleftx, stopy);

  int xc = HorizSpace+x1*SquareWidth, yc = VertSpace+y1*SquareHeight;
  for (int i = 0; i <= SquareWidth; ++i) {
    world->Draw(left, top, wd, ht, sleftx, stopy);
    Draw(xc, yc);
    screen->Refresh();
    xc += xinc;
    yc += yinc;
  }
}

int Battle(Unit *a, Unit *d)
{
  int attack = a->Attack(), defend = d->Defense(a->type);
  Unit *loser;
  if (attack == 0)
    loser = a;
  else if (defend == 0)
    loser = d;
  else {
    attack = rand() % attack;
    defend = rand() % defend;
    if (attack >= defend)
      loser = d;
    else
      loser = a;
  }
  City *city;
  if (loser == d &&
      (city = trans->TransCity(world->WhichCity(d->x, d->y))) != NULL)
    city->TakeHit();
  *moveQ << PieceMove(loser->id, PIECE_DIE, 0, 0);
  Unit *winner = loser == a ? d : a;
  if (!winner->Veteran()) { // make him a veteran
    winner->MakeVeteran();
    *moveQ << PieceMove(winner->id, UNIT_UPDATE, winner->Fortified() ? 1 : 0,
			1);
  }
  int ret = loser != a;
  delete loser;
  return ret;
}

// if this is our unit then:
//   if we are moving into a square with an enemy piece, we attack it
//   this is a very macho unit, if there are lots of enemy pieces, it will
//   attack the one with the highest defense
//   (attack is managed by another function, it will kill one of pieces
//    by sending out a message and invoking it's destructor)
//   if we move into an enemy city we take it over, the info for the
//   city will be sent over to us later
//   otherwise we just move into the square specified
// else we are showing a remote player's turn
//  if this unit is visible show the move, show only the visible part, i.e.
//  if it moves from visible to invisible make it vanish, if it moves from
//  invisible to visible make it appear otherwise just don't show the move
//  if the unit moves into an enemy city we mark the city as captured
//  if it was our city we send its info across to the player who captured it
// if there was a battle and this unit dies 0 is returned
int MoveUnit(Unit *unit, int x, int y, MsgQ *q)
{
  if (unit->ownerId != playerId) { // remote players turn
    if (unit->carriedBy != 0) {
      trans->TransUnit(unit->carriedBy)->Carrying().Delete(unit->id);
      unit->carriedBy = 0;
      unit->currOrder = ' ';
    }
    else
      world->Units(unit->x, unit->y).Delete(unit->id);
    if (world->Visible(unit->x, unit->y) || world->Visible(x, y)) {
      int snewx, snewy, sx, sy;
      display->ShowPiece(unit->x, unit->y, unit->id);
      display->TranslateScreen(unit->x, unit->y, sx, sy);
      display->TranslateScreen(x, y, snewx, snewy);
      unit->ShowMoveTo(sx, sy, snewx, snewy, x, y);
      world->Units(x, y).Insert(unit->id);
      world->Draw(x, y, 1, 1, snewx, snewy);
      world->Draw(unit->x, unit->y, 1, 1, sx, sy);
    }
    else
      world->Units(x, y).Insert(unit->id);
    unit->SetCoord(x, y);
    City *city = trans->TransCity(world->WhichCity(x, y));
    if (city == NULL) {
      // check if unit has moved onto a carrier
      for (Lister<ulong> unitl = world->Units(x, y); unitl; unitl.Next()) {
	Unit *ptr = trans->TransUnit(unitl.Elem());
	if (ptr->CanCarry(unit)) {
	  ptr->Carrying().Insert(unit->id);
	  unit->carriedBy = ptr->id;
	  unit->currOrder = 's';
	  world->Units(x, y).Delete(unit->id);
	  break;
	}
      }
      return 1;
    }
    if (city->ownerId == unit->ownerId) return 1;
    if (city->ownerId == playerId) { // our city was captured
      city->SendCapturedInfo(q);
      world->HideSquare(city->x, city->y, 2); // can't see the square anymore
    }
    // change ownership of city
    city->ownerId = unit->ownerId;
    return 1;
  }
  // this is our unit

  // check if the target square has enemy pieces
  // if it does, we attack
  Lister<ulong> units(world->Units(x, y));
  int maxDefense = -1;
  Unit *enemy = NULL;
  while (units) {
    Unit *ptr = trans->TransUnit(units.Elem());
    if (ptr->ownerId != playerId && ptr->Defense(unit->type) > maxDefense) {
      maxDefense = int(ptr->Defense(unit->type));
      enemy = ptr;
    }
    units.Next();
  }
  if (enemy != NULL) // Attack
    return Battle(unit, enemy); // fight it out

  // ok, nothing dangerous in the target square, lets do the move
  if (unit->carriedBy) {
    trans->TransUnit(unit->carriedBy)->Carrying().Delete(unit->id);
    unit->carriedBy = 0;
  }
  else {
    world->Units(unit->x, unit->y).Delete(unit->id);
    world->HideSquare(unit->x, unit->y, unit->Visibility());
  }
  int snewx, snewy, sx, sy;
  display->ShowPiece(unit->x, unit->y, unit->id);
  display->TranslateScreen(unit->x, unit->y, sx, sy);
  display->TranslateScreen(x, y, snewx, snewy);
  unit->ShowMoveTo(sx, sy, snewx, snewy, x, y);

  unit->SetCoord(x, y);

  // check if we moved onto a carrier
  int carried = 0;
  for (Lister<ulong> unitl = world->Units(x, y); unitl; unitl.Next()) {
    Unit *ptr = trans->TransUnit(unitl.Elem());
    if (ptr->CanCarry(unit)) {
      ptr->Carrying().Insert(unit->id);
      unit->carriedBy = ptr->id;
      unit->currOrder = 's';
      carried = 1;
      break;
    }
  }

  if (!carried) {
    world->Units(x, y).Insert(unit->id);
    world->SeeSquare(x, y, unit->Visibility());

    // check if there is an enemy city here
    City *city;
    if ((city = trans->TransCity(world->WhichCity(x, y))) != NULL &&
	city->ownerId != playerId) {
      // take over city, we'll get the info for the city later
      city->ownerId = playerId;
      world->SeeSquare(city->x, city->y, 2);
    }
  }
  // add the move to the list of moves
  *moveQ << PieceMove(unit->id, PIECE_MOVE, x, y);
  return 1;
}

void PieceMove::NetworkOrder()
{
  id = htonl(id);
  x = htons(x);
  y = htons(y);
  type = htons(type);
  city = htonl(city);
}

void PieceMove::HostOrder()
{
  id = ntohl(id);
  x = ntohs(x);
  y = ntohs(y);
  type = ntohs(type);
  city = ntohl(city);
}
