#include <string.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

// pixmaps
#include "../pic/bulb.xpm"
#include "../pic/elvis.xpm"
#include "../pic/food.xpm"
#include "../pic/goonda.xpm"
#include "../pic/lux.xpm"
#include "../pic/money.xpm"
#include "../pic/shield.xpm"
#include "../pic/trade.xpm"
#include "../pic/worker.xpm"

#include "../pic/city.xpm"

const char *backGroundColor = "#79e78e38ffff";

int City::white, City::black, City::back, City::red;

long City::hBulb, City::hElvis, City::hFood;
long City::hGoonda, City::hLux, City::hMoney;
long City::hShield, City::hTrade, City::hWorker;

City::City(char *Name, ulong Id, int X, int Y, uchar OwnerId)
{
  name = Name;
  x = X;
  y = Y;
  id = Id;
  trans->SetPtr(id, this);
  ownerId = OwnerId;
  players[ownerId]->citys.Insert(id);
  size = 1;

  building = "Settlers";
  accProd = 0;
  needProd = buildObjects.Find(StrKey(building))->prodCost;

  foodSupport = foodStored = 0;
  foodStoreCap = 20;

  tradeBonus = 0;

  world->WhichCity(x,y) = id;
  if (ownerId == playerId) world->SeeSquare(x, y, 2);
  world->Working(x, y) = id;
  DrawIfVisible();
  PutExtraWorker();
  if (ownerId == playerId)
    ComputeEcon();
}

// read from save file
City::City()
{
  id = ReadULong();
  name = ReadString();
  x = ReadUShort();
  y = ReadUShort();
  ownerId = ReadUChar();
  size = ReadUChar();
  building = LookupBuilding(ReadString(), 0);
  tradeBonus = ReadUShort();
  accProd = ReadUShort();
  needProd = ReadUShort();
  foodStored = ReadUShort();
  foodSupport = ReadUShort();
  foodStoreCap = ReadUShort();
  int n = ReadUShort();
  for (;n > 0; --n)
    buildings.Insert(LookupBuilding(ReadString(), 0));

  trans->SetPtr(id, this);
  players[ownerId]->citys.Insert(id);
  world->WhichCity(x, y) = id;
}

City::~City()
{
  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);
    if (world->Working(x1, y1) == id)
      world->Working(x1, y1) = 0;
  }
  while (buildings) {
    char *name = buildings.RemoveHead();
    players[ownerId]->DeleteWonder(name);
  }
  world->WhichCity(x,y) = 0;
  players[ownerId]->citys.Delete(id);
  delete name;
  DrawIfVisible();
  if (id == playerId)
    world->HideSquare(x, y, 2);
}

char *City::LookupBuilding(char *str, int insertWonder)
{
  BuildObject *obj = buildObjects.Find(StrKey(str));
  delete str;
  if (obj == NULL)
    return NULL;
  if (insertWonder && obj->wonder) {
    players[ownerId]->wonders.Insert(obj->name);
    obj->built = 1;
  }
  return obj->name;
}

void City::Draw(int atx, int aty)
{
  if (players[ownerId]->cityPic == 0)
    players[ownerId]->cityPic = players[ownerId]->CompilePic(city_xpm);
  screen->DrawPixmap(atx, aty, players[ownerId]->cityPic);
  if (size < 10)
    screen->WriteInt(atx+4, aty+4, size, 1, black);
  else
    screen->WriteInt(atx, aty, size, 2, black);
}

void City::DrawIfVisible()
{
  if (!world->Visible(x, y) || !display->Visible(x, y)) return;
  int sx, sy;
  display->TranslateScreen(x, y, sx, sy);
  world->Draw(x, y, 1, 1, sx, sy);
}

void AllocCityColors()
{
  City::white = screen->AllocColor("#ffffffffffff");
  City::red = screen->AllocColor("red");
  City::black = screen->AllocColor("#000000000000");
  City::back = screen->AllocColor((char *)backGroundColor);
  City::hBulb = screen->CompilePixmap(bulb_xpm);
  City::hElvis = screen->CompilePixmap(elvis_xpm);
  City::hFood = screen->CompilePixmap(food_xpm);
  City::hGoonda = screen->CompilePixmap(goonda_xpm);
  City::hLux = screen->CompilePixmap(lux_xpm);
  City::hMoney = screen->CompilePixmap(money_xpm);
  City::hShield = screen->CompilePixmap(shield_xpm);
  City::hTrade = screen->CompilePixmap(trade_xpm);
  City::hWorker = screen->CompilePixmap(worker_xpm);
}

// find an empty spot and put work on it
void City::PutExtraWorker()
{
  int mx, my, f = -1, p = -1, t = -1;
  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);
    if (world->Working(x1, y1) != 0) continue;
    int cf = world->Food(x1, y1, ownerId);
    int cp = world->Prod(x1, y1, ownerId);
    int ct = world->Trade(x1, y1, ownerId);
    if (cf > f || (cf == f && (ct > t || (ct == t && cp > p)))) {
      f = cf; p = cp; t = ct;
      mx = x1; my = y1;
    }
  }
  if (f != -1)
    world->Working(mx, my) = id;
}

