#include <stdlib.h>
#ifndef MS_WIN
#include <unistd.h>
#include <netinet/in.h>
#else
#include <winsock.h>
#endif
#include <stdio.h>
#include <time.h>

#include "world.h"
#include "rivers.h"
#include "MsgQ.h"
#include "debug.h"
#include "defs.h"
#include "trans.h"
#include "graph.h"
#include "display.h"
#include "units.h"
#include "city.h"
#include "player.h"
#include "savefile.h"

#include "../pic/ocean.xpm"
#include "../pic/mountain.xpm"
#include "../pic/hills.xpm"
#include "../pic/desert.xpm"
#include "../pic/swamp.xpm"
#include "../pic/grass1.xpm"
#include "../pic/grass.xpm"
#include "../pic/forest.xpm"
#include "../pic/jungle.xpm"
#include "../pic/plains.xpm"

char **World::pictures[LAST_TERRAIN_TYPE] = {
  ocean_xpm,
  NULL,
  mountains_xpm,
  hills_xpm,
  desert_xpm,
  swamp_xpm,
  plains_xpm,
  grass_xpm,
  grass1_xpm,
  forest_xpm,
  jungle_xpm };

#include "../pic/gold.xpm"
#include "../pic/coal.xpm"
#include "../pic/oasis.xpm"
#include "../pic/oil.xpm"
#include "../pic/horses.xpm"
#include "../pic/gems.xpm"
#include "../pic/mining.xpm"

long World::minedPic = 0;

char **World::goodiePics[LAST_TERRAIN_TYPE] = {
  NULL,
  NULL,
  gold_xpm,
  coal_xpm,
  oasis_xpm,
  oil_xpm,
  horses_xpm,
  NULL,
  NULL,
  horses_xpm,
  gems_xpm
  };

long World::compiledPictures[LAST_TERRAIN_TYPE] = {
  0, 0, 0, 0,
  0, 0, 0, 0,
  0, 0, 0, 0 };

long World::compiledGoodies[LAST_TERRAIN_TYPE] = {
  0, 0, 0, 0,
  0, 0, 0, 0,
  0, 0, 0, 0 };

double World::terrainDefense[LAST_TERRAIN_TYPE] = {
  1,
  1,
  3,
  2,
  1,
  1.5,
  1,
  1,
  1,
  1.5,
  1.5,
  .8 // river
  };

inline int rnd(int max) { return rand() % max; }

// local alt variation
const int LocalVar = 15;

// percent of land which is a certain type
// anything remaining is automatically plains
const int Mountains = 6;
const int Hills = 8;
const int Desert = 3;
const int Swamp = 3;
const int GrassPer = 10;
const int Grass1Per = 20;
const int Jungle = 5;
const int Forest = 6;

const int GoodiePer = 8; // percent of land which has goodies

int World::blackColor;
int World::waterColor;
int World::landColor;

World::World(int MaxX, int MaxY, int ContDist, int ChunkSize, int NumChunks,
	     int LandPercent, int NumRivers, int RiverLength)
: world(MaxX, MaxY), cities(MaxX, MaxY),
  units(MaxX, MaxY), visible(MaxX, MaxY), working(MaxX, MaxY)
{
  maxX = MaxX;
  maxY = MaxY;

  int x, y;
  for (x = 0; x < maxX; ++x) for (y = 0; y < maxY; ++y) {
    Set(x, y, WATER);
    cities(x,y) = 0;
    working(x, y) = 0;
    visible(x,y) = 0;
  }

  long now;
  time(&now);
  srand((unsigned int)now);

  Debug('w', "cleared world, making terrains\n");
  int land = MakeTerrain(MaxX*MaxY, LandPercent, NumChunks, ChunkSize,
			 ContDist, PLAINS, WATER);
  MakeTerrain(land, Mountains, 5, 1, ContDist/2, MOUNTAINS, PLAINS);
  MakeTerrain(land, Hills, 7, 1, ContDist/2, HILLS, PLAINS);
  MakeTerrain(land, Desert, 7, 2, ContDist/2, DESERT, PLAINS);
  MakeTerrain(land, Swamp, 4, 2, ContDist/2, SWAMP, PLAINS);
  MakeTerrain(land, Forest, 7, 2, ContDist/2, FOREST, PLAINS);
  MakeTerrain(land, Jungle, 7, 2, ContDist/2, JUNGLE, PLAINS);
  MakeTerrain(land, GrassPer, 7, 2, ContDist/2, GRASS, PLAINS);
  MakeTerrain(land, Grass1Per, 7, 2, ContDist/2, GRASS1, PLAINS);

  Debug('w', "Making rivers\n");
  rivers = new Rivers(NumRivers, RiverLength, this);

  // add goodies, look for the right terrain type and add it
  int goodie = (GoodiePer*land)/100;
  while (goodie > 0) {
    x = rnd(maxX); y = rnd(maxY);
    if (IsGoodie(x, y)) continue;
    int ter = Terrain(x, y);
    if (ter == GRASS || ter == GRASS1 || ter == RIVER || ter == WATER)
      continue;
    --goodie;
    world(x, y) |= GOODIE;
  }
}

