#include <string.h>
#include <stdarg.h>

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

#include "defs.h"
#include "global.h"

#include "misc.h"
#include "graph.h"
#include "city.h"
#include "world.h"
#include "rivers.h"
#include "MsgQ.h"
#include "player.h"
#include "units.h"
#include "trans.h"
#include "display.h"
#include "move.h"
#include "rules.h"
#include "hash.h"
#include "savefile.h"
#include "citydisplay.h"
#include "mapdisplay.h"
#include "displays.h"

static void CityHandleBuildChoice(char *choice, City *city) {
  city->HandleBuildChoice(choice); }

static void CityHandleBuyChoice(char *choice, City *city) {
  city->HandleBuyChoice(choice); }

CityDisplay* City::Show()
{
  if (display != NULL) {
    display->Raise();
    return display;
  }
  else
    return screen->CreateCityDisplay(this, players[ownerId]);
};

void City::DrawIfVisible(MapDisplay *window)
{
  if (!world->Visible(coord) || !window->Visible(coord)) return;
  world->Draw(window, coord, 1, 1);
}

void City::ChooseBuild()
{
  List<charp> buildl = CanBuild();
  state = IN_SELECT;
  screen->CreateChoiceFrame("Select ...", buildl, CityHandleBuildChoice, this);
  buildl.Delete();
}

void City::HandleBuildChoice(char *choice)
{
  state = NORMAL;
  char *endp = strchr(choice, '/');
  if (endp)
    *endp = '\0';
  endp = strrchr(choice, ' ');
  *endp = '\0';
  Debug('p', "Selected building %s\n", choice);

  BuildObject *obj = buildObjects.Find(StrKey(choice));

  if (obj != NULL) {
    Debug('p', "Found obj %s\n", Objects[*obj].name);
    WorkOn(*obj);
    }
  ShowWorkInProg();
}

void City::ChooseBuy()
{
  int cost = BuyCost();
  if (cost < 0 || cost > players[ownerId]->money)
    return;

  List<charp> buyl;
  char str[30], str1[50], str2[50];
  sprintf(str,"Buy %s ?", Objects[building].name);
  sprintf(str1,"Yes, buy %s for %d coin",Objects[building].name,cost);
  buyl.Insert(str1);
  sprintf(str2,"No, do not buy %s",Objects[building].name);
  buyl.Insert(str2);
  state = IN_SELECT;
  screen->CreateChoiceFrame(str, buyl, CityHandleBuyChoice, this);
  buyl.Delete();
}

void City::HandleBuyChoice(char *choice)
{
  state = NORMAL;
  if (strncmp(choice,"Yes",3) == 0) {
    players[ownerId]->money -= BuyCost();
    accProd = needProd();
    ShowWorkInProg();
  }
}

void City::Produce()
{
  if (building == NOOBJECT) {
    AddMessage(messages, "%s isn't building anything", name);
    return;
  }
  if (HasBuilding(building) || Objects[building].built) {
    AddMessage(messages, "%s has already been built", Objects[building]);
    return;
  }

  AddMessage(messages, "%s builds %s", name, Objects[building].name);

  if (Objects[building].IsUnit()) {
    if (Objects[building].type == SETTLER) {
      //making settler takes one population from city:
      size--;
      //if (foodStored > foodStoreCap()) foodStored = foodStoreCap();
      *moveQ << PieceMove(id, CITY_SIZE, size, 0);
    }
    Unit *unit = NewUnit(building, ownerId, trans->New(), HasBuilding(BARRACKS), id, coord);
    // AddUnit(unit->id); // moved to constructor
    *moveQ << PieceMove(unit->id, PIECE_CREATE, coord, unit->Type(), 0, id);
  }
  else {
    if (Objects[building].IsWonder()) {
      //players[ownerId]->InsertWonder(building); // moved to AddBuilding
      Objects[building].built = 1;
      *moveQ << PieceMove(ownerId, BUILDWONDER, 0, 0);
      *moveQ << building;
      if (!Objects[building].isObsolete)
        switch (Objects[building].type) {
	case DARWIN:
	  players[ownerId]->scienceAcc += 2*players[ownerId]->needScience+15;
	  break;
	default:
	  break;
        }
    }

    AddBuilding(building);
    switch(building) {
    case MFGPLANT:
      DeleteBuilding(FACTORY);
      break;
    case PALACE:
      if (players[ownerId]->capital != NULL)
	players[ownerId]->capital->DeleteBuilding(PALACE);
      players[ownerId]->capital = trans->TransCity(id);
      break;
    case CITYWALLS:
      *moveQ << PieceMove(id, CITY_WALL, 1, 0);
      break;
    case SDIDEFENSE:
      *moveQ << PieceMove(id, SDI_DEFENSE, 1, 0);
      break;
    default:
      break;
    }
    building = NOOBJECT;
  }
  accProd = 0;
}

