#include <string.h>
#include "misc.h"
#include "world.h"
#include "trans.h"
#include "units.h"
#include "move.h"
#include "city.h"
#include "player.h"
#include "MsgQ.h"
#include "rules.h"
#include "savefile.h"
#include "global.h"
#include "mapdisplay.h"

int SeaUnit(BuildObject type)
{
  switch (type) {
  case BATTLESHIP:
  case CARRIER:
  case CRUISER:
  case FRIGATE:
  case IRONCLAD:
  case SAIL:
  case SUBMARINE:
  case TRANSPORT:
  case TRIREME:
    return 1;
  default:
    return 0;
  }
}

int AirUnit(BuildObject type)
{
  switch (type) {
  case FIGHTER:
  case BOMBER:
  case NUCLEAR:
    return 1;
  default:
    return 0;
  }
}

int UnitVisibility(BuildObject type)
{
  switch (type) {
  case CARRIER:
  case BATTLESHIP:
  case BOMBER:
  case CRUISER:
  case SUBMARINE:
    return 2;
  default:
    return 1;
  }
}

int UnitAttack(BuildObject type)
{
  return Units[type].attack;
}

int UnitDefense(BuildObject type)
{
  return Units[type].defend;
}

int UnitMoves(BuildObject type)
{
  return Units[type].move;
}

int SettlerOrder(Unit *u, int key)
{
  City *city;
  switch (key) {
  case 'b': // build city
    if (world->WhichCity(u->coord) != 0) {
      City *city = trans->TransCity(world->WhichCity(u->coord));
      city->size++;
      *moveQ << PieceMove(city->id,CITY_SIZE,city->size,0);
      *moveQ << PieceMove(u->id, PIECE_DIE, 0, 0);
      return 0; // we died
    }
    city = new City(strdup(*cityNames++), trans->New(), u->coord, playerId);
    // city counts as road automatically:
    world->MakeRoad(u->coord);
    // auto-irrigate on city square if it is 'clear' type terrain:
    switch (world->Terrain(u->coord)) {
    case GRASS:
    case PLAINS:
    case RIVER:
      world->Irrigate(u->coord);
    default:
      break;
    }
    *moveQ << PieceMove(city->id, CITY_CREATE, u->coord);
    *moveQ << city->name;
    *moveQ << PieceMove(u->id, PIECE_DIE, 0, 0);
    return 0; // we died
  case 'i': // irrigate
    if ((u->orderProgress = world->IrrigTime(u->coord)) > 0) {
      u->currOrder = 'i';
      u->numMoves = 0;
    }
    break;
  case 'p': // unPollute
    if ((u->orderProgress = world->UnPolluteTime(u->coord)) > 0) {
      u->currOrder = 'p';
      u->numMoves = 0;
    }
    break;
  case 'r': // road
    if (world->IsRailRoad(u->coord)) break; // can't do more
    if (world->IsRoad(u->coord)) { // try to build railroad
      if (players[u->ownerId]->HasDiscovered(RAILROAD) &&
	  (u->orderProgress = world->RailRoadTime(u->coord)) > 0) {
	u->currOrder = 'r';
	u->numMoves = 0;
      }
      break;
    }
    if (world->Terrain(u->coord) == RIVER &&
	!players[u->ownerId]->HasDiscovered(BRIDGEBUILDING))
      {
	screen->DispMessageBox("You don't know about Bridge Building !", NULL ,NULL);
	break;
      }
    if ((u->orderProgress = world->RoadTime(u->coord)) > 0) {
      u->currOrder = 'r';
      u->numMoves = 0;
    }
    break;
  case 'm':
    if (world->Mined(u->coord)) break; // can't do more
    if ((u->orderProgress = world->MineTime(u->coord)) > 0) {
      Debug('u',"Mining at %d %d (%d turns)", u->coord, u->orderProgress);
      u->currOrder = 'm';
      u->numMoves = 0;
    }
    break;
  }
  return 1;
}