// read from save file
World::World()
{
  maxX = ReadUShort();
  maxY = ReadUShort();
  Debug('s', "world %d %d\n", maxX, maxY);

  world.Resize(maxX, maxY);
  cities.Resize(maxX, maxY);
  units.Resize(maxX, maxY);
  visible.Resize(maxX, maxY);
  working.Resize(maxX, maxY);

  for (int x = 0; x < maxX; ++x) for (int y = 0; y < maxY; ++y)
    cities(x, y) = 0;

  ReadBytes((char *)world.GetFirst(), world.Size());
  ReadBytes((char *)working.GetFirst(), working.Size());
  ReadBytes((char *)visible.GetFirst(), visible.Size());
  Debug('s', "Restored most of world\n");

  rivers = new Rivers(this);
}

World::World(MsgQ *q)
{
  char *buf;

  *q >> maxX;
  *q >> maxY;

  world.Resize(maxX, maxY);
  cities.Resize(maxX, maxY);
  units.Resize(maxX, maxY);
  visible.Resize(maxX, maxY);
  working.Resize(maxX, maxY);

  *q >> buf;
  world.CopyFrom((ushort *)buf);
  delete buf;

  for (int x = 0; x < maxX; ++x) for (int y = 0; y < maxY; ++y) {
    cities(x,y) = 0;
    visible(x,y) = 0; // all black
    working(x, y) = 0;
    world(x, y) = ntohs(world(x, y));
  }

  rivers = new Rivers(q, this);
}

World::~World()
{
  delete rivers;
}

void World::AllocColors()
{
  blackColor = screen->AllocColor("#000000000000");
  landColor = screen->AllocColor("#22008b002200");
  waterColor = screen->AllocColor("#00007000ff00");
}

void World::Send()
{
  MsgQ *q = new MsgQ;
  *q << "map_start";
  *q << maxX;
  *q << maxY;

  Array<ushort> nWorld(maxX, maxY);
  for (int x = 0; x < maxX; ++x) for (int y = 0; y < maxY; ++y)
    nWorld(x, y) = htons(world(x, y));

  char *buf = (char *)nWorld.GetArray();
  *q << Msg(buf, (int)world.Size());

  rivers->Send(q);
  SendQ(q);
}

int World::MakeTerrain(int max, int percent, int NumChunks, int ChunkSize,
		       int ContDist, int type, int where)
{
  int x, y;
  long left = (long(max)*percent)/100, n;
  while (left > 0) {
    long numcont = left / (NumChunks * 3L * ChunkSize*ChunkSize) + 1;
    Debug('w', "new continents of %d\n", type);
    for (n = 0; n < numcont; ++n) {
      NewChunk(x, y, ContDist + rnd(ContDist), type, where);
      left -= MakeChunk(x, y, ChunkSize, type);
    }
    Debug('w', "growing\n");
    for (n = 0; n < numcont * NumChunks; ++n) {
      GrowChunk(x, y, type);
      left -= MakeChunk(x, y, ChunkSize, type);
    }
  }
  left = 0;
  for (x = 0; x < maxX; ++x) for (y = 0; y < maxY; ++y)
    if (Terrain(x,y) == type) ++left;
  return left;
}