void City::ComputeEcon()
{
  prod = food = trade = 0;
  working = 0;
  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);
    if (world->Working(x1, y1) != id) continue;
    ulong enemyUnit = 0;
    for (Lister<ulong> unitl = world->Units(x1, y1); unitl; unitl.Next())
      if (trans->TransUnit(unitl.Elem())->ownerId != ownerId)
	enemyUnit = unitl.Elem();
    if (enemyUnit != 0) {
      world->Working(x1, y1) = 0; // can't work here
      continue;
    }
    if (working == size && !(x1 == x && y1 == y)) {
      world->Working(x1, y1) = 0;
      continue;
    }
    food += world->Food(x1, y1, ownerId);
    prod += world->Prod(x1, y1, ownerId);
    int val = world->Trade(x1, y1, ownerId);
    if (val > 0) val += tradeBonus;
    trade += val;
    if (!(x1 == x && y1 == y)) ++working;
  }
  Player *p = players[ownerId];
  elvi = size-working;
  luxuries = 2*elvi;
  money = trade*p->tax/100;
  science = trade-money;
  goondas = size/4;
  happy = 0;
  food -= size;
  food -= foodSupport;

  players[ownerId]->WonderEffect(this);
  if (p->govt < REPUBLIC) // martial order
    goondas -= world->Units(x, y).Count();
  for (Lister<charp> l = buildings; l; l.Next()) {
    BuildObject *obj = buildObjects.Find(StrKey(l.Elem()));
    switch (obj->type) {
    case BANK:
      luxuries = int(luxuries*1.5);
      money = int(money*1.5);
      break;
    case CATHEDRAL:
      goondas -= p->cathedralEffect;
      break;
    case COLOSSEUM:
      goondas -= 3;
    case FACTORY:
      prod = int(prod*1.5);
      break;
    case HYDROPLANT:
      prod = int(prod*1.5);
      break;
    case LIBRARY:
      science = int(science*1.5);
      break;
    case MARKETPLACE:
      luxuries = int(luxuries*1.5);
      money = int(money*1.5);
      break;
    case MFGPLANT:
      prod *= 2;
      break;
    case POWERPLANT:
      prod = int(prod*1.5);
      break;
    case TEMPLE:
      goondas = goondas-p->templeEffect;
      break;
    case UNIVERSITY:
      science = int(science*1.5);
    }
  }
  if (goondas < 0) goondas = 0;

  int support = units.Count();
  if (p->govt == DESPOT) support -= size;
  if (support < 0) support = 0;
  surpProd = prod-support;
}

int City::Update()
{
  Debug('c', "Updating city %s\n", name);
  ComputeEcon();

  while (surpProd < 0) { // kill a unit
    Unit *unit = trans->TransUnit(units.RemoveHead());
    AddMessage(messages, "%s can't support %s", name, names[unit->Type()]);
    *moveQ << PieceMove(unit->id, PIECE_DIE, 0, 0);
    if (unit->type == SETTLER)
      ++food;
    delete unit;
    ++surpProd;
  }
  accProd += surpProd;
  foodStored += food;
  if (foodStored < 0) { // kill off settlers
    for (Lister<ulong> unitl = units; unitl && foodStored < 0;) {
      Unit *unit = trans->TransUnit(unitl.Elem());
      if (unit->type == SETTLER) {
	AddMessage(messages, "%s can't feed Settlers", name);
	*moveQ << PieceMove(unit->id, PIECE_DIE, 0, 0);
	delete unit;
	unitl = units;
	++foodStored;
      }
      else
	unitl.Next();
    }
  }

  if (foodStored < 0) {
    foodStored = 0;
    --size;
    foodStoreCap -= 10;
    AddMessage(messages, "Famine in %s, population decrease", name);
    if (size <= 0) // city dies
      return 0;
    else
      *moveQ << PieceMove(id, CITY_SIZE, size, 0);
    DrawIfVisible();
  }
  if (foodStored >= foodStoreCap) { // grow city
    foodStored -= foodStoreCap;
    ++size;
    *moveQ << PieceMove(id, CITY_SIZE, size, 0);
    foodStoreCap += 10;
    PutExtraWorker();
    DrawIfVisible();
  }

  if (building != NULL && accProd >= needProd)
    Produce();
  ComputeEcon();

  players[ownerId]->money += money;
  players[ownerId]->scienceAcc += science;

  for (Lister<charp> l = buildings; l;) {
    BuildObject *obj = buildObjects.Find(StrKey(l.Elem()));
    players[ownerId]->money -= obj->maint;
    if (players[ownerId]->money < 0) {
      players[ownerId]->money += obj->prodCost;
      AddMessage(messages, "%s can't maintain %s", name, l.Elem());
      l.Delete();
      players[ownerId]->DeleteWonder(obj->name);
    }
    else
      l.Next();
  }

  return 1;
}