void SettlerCompleteOrder(Unit *u, MapDisplay */*display*/)
{
  if (u->currOrder != ' ') {
    if (--u->orderProgress <= 0) {
      switch (u->currOrder) {
      case 'i':
	world->Irrigate(u->coord);
	break;
      case 'p':
	world->UnPollute(u->coord);
	break;
      case 'r':
	if (world->IsRoad(u->coord)) {
	  if (players[u->ownerId]->HasDiscovered(RAILROAD))
	    world->MakeRailRoad(u->coord);
	}
	else
	  world->MakeRoad(u->coord);
	break;
      case 'm':
	world->Mine(u->coord);
	break;
      default:
	return;
      }
      u->currOrder = ' ';
    }
    u->numMoves = 0;
    // world->SeeSquare(display, u->coord);
  } 
}

Unit *NewUnit(BuildObject type, int owner, ulong id, int vet, ulong city, MapCoord c, MapDisplay *display)
{
  Debug('u', "Creating new Unit id %ld, owner %d, at %d %d\n", id, owner, c.x, c.y);
  Unit *unit = new Unit(owner, id, vet, city, c, type);
  trans->SetPtr(unit->id, unit);
  Debug('u', "Doing display for new Unit\n");
  if (owner == playerId) {
    display->ShowPiece(unit->coord, unit->id);
    world->SeeSquare(display, unit->coord, unit->Visibility());
  }
  else
    unit->DrawIfVisible(display);
  Debug('u', "Done NewUnit for new Unit\n");
  return unit;
}

int Unit::Order(int key)
{
  if (key == 'f') {
    currOrder = 'f';
    numMoves = 0;
  }
  else if (key == 's') {
    currOrder = 's';
    numMoves = 0;
  }
  else if (key == ' ')
    numMoves = 0;
  else if (key == 'd') {
    City *c = trans->TransCity(world->WhichCity(coord));
    if (c != NULL)
      c->accProd += Objects[type].DisbandValue();

    *moveQ << PieceMove(this->id, PIECE_DIE, 0, 0);
    return 0; // we died
  }
  else if (key == 'u' && carrying) { // wake up cargo
    for (Lister<ulong> l = carrying; l; l.Next()) {
      Unit *ptr = trans->TransUnit(l.Elem());
      ptr->currOrder = ' ';
      ptr->numMoves = UnitMoves(ptr->type);
    }
  }
  else {
    switch (type) {
    case SETTLER:
      return SettlerOrder(this, key);
    default:
      break;
    }
  }
  return 1;
}

int Unit::EndTurn()
{
  if (!AirUnit(type))
    return 1;
  
  int CanLand = world->WhichCity(coord);
  if (!CanLand) {
    for (Lister<ulong> l = world->Units(coord);l; l.Next()) {
      if (trans->TransUnit(l.Elem())->CanCarry(this))
	CanLand = 1;
    }
  }

  if ((type == BOMBER) && !(flags & MUST_LAND))
    {
      if (CanLand)
	flags &= ~MUST_LAND;
      else
	flags |= MUST_LAND;
      return 1;
    }
  if (CanLand)
    return 1;

  // kill unit
  *moveQ << PieceMove(id, PIECE_DIE, 0, 0);
  delete this;

  return 0;
}

void Unit::CompleteOrder(MapDisplay *display)
{
  if (currOrder == 'f') {
    if (!Fortified()) {
      Fortify();
      DrawIfVisible();
      *moveQ << PieceMove(id, UNIT_UPDATE, 1, Veteran() ? 1 : 0);
    }
    numMoves = 0;
  }
  else if (currOrder == 's') // asleep
    numMoves = 0;
  else {
    if (type == SETTLER)
      SettlerCompleteOrder(this, display);
  }
}

