#include <stdlib.h>

#ifdef USE_X11
# include <sys/types.h>
# include <netinet/in.h>
#else
# ifdef MS_WIN
#  include <winsock.h>
# endif
#endif

#include "global.h"

#include "misc.h"
#include "trans.h"
#include "player.h"
#include "units.h"
#include "city.h"
#include "world.h"
#include "move.h"
#include "MsgQ.h"
#include "savefile.h"
#include "mapdisplay.h"

static int fortColor = 0;

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

  coord = c;
  currOrder = ' ';

  if (cityId)
    trans->TransCity(cityId)->AddUnit(id, type);
  players[ownerId]->units.Insert(id);
  world->Units(coord).Insert(id);
}

// restore from save file
Unit::Unit()
{
  id = ReadULong();
  cityId = ReadULong();
  carriedBy = ReadULong();
  flags = ReadUChar();
  ownerId = ReadUChar();
  coord.x = ReadUShort();
  coord.y = ReadUShort();
  type = BuildObject(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)->AddUnit(id,type);
  if (carriedBy == 0)
    world->Units(coord).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(coord).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(MainMap, coord, Visibility());
  if (cityId != 0) {
    trans->TransCity(cityId)->RemoveUnit(id,type);
  }
  DrawIfVisible();
}

void Unit::DrawIfVisible(MapDisplay *display)
{
  if (world->Visible(coord) && display->Visible(coord))
    world->Draw(display, coord, 1, 1);
}

void Unit::Draw(WDisplay* window)
{
  window->DrawPixmap(coord, Pic(), ownerId);
  if (Fortified()) {
    if (fortColor == 0)
      fortColor = screen->AllocColor("SlateGray");
    int w, h;

    Pic()->GetInfo(w,h);
    window->Rect(coord, w-1, h, fortColor);
    // window->Rect(atx, aty+h-2, w-1, 1, fortColor); // FIXME
  }
}

void Unit::Draw(WDisplay* window, ScreenCoord c)
{
  window->DrawPixmap(c, Pic(), ownerId);
  if (Fortified()) {
    if (fortColor == 0)
      fortColor = screen->AllocColor("SlateGray");
    int w, h;
    Pic()->GetInfo(w, h);
    window->Rect(coord, w-1, h, fortColor);
    // window->Rect(atx, aty+h-2, w-1, 1, fortColor); // FIXME
  }
}

void Unit::ShowMoveTo(MapDisplay *w, MapCoord dest)
{
  int steps = 16;
  MapCoord topleft;

  topleft.y = coord.y == dest.up().y   ? coord.y : dest.y;
  topleft.x = coord.x == dest.left().x ? coord.x : dest.x;
  Offset off = dest-coord;
  int xinc = off.x; int yinc = off.y;
  int ht = yinc == 0 ? 1 : 2, wd = xinc == 0 ? 1 : 2;

  // alter this to change the way piece moves are displayed
  for (int i = 0; i <= steps; ++i) {
    world->Draw(w, topleft, wd, ht);
    w->DrawPixmap(coord, Pic(), ownerId, xinc*i/(double)steps,yinc*i/(double)steps);
    w->Flush();
  }
}

int Battle(Unit *a, Unit *d)
{
  int attack = a->Attack(), defend = d->Defense(a->type);
  City *city = trans->TransCity(world->WhichCity(d->coord));

  if (a->type == BOMBER)
    a->numMoves = 0;
  else
    --a->numMoves;

  if (a->type == NUCLEAR) {
    *moveQ << PieceMove(a->id, PIECE_DIE, 0, 0);
    delete a;
    if (city != NULL) {
      if (!city->NuclearAttack())
	return 0;
    }
    
    for (int i=0;i<6;i++)
      world->PolluteNear(d->coord);

    while (Lister<ulong> unitl = world->Units(d->coord)) {
      Unit *ptr = trans->TransUnit(unitl.Elem());
      *moveQ << PieceMove(ptr->id, PIECE_DIE, 0, 0);
      delete ptr;
    }
    return 0;
  }

  Unit *loser;
  if (attack == 0)
    loser = a;
  else if (defend == 0)
    loser = d;
  else {
    attack = random() % attack;
    defend = random() % defend;
    if (attack >= defend)
      loser = d;
    else
      loser = a;
  }

  if (loser == d && (city != 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, MapCoord dest, double cost, MsgQ *q, MapDisplay *display)
{
  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->coord).Delete(unit->id);
    if (world->Visible(unit->coord) || world->Visible(dest)) {
      display->ShowPiece(unit->coord, unit->id);
      unit->ShowMoveTo(display, dest);
      world->Units(dest).Insert(unit->id);
      world->Draw(display, unit->coord, 1, 1);
      world->Draw(display, dest, 1, 1);
    }
    else
      world->Units(dest).Insert(unit->id);
    unit->SetCoord(dest);
    City *city = trans->TransCity(world->WhichCity(dest));
    if (city == NULL) {
      // check if unit has moved onto a carrier
      for (Lister<ulong> unitl = world->Units(dest); 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(dest).Delete(unit->id);
	  break;
	}
      }
      return 1;
    } else {
      if ((city->ownerId == unit->ownerId) || (unit->type == CARAVAN) || (unit->type == DIPLOMAT)) return 1;
      city->Capture(unit->ownerId,q);
      //if (city->ownerId == playerId) { // our city was captured
      // FIXME: what happens to the wonders
      // FIXME: do we still think we own this city?
      //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
  if ((unit->type != CARAVAN) && (unit->type != DIPLOMAT) || !world->WhichCity(dest)) {
    Lister<ulong> units(world->Units(dest));
    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 (AirUnit(unit->type) && world->WhichCity(dest)) //what if it's not our city?
    unit->numMoves = 0;
  else
    unit->numMoves -= cost;
  if (unit->carriedBy) {
    trans->TransUnit(unit->carriedBy)->Carrying().Delete(unit->id);
    unit->carriedBy = 0;
  }
  else {
    world->Units(unit->coord).Delete(unit->id);
    world->HideSquare(display, unit->coord, unit->Visibility());
  }
  display->ShowPiece(unit->coord, unit->id);
  unit->ShowMoveTo(display, dest);

  unit->SetCoord(dest);

  // check if we moved onto a carrier
  int carried = 0;
  for (Lister<ulong> unitl = world->Units(dest); 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(dest).Insert(unit->id);
    world->SeeSquare(display, dest, unit->Visibility());

    // check if there is an enemy city here
    City *city;
    if ((city = trans->TransCity(world->WhichCity(dest))) != NULL &&
	city->ownerId != playerId && (unit->type != DIPLOMAT) && (unit->type != CARAVAN)) {
      // take over city, we'll get the info for the city later
      city->ownerId = playerId;
      world->SeeSquare(display, city->coord, CITY_VISIBILITY);
    }
  }
  // add the move to the list of moves
  *moveQ << PieceMove(unit->id, PIECE_MOVE, dest);
  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);
}