void City::InfoScreen(CityDisplay *window)
{
  DrawBasic(window);
  DrawCityMap(window);
  ShowPeople(window);
  ShowEcon(window);
  ShowFood(window);
  ShowOwnUnits(window);
  ShowStationedUnits(window);
  ShowWorkInProg(window);
  ShowBuildings(window);
  ShowSmokeStacks(window);
}

void City::SetDisplay(CityDisplay *w) {display = w;}
void City::InfoScreen(void) {InfoScreen(display);}
void City::DrawBasic() {DrawBasic(display);}
void City::DrawCityMap() {DrawCityMap(display);}
void City::ShowPeople() {ShowPeople(display);}
void City::ShowEcon() {ShowEcon(display);}
void City::ShowFood() {ShowFood(display);}
void City::ShowOwnUnits() {ShowOwnUnits(display);}
void City::ShowStationedUnits() {ShowStationedUnits(display);}
void City::ShowWorkInProg() {ShowWorkInProg(display);}
void City::ShowBuildings() {ShowBuildings(display);}
void City::ShowSmokeStacks() {ShowSmokeStacks(display);}

void City::DrawBasic(CityDisplay *window)
{
  window->Clear();
  //window->WriteStr(160-strlen(name)*4, 0, name, WDisplay::black);
}

void City::DrawCityMap(CityDisplay *window)
{
  MapCoord c;
  for (int xi = 0; xi < 5; ++xi) for (int yi = 0; yi < 5; ++yi) {
    if ((xi % 4) == 0 && (yi % 4) == 0) continue;
    c = coord.down(yi-2).right(xi-2);
    if (c.valid()) {
      ulong enemyUnit = 0;
      if (c != coord) {
	for (Lister<ulong> unitl = world->Units(c); unitl; unitl.Next())
	  if (trans->TransUnit(unitl.Elem())->ownerId != ownerId)
	    enemyUnit = unitl.Elem();
      }
      if (enemyUnit != 0)
	trans->TransUnit(enemyUnit)->Draw(window);
      else if (world->WhichCity(c) != 0)
	trans->TransCity(world->WhichCity(c))->Draw(window);
      else
	world->DrawSquare(window, c);
      
      // now put the work stuff
      if (world->Working(c) == id) { // we're working here
	//int xc = 120+xi*16, yc = 24+yi*16;
	//int x = xc, y = yc;
	window->ShowResources(c, world->Food(c, ownerId, LoveTheRuler), 
			      world->Prod(c, ownerId, LoveTheRuler),
			      world->Trade(c, ownerId, LoveTheRuler)+(world->Trade(c, ownerId, LoveTheRuler) > 0 ? players[ownerId]->TradeBonus() : 0));
      }
      else if (world->Working(c) != 0)
	window->Rect(c, SquareWidth-1, SquareHeight-1, CityDisplay::red);
    }
  }
}
  
void City::ShowPeople(CityDisplay *window)
{
  int content = size-happy-goondas;
  window->ShowPeople(happy, content, goondas, elvi, taxmen, scientists);
}

