/*-----------------------------------------------------------------------------

  World.cpp

  2009 Shamus Young

-------------------------------------------------------------------------------

  This holds a bunch of variables used by the other modules. It has the 
  claim system, which tracks all of the "property" is being used: As roads,
  buildings, etc. 

-----------------------------------------------------------------------------*/

#define LIGHT_COLOR_COUNT (sizeof(light_colors)/sizeof(HSL))

#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "glTypes.h"
#include "Building.h"
#include "Car.h"
#include "Deco.h"
#include "GetTickCount.h"
#include "Light.h"
#include "Macro.h"
#include "Random.h"
#include "Sky.h"
#include "Texture.h"
#include "World.h"

struct plot
{
  int             x;
  int             z;
  int             width;
  int             depth;

};

enum {
  FADE_IDLE,
  FADE_OUT,
  FADE_WAIT,
  FADE_IN,
};

struct HSL
{
  float     hue;
  float     sat;
  float     lum;
};

class CStreet
{
public:
  int                 _x;
  int                 _y;
  int                 _width;
  int                 _depth;
  CMesh*              _mesh;
  
  CStreet (int x, int y, int width, int depth);
  ~CStreet();
  void                Render ();

};

static HSL            light_colors[] = 
{ 
  { 0.04f,  0.9f,  0.93f },  //Amber / pink
  { 0.055f, 0.95f, 0.93f },  //Slightly brighter amber 
  { 0.08f,  0.7f,  0.93f },  //Very pale amber
  { 0.07f,  0.9f,  0.93f },  //Very pale orange
  { 0.1f,   0.9f,  0.85f },  //Peach
  { 0.13f,  0.9f,  0.93f },  //Pale Yellow
  { 0.15f,  0.9f,  0.93f },  //Yellow
  { 0.17f,  1.0f,  0.85f },  //Saturated Yellow
  { 0.55f,  0.9f,  0.93f },  //Cyan
  { 0.55f,  0.9f,  0.93f },  //Cyan - pale, almost white
  { 0.6f,   0.9f,  0.93f },  //Pale blue
  { 0.65f,  0.9f,  0.93f },  //Pale Blue II, The Palening
  { 0.65f,  0.4f,  0.99f },  //Pure white. Bo-ring.
  { 0.65f,  0.0f,  0.8f  },   //Dimmer white.
  { 0.65f,  0.0f,  0.6f  },   //Dimmest white.
 }; 

static GLrgba         bloom_color;
static long           last_update;
static char           world[WORLD_SIZE][WORLD_SIZE];
static CSky*          sky;
static int            fade_state;
static unsigned       fade_start;
static float          fade_current;
static int            modern_count;
static int            tower_count;
static int            blocky_count;
static bool           reset_needed;
static int            skyscrapers;
static GLbbox         hot_zone;
static int            logo_index;
static int            scene_begin;

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

