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

#include "misc.h"
#include "defs.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"

#ifdef FOR_X11
#include <netinet/in.h>
#else
#include <winsock.h>
#endif

void City::Produce()
{
  if (building == NULL) return;
  BuildObject *obj = buildObjects.Find(StrKey(building));
  if (obj->built != 0 || HasBuilding(obj->name)) {
    AddMessage(messages, "%s has already been built", obj->name);
    return;
  }
  if (obj->unit) {
    if (obj->type == SETTLER)
      ++foodSupport;
    Unit *unit = NewUnit(obj->type, ownerId, trans->New(), 0, id, x, y);
    units.Insert(unit->id);
    *moveQ << PieceMove(unit->id, PIECE_CREATE, x, y, unit->Type(),
			0, id);
  }
  else {
    if (obj->wonder) {
      players[ownerId]->wonders.Insert(obj->name);
      obj->built = 1;
      *moveQ << PieceMove(ownerId, WONDER, 0, 0);
      *moveQ << obj->name;
      if (!WonderObsolete(obj, ownerId)) {
	if (obj->type == MICHELANGELO)
	  players[ownerId]->cathedralEffect = 6;
	else if (obj->type == ORACLE)
	  players[ownerId]->templeEffect = 4;
	else if (obj->type == COLOSSUS)
	  tradeBonus = 1;
      }
    }
    buildings.Insert(obj->name);
    building = NULL;
    needProd = 0;
  }
  AddMessage(messages, "%s builds %s", name, obj->name);
  accProd = 0;
}

void City::WorkOn(char *what)
{
  building = what;
  needProd = buildObjects.Find(StrKey(building))->prodCost;
}

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

void City::DrawBasic()
{
  screen->Clear();
  screen->WriteStr(160-strlen(name)*4, 0, name, white);
}

void City::PutIcon(long handle, int n, int &xc, int &yc, int inc,
		   int xlim, int xl)
{
  while (n-- > 0) {
    screen->DrawPixmap(xc, yc, handle);
    xc += inc;
    if (xc >= xlim) { xc = xl; yc += 8; }
  }
}

void City::DrawCityMap()
{
  for (int xi = 0; xi < 5; ++xi) for (int yi = 0; yi < 5; ++yi) {
    int x1 = world->FixX(x+xi-2), y1 = world->FixY(y+yi-2);
    int xc = 120+xi*16, yc = 24+yi*16;
    ulong enemyUnit = 0;
    if (!(x1 == x && y1 == y)) {
      for (Lister<ulong> unitl = world->Units(x1, y1); unitl; unitl.Next())
	if (trans->TransUnit(unitl.Elem())->ownerId != ownerId)
	  enemyUnit = unitl.Elem();
    }
    if (enemyUnit != 0)
      trans->TransUnit(enemyUnit)->Draw(xc, yc);
    else if (world->WhichCity(x1, y1) != 0)
      trans->TransCity(world->WhichCity(x1, y1))->Draw(xc, yc);
    else
      world->DrawSquare(x1, y1, xc, yc);

    // now put the work stuff
    if (world->Working(x1, y1) == id) { // we're working here
      int tot = world->Food(x1, y1, ownerId)+
	world->Prod(x1, y1, ownerId)+world->Trade(x1, y1, ownerId)+
	  (world->Trade(x1, y1, ownerId) > 0 ? tradeBonus : 0);
      int inc = tot < 3 ? 8 : 8/(int(tot/2.0+0.5)-1);
      int x = xc, y = yc;
      PutIcon(hFood, world->Food(x1, y1, ownerId), x, y, inc, xc+16, xc);
      PutIcon(hShield, world->Prod(x1, y1, ownerId), x, y, inc, xc+16, xc);
      PutIcon(hTrade, world->Trade(x1, y1, ownerId) +
	      (world->Trade(x1, y1, ownerId) > 0 ? tradeBonus : 0),
	      x, y, inc, xc+16, xc);
    }
    else if (world->Working(x1, y1) != 0)
      screen->Rect(xc, yc, 15, 15, red);
  }
}
  
void City::ShowPeople()
{
  int x = 0, y = 10, i;
  int content = working-goondas;
  screen->FillRect(0, 10, 320, 12, back);
  for (i = 0; i < content; ++i, x += 8)
    screen->DrawPixmap(x, y, hWorker);
  x += 4;
  for (i = 0; i < goondas; ++i, x += 8)
    screen->DrawPixmap(x, y, hGoonda);
  x += 4;
  for (i = 0; i < elvi; ++i, x += 8)
    screen->DrawPixmap(x, y, hElvis);
}

void City::ShowEcon()
{
  screen->FillRect(0, 24, 80, 40, back);
  screen->DrawPixmap(0, 24, hFood);
  screen->DrawPixmap(0, 34, hShield);
  screen->DrawPixmap(0, 44, hTrade);
  screen->WriteInt(10, 24, food, 4, white);
  screen->WriteInt(10, 34, surpProd, 4, white);
  screen->WriteInt(10, 44, trade, 4, white);

  screen->DrawPixmap(0, 54, hLux);
  screen->DrawPixmap(24, 54, hMoney);
  screen->DrawPixmap(48, 54, hBulb);
  screen->WriteInt(8, 54, luxuries, 2, white);
  screen->WriteInt(32, 54, money, 2, white);
  screen->WriteInt(56, 54, science, 2, white);
}