// find a chunk of the same type to merge with
void World::GrowChunk(int &xp, int &yp, int type)
{
  int x, y;
  while (Terrain(x = rnd(maxX), y = rnd(maxY)) != type);
  xp = x;
  yp = y;
}

// check if there is enough open space to make a new chunk of type
int World::Verify(int x, int y, int dist, int type, int where)
{
  int xbase, ybase, x1, y1, n = 0, side = 2*dist+1;
  for (ybase = y - dist; ybase <= y + dist; ++ybase) {
    for (xbase = x - dist; xbase <= x + dist; ++xbase) {
      y1 = ybase < 0 ? ybase+maxY : ybase % maxY;
      x1 = xbase < 0 ? xbase+maxX : xbase % maxX;
      if (Terrain(x1, y1) == type)
	return 0;
      if (Terrain(x1, y1) == where) ++n;
    }
  }
  double frac = double(n)/side/side;
  return frac > 0.5;
}

// find a spot with enough open space amongst where to make a new chunk of type
void World::NewChunk(int &xp, int &yp, int dist, int type, int where)
{
  int x, y, i;
  for (i = 0; i < 30; ++i) {
    y = rnd(maxY);
    x = rnd(maxX);
    if (Terrain(x, y) == where && Verify(x, y, dist, type, where)) break;
  }
  if (i == 30) // didn't find open space, just merge with another
    GrowChunk(xp, yp, type);
  else { // found open space
    xp = x;
    yp = y;
  }
}

// make a block of given terrain type possibly leaving out spots on the edge
int World::MakeChunk(int x, int y, int dist, int type)
{
  int xbase, ybase;
  int xfail, yfail;
  int x1, y1;
  int created = 0;

  for (ybase = y - dist; ybase <= y + dist; ybase++) {
    yfail = y - ybase;
    if (yfail < 0)
      yfail = -yfail;
    y1 = ybase < 0 ? ybase+maxY : ybase % maxY;
    for (xbase = x - dist; xbase <= x + dist; xbase++) {
      x1 = xbase < 0 ? xbase+maxX : xbase % maxX;
      if (Terrain(x1, y1) == type)
	continue;
      xfail = x - x1;
      if (xfail < 0)
	xfail = -xfail;
      if (xfail < yfail)
	xfail = yfail;
      if (xfail < dist-1 || !rnd(xfail + 1) ||
	  !rnd(xfail + 1)) {
	Set(x1, y1, type);
	created++;
      }
    }
  }
  return created;
}

// draw top left corner of map
void World::Draw(int x, int y)
{
  Draw(x, y, SquaresWide, SquaresHigh, 0, 0);
}

void World::Draw(int x, int y, int cols, int rows, int atx, int aty)
{
  int xc;
  int yc = aty*SquareHeight+VertSpace;
  int realx, realy;
  for (int y1 = 0; y1 < rows; ++y1, yc += SquareHeight) {
    xc = atx*SquareWidth+HorizSpace;
    realy = (y+y1)%maxY;
    for (int x1 = 0; x1 < cols; ++x1, xc += SquareWidth) {
      realx = (x1+x)%maxX;
      if (visible(realx, realy) > 1 && cities(realx, realy) != 0)
	trans->TransCity(cities(realx, realy))->Draw(xc, yc);
      else {
	DrawSquare(realx, realy, xc, yc);
	if (visible(realx, realy) > 1 && units(realx, realy))
	  trans->TransUnit(units(realx, realy).First())->Draw(xc, yc);
      }
    }
  }
}

void World::DrawSquare(int wx, int wy, int xc, int yc)
{
  if (visible(wx, wy) == 0) {
    screen->FillRect(xc, yc, SquareWidth, SquareHeight, blackColor);
    return;
  }
  long what = What(wx, wy);
  long terrain = what & TERRAIN;
  if (terrain == RIVER)
    rivers->DrawRiver(wx, wy, xc, yc);
  else {
    if (compiledPictures[terrain] == 0)
      compiledPictures[terrain] = screen->CompilePixmap(pictures[terrain]);
    screen->DrawPixmap(xc, yc, compiledPictures[terrain]);
    if (what & GOODIE) {
      if (compiledGoodies[terrain] == 0)
	compiledGoodies[terrain] = screen->CompilePixmap(goodiePics[terrain]);
      screen->DrawPixmap(xc, yc, compiledGoodies[terrain]);
    }
    if (what & MINED) {
      if (minedPic == 0)
	minedPic = screen->CompilePixmap(mining_xpm);
      screen->DrawPixmap(xc, yc, minedPic);
    }
  }
}