void City::ShowEcon(CityDisplay *window)
{
  window->ShowEcon(food, surpProd(), trade, corruption, luxuries, money, science);
}

int City::surpProd()
{
  if (disordered())
    return 0;

  int prodUsed = units.Count();
  if (players[ownerId]->govt < MONARCH)
    prodUsed -= size;
  if (prodUsed < 0) prodUsed = 0;

  return prod-prodUsed;
}

void City::ShowOwnUnits(CityDisplay *window)
{

  Lister<ulong> unitl = units;
  int n = 0;
  int foodUsed = foodSupport();
  int prodUsed = prod-surpProd();
  int food, prod, unhappy;
  while (unitl) {
    Unit *unit = trans->TransUnit(unitl.Elem());
    
    food = 0;
    if (foodUsed > 0 && unit->type == SETTLER) {
      foodUsed-=players[ownerId]->FoodUsedPerSettler();
      food = players[ownerId]->FoodUsedPerSettler();
    }
    prod = 0;
    if (prodUsed > 0) {
      --prodUsed;
      prod = 1;
    }
    unhappy = 0;
    if (unit->coord != coord)
      if ((unit->type != SETTLER) && (unit->type != DIPLOMAT) && (unit->type != CARAVAN))
	unhappy = players[ownerId]->UnhappyPerUnitAway();

    window->ShowOwnUnit(n, unit, food, prod, unhappy);
    n++;
    unitl.Next();
  }
}

void City::ShowFood(CityDisplay *window)
{
  char text[20];
  sprintf(text, "%d / %d", foodStored, foodStoreCap());
  window->ShowFood(text);
}

void City::ShowStationedUnits(CityDisplay *window)
{
  Lister<ulong> unitl = world->Units(coord);
  window->ShowStationedUnits(unitl);
}

void City::ShowWorkInProg(CityDisplay *window)
{
  char text[20];
  window->ShowWorkInProgress(Objects[building].name);
  sprintf(text, "%d / %d", accProd, needProd());
  window->ShowBuildingProgress(text);
}

void City::ShowBuildings(CityDisplay *window)
{
  List<charp> buildings;
  int n = 0;

  for (BuildObject b = FIRSTBUILDING; (b <= LASTWONDER) && (n < 11); b = BuildObject(b+1))
    if (HasBuilding(b))
      buildings.Insert(Objects[b].name);
  window->ShowBuildings(buildings);
}

void City::ShowSmokeStacks(CityDisplay *window)
{
  window->ShowSmokeStacks(pollution);
}

void City::SendCityInfo(MsgQ *q)
{
  *q << id;
  *q << size;
  *q << int(building);
  *q << foodStored;
  *q << buildings;
  *q << wonders;
}

void City::Capture(int capturerId, MsgQ *q, MapDisplay *display)
{
  if (capturerId == playerId) { // we took this city over
    // take over city, we'll get the info for the city later
    world->SeeSquare(display, coord, CITY_VISIBILITY);
  }
  else {
    if (ownerId == playerId) { // this *was* our city
      SendCityInfo(q);
      // FIXME: remove wonders
      // FIXME: kill units
      world->HideSquare(display, coord, CITY_VISIBILITY); // can't see the square anymore
    }
  }
  
  if (players[ownerId]->capital == this)
    players[ownerId]->capital = NULL;
  
  ownerId = capturerId;
  // FIXME: remove from owners city list
}

void GetCapturedCity(MsgQ *q, Player *p)
{
  long n;
  while (q->Count() > 0) {
    *q >> n;
    City *city = trans->TransCity((ulong)n);
    while (city->units) city->units.RemoveHead();

    *q >> city->size;
    *q >> city->building;
    
    city->accProd = 0;
    *q >> city->foodStored;
    //city->foodSupport = 0;

    *q >> city->buildings;
    *q >> city->wonders;

    p->citys.Insert(city->id);
    city->ComputeEcon();
  }
}

