#include "world.h"
#include "rivers.h"
#include "MsgQ.h"
#include "riverpic.h"
#include "savefile.h"
#include "debug.h"
#include <stdlib.h>

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

Rivers::Rivers(int NumRivers, int RiverLength, World *pworld)
{
  world = pworld;
  riverMap = new char[world->MaxX()*world->MaxY()];
  riverInfo = new RiverInfo[world->MaxX()*world->MaxY()];
  int x, y, i;

  riverId = 1;
  for (i = 0; i < NumRivers; ++i, ++riverId)
    NewRiver(RiverLength);

  // now translate the riverInfo array into bitmaps
  for (x = 0; x < world->MaxX(); ++x) for (y = 0; y < world->MaxY(); ++y) {
    RiverInfo *ptr = &Get(x, y);
    char *ptr1 = riverMap+y*world->MaxX()+x;
    if (ptr->NumPts > 0) {
      *ptr1 = EncodeRiverBitmap(WhichRiverBitmap(ptr->NumPts,
						 ptr->pts[0], ptr->pts[1],
						 ptr->pts[2]));
      world->Set(x, y, RIVER);
    }
    else
      *ptr1 = 0;
  }
  delete [] riverInfo;
}

Rivers::Rivers(World *pworld)
{
  world = pworld;
  riverMap = new char[world->MaxX()*world->MaxY()];
  ReadBytes(riverMap, world->MaxX()*world->MaxY());
  Debug('s', "Restored rivers\n");
}

Rivers::Rivers(MsgQ *q, World *pworld)
{
  world = pworld;

  *q >> riverMap;
  delete q;
}

Rivers::~Rivers()
{
  delete [] riverMap;
}

void Rivers::Save()
{
  WriteBytes(riverMap, world->MaxX()*world->MaxY());
}

void Rivers::Send(MsgQ *q)
{
  int len = world->MaxX()*world->MaxY();
  char *buf = new char[len];
  memcpy(buf, riverMap, len);
  *q << Msg(buf, len);
}

// if you call this x and y must be within the right limits
void Rivers::DrawRiver(int x, int y, int atx, int aty)
{
  char *ptr = riverMap+y*world->MaxX()+x;
  if (*ptr != 0)
    ::DrawRiver(atx, aty, *ptr);
}

void Rivers::NewRiver(int riverLength)
{
  int dir, x, y;

  // pick a point next to the sea where the river will end
  RiverEnd(x, y, dir);

  riverLength += rnd(riverLength*4) - riverLength;

  // now walk the river uphill till riverLength runs out or we hit
  // another river
  while (riverLength > 0) {
    int x1, y1, dir1;
    int stat = PickNewDir(x, y, dir, x1, y1, dir1);
    if (riverLength == 1 || stat == 2) {
      // end of river
      Get(x, y).riverId = riverId;
      AddPiece(x, y, dir, -1);
      break;
    }
    else if (stat == 1) {
      // normal river move
      Get(x, y).riverId = riverId;
      AddPiece(x, y, dir, dir1);
      x = x1;
      y = y1;
      dir = (dir1+2)%4;
      --riverLength;
    }
    else {
      // going to hit another river, so we should
      // make both moves and stop
      Get(x, y).riverId = riverId;
      AddPiece(x, y, dir, dir1);
      Get(x1, y1).riverId = riverId;
      AddPiece(x1, y1, (dir1+2)%4, -1);
      break;
    }
  }
}

void Rivers::ConvDir(int x, int y, int dir, int &x1, int &y1)
{
  x1 = x;
  y1 = y;
  switch (dir) {
  case 0:
    x1 = x-1; break;
  case 1:
    y1 = y-1; break;
  case 2:
    x1 = x+1; break;
  case 3:
    y1 = y+1; break;
  }
  if (x1 < 0) x1 = world->MaxX()-1;
  if (x1 >= world->MaxX()) x1 = 0;
  if (y1 < 0) y1 = world->MaxY()-1;
  if (y1 >= world->MaxY()) y1 = 0;
}

int Rivers::PickNewDir(int x, int y, int fromdir, int &x1, int &y1, int &dir)
{
  int forward = (fromdir+2)%4, left = (fromdir+1)%4, right = (fromdir+3)%4;
  int fx, fy, lx, ly, rx, ry, found = 1;
  ConvDir(x, y, forward, fx, fy);
  ConvDir(x, y, left, lx, ly);
  ConvDir(x, y, right, rx, ry);
  // river has about 60% chance of going in forward dir
  int chance = rand()%10;
  // try and pick new dir as highest
  // can the river go forward
  if (world->Terrain(fx, fy) != WATER && chance < 6 &&
      Get(fx, fy).riverId != riverId && Get(fx, fy).NumPts != 1) {
    x1 = fx;
    y1 = fy;
    dir = forward;
  }
  else if (world->Terrain(lx, ly) != WATER && Get(lx, ly).NumPts != 1 &&
	   chance < 8 && Get(lx, ly).riverId != riverId) {
    x1 = lx;
    y1 = ly;
    dir = left;
  }
  else if (world->Terrain(rx, ry) != WATER && Get(rx, ry).NumPts != 1 &&
	   Get(rx, ry).riverId != riverId) {
    x1 = rx;
    y1 = ry;
    dir = right;
  }
  else if (world->Terrain(fx, fy) != WATER && Get(fx, fy).NumPts != 1 &&
	   Get(fx, fy).riverId != riverId) {
    x1 = fx;
    y1 = fy;
    dir = forward;
  }
  else if (world->Terrain(lx, ly) != WATER && Get(lx, ly).NumPts != 1 &&
	   Get(lx, ly).riverId != riverId) {
    x1 = lx;
    y1 = ly;
    dir = left;
  }
  else
    found = 0;
  if (found)
    return Get(x1, y1).NumPts > 0 ? 3 : 1;
  else
    return 2;
}

void Rivers::AddPiece(int x, int y, int fromdir, int todir)
{
  int n = Get(x, y).NumPts;
  // already a river here
  if (n > 0) {
    if (n != 3) Get(x, y).pts[n] = fromdir;
    ++Get(x, y).NumPts;
  }
  else {
    Get(x, y).NumPts = todir == -1 ? 1 : 2;
    Get(x, y).pts[0] = fromdir;
    Get(x, y).pts[1] = todir;
  }
}

void Rivers::RiverEnd(int &ex, int &ey, int &dir)
{
  // try and pick a land point which has sea next to it and has all
  // three possible river directions open
  int i;
  do {
    ex = rnd(world->MaxX());
    ey = rnd(world->MaxY());
    if (world->Terrain(ex, ey) == WATER || Get(ex, ey).NumPts != 0) continue;

    int numSea = 0, bad = 0, seaDir;
    for (i = 0; i < 4; ++i) {
      int x1, y1;
      ConvDir(ex, ey, i, x1, y1);
      if (Get(x1, y1).NumPts != 0) ++bad;
      if (world->Terrain(x1, y1) == WATER) {
	++numSea;
	seaDir = i;
      }
    }
    if (numSea != 1 || bad != 0) continue;

    // found a nice square
    dir = seaDir;
    return;
  }
  while (1);
}