void World::DrawBase(int x, int y, int atx, int aty)
{
  int xc = atx*SquareWidth+HorizSpace, yc = aty*SquareHeight+VertSpace;
  if (visible(x,y) > 1 && cities(x,y) != 0)
    trans->TransCity(cities(x,y))->Draw(xc, yc);
  else
    DrawSquare(x, y, xc, yc);
}

void World::SeeSquare(int x, int y, int side)
{
  int y1 = (y-side+maxY)%maxY;
  int n = side*2+1;
  for (int i = n; i > 0; --i, y1 = (y1+1)%maxY) {
    int x1 = (x-side+maxX)%maxX;
    for (int j = n; j > 0; --j, x1 = (x1+1)%maxX) {
      int stat = visible(x1, y1);
      if (stat == 0) // discovered new square, mark it on the map
	MarkSquare(x1, y1, -1);
      MakeVisible(x1, y1);
      if (stat < 2 && display->Visible(x1, y1)) { // has become visible
	int sx, sy;
	display->TranslateScreen(x1, y1, sx, sy);
	Draw(x1, y1, 1, 1, sx, sy);
      }
    }
  }
}

void World::HideSquare(int x, int y, int side)
{
  int y1 = (y-side+maxY)%maxY;
  int n = side*2+1;
  for (int i = n; i > 0; --i, y1 = (y1+1)%maxY) {
    int x1 = (x-side+maxX)%maxX;
    for (int j = n; j > 0; --j, x1 = (x1+1)%maxX) {
      LoseVisible(x1, y1);
      if (visible(x1, y1) == 1 && display->Visible(x1, y1)) {
	int sx, sy;
	display->TranslateScreen(x1, y1, sx, sy);
	Draw(x1, y1, 1, 1, sx, sy);
      }
    }
  }
}

void World::MarkSquare(int x, int y, int c)
{
  int sx = (x*HorizSpace)/maxX, sy = (y*64)/maxY+VertSpace;
  if (c == -1)
    c = Terrain(x, y) == WATER ? waterColor : landColor;
  screen->FillRect(sx, sy, 1, 1, c);
}

double World::SquareDefense(int x, int y, int attacker)
{
  double mod = 1;
  City *city;
  if ((city = trans->TransCity(cities(x, y))) != NULL &&
      attacker != BOMBER && attacker != ARTILLERY &&
      city->HasBuilding("City Walls"))
    mod *= 3;
  mod *= terrainDefense[Terrain(x, y)];
  return mod;
}

void World::DrawMainMap()
{
  for (int x = 0; x < maxX; ++x) for (int y = 0; y < maxY; ++y)
    if (visible(x, y) > 0)
      MarkSquare(x, y, -1);
}

int World::Food(int x, int y, int player)
{
  static int goodie[] = { 0, 0, 0, 1, 3, 1, 1, 2, 2, 3, 1, 2 };
  static int normal[] = { 1, 0, 0, 1, 0, 1, 1, 2, 2, 1, 1, 2 };
  static int irrig[]  = { 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1 };
  int food = IsGoodie(x, y) ? goodie[Terrain(x, y)] : normal[Terrain(x, y)];
  if (Irrigated(x, y)) food += irrig[Terrain(x, y)];
  if (IsRailRoad(x, y)) food = int(food*1.5);
  if (food >= 3 && players[player]->govt < MONARCH)
    --food;
  return food;
}

int World::Prod(int x, int y, int player)
{
  static int goodie[] = { 0, 0, 1, 2, 1, 4, 3, 0, 1, 2, 0, 1 };
  static int prod[] =   { 0, 0, 1, 0, 1, 0, 1, 0, 1, 2, 0, 1 };
  static int mining[] = { 0, 0, 1, 3, 1, 0, 0, 0, 0, 0, 0, 0 };
  int p = IsGoodie(x, y) ? goodie[Terrain(x, y)] : prod[Terrain(x, y)];
  if (Mined(x, y)) p += mining[Terrain(x, y)];
  if (p >= 3 && players[player]->govt < MONARCH)
    --p;
  return p;
}