// converts the squares the city is working on into a map which
// we can send over the net
char *City::MakeWorkMap(int &len)
{
  ushort *map = new ushort[5];
  for (int yi = 0; yi < 5; ++yi) {
    map[yi] = 0;
    for (int xi = 0; xi < 5; ++xi) {
      if ((xi % 4) == 0 && (yi % 4) == 0) continue;
      if (world->Working(coord.down(yi-2).right(xi-2)) == id)
	map[yi] |= 1<<xi;
    }
    map[yi] = htons(map[yi]);
  }
  len = sizeof(ushort)*5;
  return (char *)map;
}

void City::TransWorkMap(MsgQ *q)
{
  Msg m;
  *q >> m;
  ushort *map = (ushort *)m.buf;
  for (int yi = 0; yi < 5; ++yi) {
    map[yi] = ntohs(map[yi]);
    for (int xi = 0; xi < 5; ++xi) {
      if ((xi % 4) == 0 && (yi % 4) == 0) continue;
      if (map[yi] & (1<<xi))
	world->Working(coord.down(yi-2).right(xi-2)) = id;
      else
	if (world->Working(coord.down(yi-2).right(xi-2)) == id)
	  world->Working(coord.down(yi-2).right(xi-2)) = 0;
    }
  }
  delete map;
}

// list of things we can build
StrList City::CanBuild()
{
  List<charp> l;
  ObjectData *obj;

  for (BuildObject b = LASTWONDER; b >= FIRSTUNIT; b = BuildObject(b-1)) {
    if (HasBuilding(b))
      continue;
    if (!players[ownerId]->HasDiscovered(Objects[b].NeededScience))
      continue;
    obj = &(Objects[b]);

    if (obj->built)
      continue;
    
    if (obj->isObsolete)
      continue;

    if ((obj->IsUnit()) && SeaUnit(obj->type) && !HasCoast())
      continue;

    switch (obj->type) {
    case FACTORY:
      if (HasBuilding(MFGPLANT))
	continue;
      break;
    case POWERPLANT:
    case HYDROPLANT:
    case FUSIONPLANT:
    case NUCLEARPLANT:
      if (players[ownerId]->HasWonder(HOOVERDAM) || HasBuilding(HYDROPLANT) ||
	  HasBuilding(FUSIONPLANT) || HasBuilding(NUCLEARPLANT))
	continue;
      if (!HasBuilding(FACTORY) && !HasBuilding(MFGPLANT))
	continue;
      break;
    case MFGPLANT:
      if (!HasBuilding(MFGPLANT))
	continue;
      break;
    case UNIVERSITY:
      if (!HasBuilding(LIBRARY))
	continue;
      break;
    case BANK:
      if (!HasBuilding(MARKETPLACE))
	continue;
      break;
    case NUCLEAR:
      if (!players[ownerId]->HasDiscovered(ROCKETRY) || !Objects[MANHATTAN].built)
	continue;
      break;
    default:
      break;
    }

    l.Insert(obj->line);
  }
  return l;
}

int City::NuclearAttack()
{
  if (HasBuilding(SDIDEFENSE))
    return 0;
  size = (size+1)/2;
  *moveQ << PieceMove(id, CITY_SIZE, size, 0);
  return 1;
}

void City::TakeHit()
{
  if (!HasBuilding(CITYWALLS) /*&& units.Count() == 0*/) {
    --size;
    *moveQ << PieceMove(id, CITY_SIZE, size, 0);
  }
}

void City::Save()
{
  WriteULong(id);
  WriteString(name);
  WriteUShort(coord.x);
  WriteUShort(coord.y);
  WriteUChar(ownerId);
  WriteUChar(size);
  WriteUChar(building);
  WriteUShort(accProd);
  WriteUShort(foodStored);
  //WriteUShort(foodSupport);
  WriteUShort(scientists);
  WriteUShort(taxmen);
  WriteUShort(LoveTheRuler);

  buildings.Save();
  wonders.Save();
}