int Unit::CanMoveOn(int terrainType, MapCoord c)
{
  // can always move into a city
  // we can't move sea units *into* an enemy city, but we can attack it, so
  // this is ok
  if (world->WhichCity(c) != 0 || type == NUCLEAR) return 1;

  if (SeaUnit(type)) return terrainType == WATER;
  if (AirUnit(type)) return 1;
  if (terrainType == WATER) { // check if there is a carrier unit
    // land unit can't move from carrier to carrier
    // so if its already carried fail
    if (carriedBy != 0) return 0;
    for (Lister<ulong> unitl = world->Units(c); unitl; unitl.Next()) {
      Unit *unit = trans->TransUnit(unitl.Elem());
      if (unit->CanCarry(this)) // CanCarry checks ownership
	return 1;
    }
    return 0;
  }
  return 1;
}
    
int Unit::CanAttack(BuildObject type)
{
  // sea units can attack other sea units
  // battleships, cruisers, ironclads, frigates can attack coastal units
  switch (type) {
  case BATTLESHIP:
  case IRONCLAD:
  case FRIGATE:
  case CRUISER:
    if (AirUnit(type) || type == NUCLEAR) return 0;
    return 1;
  case CARRIER:
  case SAIL:
  case SUBMARINE:
  case TRIREME:
  case TRANSPORT:
    return SeaUnit(type);
  case FIGHTER:
    return type == BOMBER;
  case BOMBER: // can attack anything except a nuclear or fighter
    if (AirUnit(type) || type == NUCLEAR) return 0;
    return 1;
  default:
    break;
  }
  if (AirUnit(type) || SeaUnit(type) || type == NUCLEAR || carriedBy != 0)
    return 0;
  return 1;
}

double Unit::MoveCost(int terrainType, double movesRem, MapCoord dest)
{
  if (SeaUnit(type) || AirUnit(type)) return 1;
  double cost;
  if (world->IsRoad(dest))
    cost = 0.333333334;
  else if (world->IsRailRoad(dest))
    cost = 0;
  else {
    switch (terrainType) {
    case MOUNTAINS:
      cost = 3; break;
    case SWAMP:
    case HILLS:
    case FOREST:
    case JUNGLE:
      cost = 2; break;
    case DESERT:
    case PLAINS:
    case GRASS:
    case RIVER:
      cost = 1; break;
    case WATER:
      cost = movesRem;
      break;
    default:
      cost = 1;
      break;
    }
  }
  if ((cost > 1.50001*movesRem) && (movesRem < Moves(players[playerId]->SeaMoveBonus()))) {
      return -1.0; // can't move, too high cost
  }
  return cost;
}

int CarryCapacity(BuildObject unit)
{
  switch(unit) {

  case CARRIER:
    return 12;
  case FRIGATE:
    return 4;
  case SAIL:
    return 3;
  case TRANSPORT:
    return 8;
  case TRIREME:
    return 2;
  default:
    return 0;
  }
}

int Unit::CanCarry(Unit *unit)
{
  if (unit->ownerId != ownerId) return 0;
  BuildObject destType = unit->type;

  if (type == CARRIER)
    return (AirUnit(destType) || destType == NUCLEAR) && carrying.Count() < 12;

  return !AirUnit(destType) && !SeaUnit(destType) && carrying.Count() < CarryCapacity(type);
}

void Unit::SetHomeCity(int newCityId) 
{
  City *city = trans->TransCity(newCityId);
  if ((city != NULL) && (city->ownerId == ownerId)) {
    if (cityId)
      trans->TransCity(cityId)->RemoveUnit(id,type);
    cityId = newCityId;
    city->AddUnit(id,type);
  }
}

void Unit::SetCoord(MapCoord c)
{
  coord = c;
  for (Lister<ulong> l = carrying; l; l.Next())
    trans->TransUnit(l.Elem())->SetCoord(coord);
}

void Unit::Save()
{
  WriteULong(id);
  WriteULong(cityId);
  WriteULong(carriedBy);
  WriteUChar(flags);
  WriteUChar(ownerId);
  WriteUShort(coord.x);
  WriteUShort(coord.y);
  WriteUChar(type);
  WriteUChar(currOrder);
  WriteUShort(orderProgress); 
  WriteUShort(carrying.Count());
  for (Lister<ulong> l = carrying; l; l.Next())
    WriteULong(l.Elem());
}