static GLrgba get_light_color (float sat, float lum)
{

  int     index;

  index = RandomVal (LIGHT_COLOR_COUNT);
  return glRgbaFromHsl (light_colors[index].hue, sat, lum);

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

static void claim (int x, int y, int width, int depth, int val)
{

  int     xx, yy;

  for (xx = x; xx < (x + width); xx++) {
    for (yy = y; yy < (y + depth); yy++) {
      world[CLAMP (xx,0,WORLD_SIZE - 1)][CLAMP (yy,0,WORLD_SIZE - 1)] |= val;
    }
  }

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

static bool claimed (int x, int y, int width, int depth)
{

  int     xx, yy;

  for (xx = x; xx < x + width; xx++) {
    for (yy = y; yy < y + depth; yy++) {
      if (world[CLAMP (xx,0,WORLD_SIZE - 1)][CLAMP (yy,0,WORLD_SIZE - 1)])
        return true;
    }
  }
  return false;

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

static void build_road (int x1, int y1, int width, int depth)
{

  int       lanes;
  int       divider;
  int       sidewalk;

  //the given rectangle defines a street and its sidewalk. See which way it goes.
  if (width > depth) 
    lanes = depth;
  else
    lanes = width;
  //if we dont have room for both lanes and sidewalk, abort
  if (lanes < 4)
    return;
  //if we have an odd number of lanes, give the extra to a divider.
  if (lanes % 2) {
    lanes--;
    divider = 1;
  } else
    divider = 0;
  //no more than 10 traffic lanes, give the rest to sidewalks
  sidewalk = MAX (2, (lanes - 10));
  lanes -= sidewalk;
  sidewalk /= 2;
  //take the remaining space and give half to each direction
  lanes /= 2;
  //Mark the entire rectangle as used
  claim (x1, y1, width, depth, CLAIM_WALK);
  //now place the directional roads
  if (width > depth) {
    claim (x1, y1 + sidewalk, width, lanes, CLAIM_ROAD | MAP_ROAD_WEST);
    claim (x1, y1 + sidewalk + lanes + divider, width, lanes, CLAIM_ROAD | MAP_ROAD_EAST);
  } else {
    claim (x1 + sidewalk, y1, lanes, depth, CLAIM_ROAD | MAP_ROAD_SOUTH);
    claim (x1 + sidewalk + lanes + divider, y1, lanes, depth, CLAIM_ROAD | MAP_ROAD_NORTH);
  }

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

static plot find_plot (int x, int z)
{

  plot      p;
  int       x1, x2, z1, z2;

  //We've been given the location of an open bit of land, but we have no 
  //idea how big it is. Find the boundary.
  x1 = x2 = x;
  while (!claimed (x1 - 1, z, 1, 1) && x1 > 0)
    x1--;
  while (!claimed (x2 + 1, z, 1, 1) && x2 < WORLD_SIZE)
    x2++;
  z1 = z2 = z;
  while (!claimed (x, z1 - 1, 1, 1) && z1 > 0)
    z1--;
  while (!claimed (x, z2 + 1, 1, 1) && z2 < WORLD_SIZE)
    z2++;
  p.width = (x2 - x1);
  p.depth = (z2 - z1);
  p.x = x1;
  p.z = z1;
  return p;

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

static plot make_plot (int x, int z, int width, int depth)
{

  plot      p = {x, z, width, depth};
  return p;

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

void do_building (plot p)
{

  int     height;
  int     seed;
  int     area;
  int     type;
  GLrgba  color;
  bool    square;

  //now we know how big the rectangle plot is. 
  area = p.width * p.depth;
  color = WorldLightColor (RandomVal ());
  seed = RandomVal ();
  //Make sure the plot is big enough for a building
  if (p.width < 10 || p.depth < 10)
    return;
  //If the area is too big for one building, sub-divide it.
 
  if (area > 800) {
    if (COIN_FLIP) {
      p.width /= 2;
      if (COIN_FLIP)
        do_building (make_plot (p.x, p.z, p.width, p.depth));
      else
        do_building (make_plot (p.x + p.width, p.z, p.width, p.depth));
      return;
    } else {
      p.depth /= 2;
      if (COIN_FLIP)
        do_building (make_plot (p.x, p.z, p.width, p.depth));
      else
        do_building (make_plot (p.x, p.z + p.depth, p.width, p.depth));
      return;
    }
  }
  if (area < 100)
    return;
  //The plot is "square" if width & depth are close
  square = abs (p.width - p.depth) < 10;
  //mark the land as used so other buildings don't appear here, even if we don't use it all.
  claim (p.x, p.z, p.width, p.depth, CLAIM_BUILDING);
  
  //The roundy mod buildings look best on square plots.
  if (square && p.width > 20) {
    height = 45 + RandomVal (10);
    modern_count++;
    skyscrapers++;
    new CBuilding (BUILDING_MODERN, p.x, p.z, height, p.width, p.depth, seed, color);
    return;
  }
  /*
  //Rectangular plots are a good place for Blocky style buildsing to sprawl blockily.
  if (p.width > p.depth * 2 || p.depth > p.width * 2 && area > 800) {
    height = 20 + RandomVal (10);
    blocky_count++;
    skyscrapers++;
    new CBuilding (BUILDING_BLOCKY, p.x, p.z, height, p.width, p.depth, seed, color);
    return;
  }
  */
  //tower_count = -1;
  //This spot isn't ideal for any particular building, but try to keep a good mix
  if (tower_count < modern_count && tower_count < blocky_count) {
    type = BUILDING_TOWER;
    tower_count++;
  } else if (blocky_count < modern_count) {
    type = BUILDING_BLOCKY;
    blocky_count++;
  } else {
    type = BUILDING_MODERN;
    modern_count++;
  }
  height = 45 + RandomVal (10);
  new CBuilding (type, p.x, p.z, height, p.width, p.depth, seed, color);
  skyscrapers++;

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

static int build_light_strip (int x1, int z1, int direction)
{

  CDeco*  d;
  GLrgba  color;
  int     x2, z2;
  int     length;
  int     width, depth;
  int     dir_x = 0, dir_z = 0;
  float   size_adjust;
  
  //We adjust the size of the lights with this.  
  size_adjust = 2.5f;
  color = glRgbaFromHsl (0.09f,  0.99f,  0.85f);
  switch (direction) {
  case NORTH:
    dir_z = 1; dir_x = 0;break;
  case SOUTH:
    dir_z = 1; dir_x = 0;break;
  case EAST:
    dir_z = 0; dir_x = 1;break;
  case WEST:
    dir_z = 0; dir_x = 1;break;
  }
  //So we know we're on the corner of an intersection
  //look in the given  until we reach the end of the sidewalk
  x2 = x1;
  z2 = z1;
  length = 0;
  while (x2 > 0 && x2 < WORLD_SIZE && z2 > 0 && z2 < WORLD_SIZE) {
    if ((world[x2][z2] & CLAIM_ROAD))
      break;
    length++;
    x2 += dir_x;
    z2 += dir_z;
  }
  if (length < 10)
    return length;
  width = MAX (abs(x2 - x1), 1);
  depth = MAX (abs(z2 - z1), 1);
  d = new CDeco;
  if (direction == EAST)
    d->CreateLightStrip ((float)x1, (float)z1 - size_adjust, (float)width, (float)depth + size_adjust, 2, color);
  else if (direction == WEST)
    d->CreateLightStrip ((float)x1, (float)z1, (float)width, (float)depth + size_adjust, 2, color);
  else if (direction == NORTH)
    d->CreateLightStrip ((float)x1, (float)z1, (float)width + size_adjust, (float)depth, 2, color);
  else
    d->CreateLightStrip ((float)x1 - size_adjust, (float)z1, (float)width + size_adjust, (float)depth, 2, color);
  return length;

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

static void do_reset (void)
{
  
  int       x, y;
  int       width, depth, height;
  int       attempts;
  bool      broadway_done;
  bool      road_left, road_right;
  GLrgba    light_color;
  GLrgba    building_color;
  float     west_street = 0, north_street = 0, east_street = 0, south_street = 0;

  reset_needed = false;
  broadway_done = false;
  skyscrapers = 0;
  logo_index = 0;
  scene_begin = 0;
  tower_count = blocky_count = modern_count = 0;
  hot_zone = glBboxClear ();
  EntityClear ();
  LightClear ();
  CarClear ();
  TextureReset ();
  //Pick a tint for the bloom 
  bloom_color = get_light_color(0.5f + (float)RandomVal (10) / 20.0f, 0.75f);
  light_color = glRgbaFromHsl (0.11f, 1.0f, 0.65f);
  memset (world, 0, WORLD_SIZE * WORLD_SIZE);
  for (y = WORLD_EDGE; y < WORLD_SIZE - WORLD_EDGE; y += RandomVal (25) + 25) {
    if (!broadway_done && y > WORLD_HALF - 20) {
      build_road (0, y, WORLD_SIZE, 19);
      y += 20;
      broadway_done = true;
    } else {
      depth = 6 + RandomVal (6);
      if (y < WORLD_HALF / 2)
        north_street = (float)(y + depth / 2);
      if (y < (WORLD_SIZE - WORLD_HALF / 2))
        south_street = (float)(y + depth / 2);
      build_road (0, y, WORLD_SIZE, depth);
    }
  }

  broadway_done = false;
  for (x = WORLD_EDGE; x < WORLD_SIZE - WORLD_EDGE; x += RandomVal (25) + 25) {
    if (!broadway_done && x > WORLD_HALF - 20) {
      build_road (x, 0, 19, WORLD_SIZE);
      x += 20;
      broadway_done = true;
    } else {
      width = 6 + RandomVal (6);
      if (x <= WORLD_HALF / 2)
        west_street = (float)(x + width / 2);
      if (x <= WORLD_HALF + WORLD_HALF / 2)
        east_street = (float)(x + width / 2);
      build_road (x, 0, width, WORLD_SIZE);
    }
  }
  //We kept track of the positions of streets that will outline the high-detail hot zone 
  //in the middle of the world.  Save this in a bounding box so that later we can 
  //have the camera fly around without clipping through buildings.
  hot_zone = glBboxContainPoint (hot_zone, glVector (west_street, 0.0f, north_street)); 
  hot_zone = glBboxContainPoint (hot_zone, glVector (east_street, 0.0f, south_street));
  
  //Scan for places to put runs of streetlights on the east & west side of the road
  for (x = 1; x < WORLD_SIZE - 1; x++) {
    for (y = 0; y < WORLD_SIZE; y++) {
      //if this isn't a bit of sidewalk, then keep looking
      if (!(world[x][y] & CLAIM_WALK))
        continue;
      //If it's used as a road, skip it.
      if ((world[x][y] & CLAIM_ROAD))
        continue;
      road_left = (world[x + 1][y] & CLAIM_ROAD) != 0;
      road_right = (world[x - 1][y] & CLAIM_ROAD) != 0;
      //if the cells to our east and west are not road, then we're not on a corner. 
      if (!road_left && !road_right)
        continue;
      //if the cell to our east AND west is road, then we're on a median. skip it
      if (road_left && road_right)
        continue;
      y += build_light_strip (x, y, road_right ? SOUTH : NORTH);
    }
  }
  //Scan for places to put runs of streetlights on the north & south side of the road
  for (y = 1; y < WORLD_SIZE - 1; y++) {
    for (x = 1; x < WORLD_SIZE - 1; x++) {
      //if this isn't a bit of sidewalk, then keep looking
      if (!(world[x][y] & CLAIM_WALK))
        continue;
      //If it's used as a road, skip it.
      if ((world[x][y] & CLAIM_ROAD))
        continue;
      road_left = (world[x][y + 1] & CLAIM_ROAD) != 0;
      road_right = (world[x][y - 1] & CLAIM_ROAD) != 0;
      //if the cell to our east AND west is road, then we're on a median. skip it
      if (road_left && road_right)
        continue;
      //if the cells to our north and south are not road, then we're not on a corner. 
      if (!road_left && !road_right)
        continue;
      x += build_light_strip (x, y, road_right ? EAST : WEST);
    }
  }
  
  
  //Scan over the center area of the map and place the big buildings 
  attempts = 0;
   while (skyscrapers < 50 && attempts < 350) {
    x = (WORLD_HALF / 2) + (RandomVal () % WORLD_HALF);
    y = (WORLD_HALF / 2) + (RandomVal () % WORLD_HALF);
    if (!claimed (x, y, 1,1)) {
      do_building (find_plot (x, y));
      skyscrapers++;
    }
    attempts++;
  }
  
  //now blanket the rest of the world with lesser buildings
  for (x = 0; x < WORLD_SIZE; x ++) {
    for (y = 0; y < WORLD_SIZE; y ++) {
      if (world[CLAMP (x,0,WORLD_SIZE)][CLAMP (y,0,WORLD_SIZE)])
        continue;
      width = 12 + RandomVal (20);
      depth = 12 + RandomVal (20);
      height = MIN (width, depth);
      if (x < 30 || y < 30 || x > WORLD_SIZE - 30 || y > WORLD_SIZE - 30)
        height = RandomVal (15) + 20;
      else if (x < WORLD_HALF / 2)
        height /= 2;
      while (width > 8 && depth > 8) {
        if (!claimed (x, y, width, depth)) {
          claim (x, y, width, depth,CLAIM_BUILDING);
          building_color = WorldLightColor (RandomVal ());
          //if we're out of the hot zone, use simple buildings
          if (x < hot_zone.min.x || x > hot_zone.max.x || y < hot_zone.min.z || y > hot_zone.max.z) {
            height = 5 + RandomVal (height) + RandomVal (height);
            new CBuilding (BUILDING_SIMPLE, x + 1, y + 1, height, width - 2, depth - 2, RandomVal (), building_color);
          } else { //use fancy buildings.
            height = 15 + RandomVal (15);
            width -=2;
            depth -=2;
            if (COIN_FLIP) 
              new CBuilding (BUILDING_TOWER, x + 1, y + 1, height, width, depth, RandomVal (), building_color);
            else
              new CBuilding (BUILDING_BLOCKY, x + 1, y + 1, height, width, depth, RandomVal (), building_color);
          }
          break;
        }
        width--;
        depth--;
      }
      //leave big gaps near the edge of the map, no need to pack detail there.
      if (y < WORLD_EDGE || y > WORLD_SIZE - WORLD_EDGE) 
        y += 32;
    }
    //leave big gaps near the edge of the map
    if (x < WORLD_EDGE || x > WORLD_SIZE - WORLD_EDGE) 
      x += 28;
  }
  

}

/*-----------------------------------------------------------------------------

  This will return a random color which is suitible for light sources, taken
  from a narrow group of hues. (Yellows, oranges, blues.)

-----------------------------------------------------------------------------*/

GLrgba WorldLightColor (unsigned index)
{

  index %= LIGHT_COLOR_COUNT;
  return glRgbaFromHsl (light_colors[index].hue, light_colors[index].sat, light_colors[index].lum);

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

char WorldCell (int x, int y)
{

  return world[CLAMP (x, 0,WORLD_SIZE - 1)][CLAMP (y, 0, WORLD_SIZE - 1)];

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

GLrgba WorldBloomColor ()
{

  return bloom_color;

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

int WorldLogoIndex ()
{

  return logo_index++;

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

GLbbox WorldHotZone ()
{

  return hot_zone;

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

void WorldTerm (void)
{


}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

void WorldReset (void)
{

  //If we're already fading out, then this is the developer hammering on the 
  //"rebuild" button.  Let's hurry things up for the nice man...
  if (fade_state == FADE_OUT) 
    do_reset ();
  //If reset is called but the world isn't ready, then don't bother fading out.
  //The program probably just started.
  fade_state = FADE_OUT;
  fade_start = GetTickCount ();

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

void WorldRender ()
{

  if (!SHOW_DEBUG_GROUND) 
    return;
  //Render a single texture over the city that shows traffic lanes
  glDepthMask (false);
  glDisable (GL_CULL_FACE);
  glDisable (GL_BLEND);
  glEnable (GL_TEXTURE_2D);
  glColor3f (1,1,1);
  glBindTexture (GL_TEXTURE_2D, 0);
  glBegin (GL_QUADS);
  glTexCoord2f (0, 0);   glVertex3f ( 0., 0, 0);
  glTexCoord2f (0, 1);   glVertex3f ( 0, 0,  1024);
  glTexCoord2f (1, 1);   glVertex3f ( 1024, 0, 1024);
  glTexCoord2f (1, 0);   glVertex3f ( 1024, 0, 0);
  glEnd ();
  glDepthMask (true);


}


/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

float WorldFade (void)
{

  return fade_current;

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

int WorldSceneBegin ()
{

  return scene_begin;

}

/*-----------------------------------------------------------------------------

  How long since this current iteration of the city went on display,

-----------------------------------------------------------------------------*/

int WorldSceneElapsed ()
{

  int     elapsed;

  if (!EntityReady () || !WorldSceneBegin ())
    elapsed = 1;
  else
    elapsed = GetTickCount () - (WorldSceneBegin ());
  elapsed = MAX (elapsed, 1);
  return elapsed;

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

void WorldUpdate (void)
{

  unsigned      fade_delta;
  int           now;

  now = GetTickCount ();
  if (reset_needed) {
    do_reset (); //Now we've faded out the scene, rebuild it
  }
  if (fade_state != FADE_IDLE) {
    if (fade_state == FADE_WAIT && TextureReady () && EntityReady ()) {
        fade_state = FADE_IN;
        fade_start = now;
        fade_current = 1.0f;
    }    
    fade_delta = now - fade_start;
    //See if we're done fading in or out
    if (fade_delta > FADE_TIME && fade_state != FADE_WAIT) {
      if (fade_state == FADE_OUT) {
        reset_needed = true;
        fade_state = FADE_WAIT;
        fade_current = 1.0f;
      } else {
        fade_state = FADE_IDLE;
        fade_current = 0.0f;
        scene_begin = GetTickCount ();
      }
    } else {
      fade_current = (float)fade_delta / FADE_TIME;
      if (fade_state == FADE_IN)
        fade_current = 1.0f - fade_current;
      if (fade_state == FADE_WAIT)
        fade_current = 1.0f;
    }
    if (!TextureReady ())
      fade_current = 1.0f;
  } 
  if (fade_state == FADE_IDLE && !TextureReady ()) {
    fade_state = FADE_IN;
    fade_start = now;
  }
  if (fade_state == FADE_IDLE && WorldSceneElapsed () > RESET_INTERVAL)
    WorldReset ();

}

/*-----------------------------------------------------------------------------

-----------------------------------------------------------------------------*/

void WorldInit (void)
{

  last_update = GetTickCount ();
  for (int i = 0; i < CARS; i++)
    new CCar ();
  sky = new CSky ();
  WorldReset ();
  fade_state = FADE_OUT;
  // Actual GetTickCount would start greater than FADE_TIME, triggering an 
  // immediate switch to FADE_IDLE and a do_reset(). Since the fake one 
  // starts at zero, fudge fade_start back.
  fade_start = -FADE_TIME - 1;

}