void City::ShowOwnUnits()
{
  int x = 0, y = 70;
  Lister<ulong> unitl = units;
  int foodUsed = foodSupport;
  int prodUsed = units.Count();
  if (players[ownerId]->govt < MONARCH)
    prodUsed -= size;
  if (prodUsed < 0) prodUsed = 0;
  while (unitl) {
    Unit *unit = trans->TransUnit(unitl.Elem());
    unit->Draw(x, y);
    if (foodUsed > 0 && unit->type == SETTLER) {
      --foodUsed;
      screen->DrawPixmap(x, y+15, hFood);
    }
    if (prodUsed > 0) {
      screen->DrawPixmap(x+8, y+15, hShield);
      --prodUsed;
    }
    unitl.Next();
    x += 16;
    if (x > 96) { x = 0; y += 20; }
    if (y >= 130 && unitl) {
      screen->WriteChar(112, y-20, '+', white);
      break;
    }
  }
}

void City::ShowFood()
{
  screen->FillRect(2, 158, 80, 22, back);
  screen->WriteStr(4, 160, "Food Stored", white);
  screen->DrawPixmap(4, 170, hFood);
  screen->WriteInt(12, 170, foodStored, 3, white);
  screen->WriteChar(36, 170, '/', white);
  screen->WriteInt(44, 170, foodStoreCap, 3, white);
}

void City::ShowStationedUnits()
{
  Lister<ulong> unitl = world->Units(x, y);
  int x = 120, y = 110, n = 0;
  while (unitl && n < 10) {
    Unit *unit = trans->TransUnit(unitl.Elem());
    unit->Draw(x, y);
    unitl.Next();
    x += 16;
    ++n;
    if (n%5 == 0) {
      x = 120;
      y += 16;
    }
  }
}

void City::ShowWorkInProg()
{
  screen->FillRect(202, 148, 116, 34, back);
  if (building != NULL) {
    int l = strlen(building);
    screen->WriteStr(260-l*4, 150, building, white);
  }
  screen->DrawPixmap(204, 170, hShield);
  screen->WriteInt(212, 170, accProd, 4, white);
  screen->WriteChar(244, 170, '/', white);
  screen->WriteInt(252, 170, needProd, 4, white);
}

void City::ShowBuildings()
{
  int n = 0;
  int x = 204, y = 26;
  Lister<charp> lister = buildings;
  screen->FillRect(202, 24, 116, 114, back);
  while (lister && n < 11) {
    screen->WriteStr(x, y, lister.Elem(), white);
    y += 10;
    ++n;
    lister.Next();
  }
}

void City::SendCapturedInfo(MsgQ *q)
{
  *q << id;
  *q << size;
  if (building == NULL)
    *q << "";
  else
    *q << building;
  *q << needProd;
  *q << foodStored;
  *q << foodStoreCap;
  *q << tradeBonus;
  *q << buildings.Count();
  while (buildings) {
    char *str = buildings.RemoveHead();
    *q << str;
    players[ownerId]->DeleteWonder(str);
  }
}

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

    *q >> str;
    city->building = city->LookupBuilding(str, 0);
    
    city->accProd = 0;
    *q >> city->needProd;
    *q >> city->foodStored;
    *q >> city->foodStoreCap;
    *q >> city->tradeBonus;
    city->foodSupport = 0;

    *q >> n;
    while (n-- > 0) {
      char *str;
      *q >> str;
      city->buildings.Insert(city->LookupBuilding(str, 1));
    }

    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 (world->Working(world->FixX(x+xi-2), world->FixY(y+yi-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] = ntohl(map[yi]);
    for (int xi = 0; xi < 5; ++xi)
      if (map[yi] & (1<<xi))
	world->Working(world->FixX(x+xi-2), world->FixY(y+yi-2)) = id;
  }
  delete map;
}

// list of things we can build
// for the moment its just the list of things the player can build
// minus what he's already built
StrList City::CanBuild()
{
  List<charp> l;
  Lister<charp> buildl = players[ownerId]->canBuild;
  while (buildl) {
    BuildObject *obj = buildObjects.Find(StrKey(buildl.Elem()));
    if (obj == NULL) {
      buildl.Next();
      continue;
    }
    Lister<charp> needl = obj->neededObj;
    int canBuild = 1;
    while (canBuild && needl) {
      Lister<charp> havel = buildings;
      char *need = needl.Elem();
      canBuild = 0;
      while (!canBuild && havel) {
	canBuild = strcmp(need, havel.Elem()) == 0;
	havel.Next();
      }
      needl.Next();
    }
    for (Lister<charp> havel = buildings; havel && canBuild; havel.Next())
      if (strcmp(obj->name, havel.Elem()) == 0)
	canBuild = 0;
    if (canBuild)
      l.Insert(obj->name);
    buildl.Next();
  }
  return l;
}

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

int City::HasBuilding(char *name)
{
  for (Lister<charp> lister = buildings; lister; lister.Next())
    if (strcmp(lister.Elem(), name) == 0) return 1;
  return 0;
}

void City::Save()
{
  WriteULong(id);
  WriteString(name);
  WriteUShort(x);
  WriteUShort(y);
  WriteUChar(ownerId);
  WriteUChar(size);
  WriteString(building == NULL ? "X" : building);
  WriteUShort(tradeBonus);
  WriteUShort(accProd);
  WriteUShort(needProd);
  WriteUShort(foodStored);
  WriteUShort(foodSupport);
  WriteUShort(foodStoreCap);
  WriteUShort(buildings.Count());
  for (Lister<charp> l = buildings; l; l.Next())
    WriteString(l.Elem());
}
