#include "defs.h"

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

inline int rnd(int max) { return random() % 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 i;

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

  // now translate the riverInfo array into bitmaps
  for(MapCoord c(0,0); c.y < world->MaxY(); ++c) {
    RiverInfo *ptr = &Get(c);
    char *ptr1 = riverMap+c.y*world->MaxX()+c.x;
    if (ptr->NumPts > 0) {
      *ptr1 = EncodeRiverBitmap(WhichRiverBitmap(ptr->NumPts,
						 ptr->pts[0], ptr->pts[1],
						 ptr->pts[2]));
      world->Set(c, 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(WDisplay *window, MapCoord c)
{
  char *ptr = riverMap+c.y*world->MaxX()+c.x;
  if (*ptr != 0)
    ::DrawRiver(window, c, *ptr);
}

void Rivers::NewRiver(int riverLength)
{
  MapCoord coord;
  int dir;

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

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

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

void Rivers::ConvDir(MapCoord coord, int dir, MapCoord &coord1)
{
  switch (dir) {
  case 0:
    coord1 = coord.left(); break;
  case 1:
    coord1 = coord.up(); break;
  case 2:
    coord1 = coord.right(); break;
  case 3:
    coord1 = coord.down(); break;
  default:
    fprintf(stderr, "internal error in Rivers::ConvDir\n");
  }
}

int Rivers::PickNewDir(MapCoord coord, int fromdir, MapCoord &coord1, int &dir)
{
  int forward = (fromdir+2)%4, left = (fromdir+1)%4, right = (fromdir+3)%4;
  MapCoord f,l, r;
  int found = 1;
  ConvDir(coord, forward, f);
  ConvDir(coord, left, l);
  ConvDir(coord, right, r);
  // river has about 60% chance of going in forward dir
  int chance = random()%10;
  // try and pick new dir as highest
  // can the river go forward
  if (world->Terrain(f) != WATER && chance < 6 &&
      Get(f).riverId != riverId && Get(f).NumPts != 1) {
    coord1 = f;
    dir = forward;
  }
  else if (world->Terrain(l) != WATER && Get(l).NumPts != 1 &&
	   chance < 8 && Get(l).riverId != riverId) {
    coord1 = l;
    dir = left;
  }
  else if (world->Terrain(r) != WATER && Get(r).NumPts != 1 &&
	   Get(r).riverId != riverId) {
    coord1 = r;
    dir = right;
  }
  else if (world->Terrain(f) != WATER && Get(f).NumPts != 1 &&
	   Get(f).riverId != riverId) {
    coord1 = f;
    dir = forward;
  }
  else if (world->Terrain(l) != WATER && Get(l).NumPts != 1 &&
	   Get(l).riverId != riverId) {
    coord1 = l;
    dir = left;
  }
  else
    found = 0;
  if (found)
    return Get(coord).NumPts > 0 ? 3 : 1;
  else
    return 2;
}

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

void Rivers::RiverEnd(MapCoord &coord, 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 {
    coord.x = rnd(world->MaxX());
    coord.y = rnd(world->MaxY());
    if (world->Terrain(coord) == WATER || Get(coord).NumPts != 0) continue;

    int numSea = 0, bad = 0, seaDir=-99;
    for (i = 0; i < 4; ++i) {
      MapCoord coord1;
      ConvDir(coord, i, coord1);
      if (Get(coord1).NumPts != 0) ++bad;
      if (world->Terrain(coord1) == WATER) {
	++numSea;
	seaDir = i;
      }
    }
    if (numSea != 1 || bad != 0) continue;

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