int World::Trade(int x, int y, int player)
{
  static int goodie[] = { 0, 0, 6, 0, 1, 0, 0, 0, 0, 0, 4, 0 };
  static int normal[] = { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
  static int road[] =   { 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0 };
  int trade = IsGoodie(x, y) ? goodie[Terrain(x, y)] : normal[Terrain(x, y)];
  if (IsRoad(x, y) || IsRailRoad(x, y)) trade += road[Terrain(x, y)];
  if (IsRailRoad(x, y)) trade = int(trade*1.5);
  if (trade > 0 && players[player]->govt >= REPUBLIC)
    ++trade;
  if (trade >= 3 && players[player]->govt < MONARCH)
    --trade;
  return trade;
}

int World::IrrigTime(int x, int y)
{
  static int time[] = { -1, -1, -1, 4, -1, 7, 3, 3, 3, 7, 7, 2 };
  int ter = Terrain(x, y);
  if (cities(x, y) != 0 || Irrigated(x, y)) return -1;
  if (time[ter] == -1 && (ter != DESERT || !IsGoodie(x, y)))
    return -1;
  // try and find water or irrig in four directions
  if (GivesWater(x, FixY(y-1)) || GivesWater(x, FixY(y+1)) ||
      GivesWater(FixX(x-1), y) || GivesWater(FixX(x+1), y)) {
    if (ter == DESERT)
      return 3;
    else
      return time[ter];
  }
  return -1;
}

int World::RoadTime(int x, int y)
{
  static int time[] = { -1, -1, 8, 6, 2, 7, 1, 1, 1, 6, 6, 2 };
  return IsRoad(x, y) || IsRailRoad(x, y) ? -1 : time[Terrain(x, y)];
}

int World::RailRoadTime(int x, int y)
{
  static int time[] = { -1, -1, 8, 6, 2, 7, 1, 1, 1, 6, 6, 2 };
  return IsRoad(x, y) ? time[Terrain(x, y)] : -1;
}

int World::MineTime(int x, int y)
{
  static int time[] = { -1, -1, 8, 6, 4, -1, -1, -1, -1, -1, -1, -1 };
  return Mined(x, y) ? -1 : time[Terrain(x, y)];
}

void World::Save()
{
  WriteUShort(maxX);
  WriteUShort(maxY);

  WriteBytes((char *)world.GetFirst(), world.Size());
  WriteBytes((char *)working.GetFirst(), working.Size());
  WriteBytes((char *)visible.GetFirst(), visible.Size());

  rivers->Save();
}

void World::MakeRoad(int x, int y)
{
  if (currTurn != playerId)
    Debug('F', "Can't make road when not in move turn\n");
  world(x, y) |= ROAD;
  *moveQ << PieceMove(0, TERRAIN_CHANGE, x, y, world(x, y));
}

void World::MakeRailRoad(int x, int y)
{
  if (currTurn != playerId)
    Debug('F', "Can't make railroad when not in move turn\n");
  world(x, y) &= ~ROAD;
  world(x, y) |= RAILROAD;
  *moveQ << PieceMove(0, TERRAIN_CHANGE, x, y, world(x, y));
}

void World::Irrigate(int x, int y)
{
  if (currTurn != playerId)
    Debug('F', "Can't irrigate when not in move turn\n");
  int ter = Terrain(x, y);
  switch (ter) {
  case SWAMP:
  case JUNGLE:
  case FOREST:
    if (IsGoodie(x, y)) world(x, y) &= ~GOODIE;
    world(x, y) = world(x, y) & (~TERRAIN) | (rnd(10) > 4 ? GRASS1 : GRASS);
    break;
  default:
    world(x, y) |= IRRIG;
  }
  *moveQ << PieceMove(0, TERRAIN_CHANGE, x, y, world(x, y));
}

void World::Mine(int x, int y)
{
  if (currTurn != playerId)
    Debug('F', "Can't mine when not in move turn\n");
  world(x, y) |= MINED;
  *moveQ << PieceMove(0, TERRAIN_CHANGE, x, y, world(x, y));
}
