#include <cstdio>
#include <cstdlib>
#include <ctime>
#include "GameSim.h"


#define TIME_BASED 1


using namespace Game;



GameSim::GameSim()
{
  reset();
}


GameSim::~GameSim()
{
}


void GameSim::reset()
{
  std::vector<GameObjectList*> lists = objectLists();
  for (GameObjectList* list : lists)
    list->clear();
  //m_carObjects.clear();
  generateLevel();
  m_gameOver = false;
  m_won = false;
  m_time = 0.0f;
}


static bool rectangleIntersect(int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2)
{
  // Early out
  //if (ax1 > bx2 || bx1 > ax2 || ay1 > by2 || by1 > ay2)
  //  return false;
  bool overlayInX = (ax1 > bx1 && ax1 < bx2) || (ax2 > bx1 && ax2 < bx2) || (bx1 > ax1 && bx1 < ax2) || (bx2 > ax1 && bx2 < ax2);
  if (!overlayInX)
    return false;
  bool overlayInY = (ay1 > by1 && ay1 < by2) || (ay2 > by1 && ay2 < by2) || (by1 > ay1 && by1 < ay2) || (by2 > ay1 && by2 < ay2);
  if (!overlayInY)
    return false;
  return true; // overlaps in X and Y dimensions, therefore intersect
}


static float mod(float x, float y)
{
    return x - y * floor(x/y);
}


static bool objectsIntersect(float a_time, const GameObject& a_objA, const GameObject& a_objB)
{
#ifndef TIME_BASED
  return rectangleIntersect(a_objA.m_x, a_objA.m_y, a_objA.m_x + a_objA.m_w, a_objA.m_y + a_objA.m_h,
                a_objB.m_x, a_objB.m_y, a_objB.m_x + a_objB.m_w, a_objB.m_y + a_objB.m_h);
#else
  int x1 = mod(a_objB.m_x + a_objB.m_w + a_time*a_objB.m_dx, 1000.0 + a_objB.m_w) - a_objB.m_w;
  int x2 = mod(a_objB.m_x + a_objB.m_w + a_time*a_objB.m_dx, 1000.0 + a_objB.m_w) - 0;
  /*
  int x1 = (int(a_objB.m_x + a_objB.m_w + a_time*a_objB.m_dx) % int(1000 + a_objB.m_w)) - a_objB.m_w;
  int x2 = (int(a_objB.m_x + a_objB.m_w + a_time*a_objB.m_dx) % int(1000 + a_objB.m_w)) - 0;
  */
  return rectangleIntersect(a_objA.m_x, a_objA.m_y, a_objA.m_x + a_objA.m_w, a_objA.m_y + a_objA.m_h,
                x1, a_objB.m_y, x2, a_objB.m_y + a_objB.m_h);
#endif
}


void GameSim::movePlayer(Movement a_movement)
{
  if (m_gameOver)
    return;

  float origX = m_player.front().m_x;
  float origY = m_player.front().m_y;

  if (a_movement == Forward)
  {
    m_player.front().m_y += 10;
  }
  else if (a_movement == Backward)
  {
    m_player.front().m_y -= 10;
  }
  else if (a_movement == Left)
  {
    m_player.front().m_x -= 10;
  }
  else if (a_movement == Right)
  {
    m_player.front().m_x += 10;
  }

  bool onWater = false;
  //bool willHitObstacle = false;

  // reset
  for (GameObject& tile : m_backgroundLevel)
  {
    if (tile.m_type == Water || tile.m_type == WaterLane)
    {
        if (objectsIntersect(m_time, m_player.front(), tile))
        {
            onWater = true;
            break;
        }
    }
  }
  for (GameObject& tile : m_populationLevel)
  {
    if (objectsIntersect(m_time, m_player.front(), tile))
    {
      if (onWater)
      {
         // maybe game over if we aren't on a craft
      }
      else
      {
        // rollback movement
        m_player.front().m_x = origX;
        m_player.front().m_y = origY;
      }
    }
  }
}


int heightForType(ObjectType t)
{
  if (t == Ground)
   return -4;
  return (t == Water || t == WaterLane) ? -3 : ((t == Rail || t == Ground) ? -1 : -2);
}


int GameSim::getLaneHeight(int y)
{
  int lane = y / 10;
  if (lane < 0 || lane >= lanes)
    return 0;
  ObjectType t = laneFlags[lane];
  return heightForType(t);
}


int GameSim::getPlayerHeight()
{
  int z = getLaneHeight(m_player.front().m_y);
  if (m_player.front().m_dx)
    return z + 15;
  return z;
}


void GameSim::generateLevel()
{
    for (int y = 0; y < lanes; y++)
    {
      int laneWidth = m_rng.generate(0, 5);
      ObjectType laneFlag = ObjectType(m_rng.generate(0, 4));

      if (y && laneFlags[y-1] == laneFlag)
      {
        y--;
        continue;
      }

      if (laneFlag == Water)
        laneWidth = 1;
      if (laneFlag == Rail && laneWidth > 3)
        laneWidth = 3;

      for (int i = 0; i < laneWidth; i++)
      {
        laneFlags[y] = laneFlag;
        y++;
        if (y >= lanes)
        {
          break;
        }
      }
      y--;
    }

    laneFlags[0] = Ground;
    laneFlags[1] = Ground;
    laneFlags[2] = Ground;
    laneFlags[3] = Ground;
    laneFlags[4] = Ground;
    laneFlags[5] = Ground;
    laneFlags[6] = Ground;

    //for (int x = 0; x < 100; x++)
    {
       for (int y = (lanes-1); y >= 0; )//y--)
       {
          GameObject tile;
          tile.m_lane = y;
          tile.m_type = laneFlags[y];
          //int y1 = y;
          int laneWidth = 0;
          while (y>=0 && laneFlags[y] == tile.m_type)
          {
              y--;
              laneWidth++;
          }
          tile.m_x = 0;//x * 10;
          tile.m_y = (y+1) * 10;
          tile.m_z = heightForType(tile.m_type);
          tile.m_w = 10*100; //10;
          tile.m_h = 10*laneWidth;
          tile.m_d = 6; // height for terrain
          tile.m_dx = 0;
          tile.m_dy = 0;
          tile.m_color = 0x808020 + (laneFlags[y]*5);
          m_backgroundLevel.push_back(tile);
       }
    }

    for (int y = (lanes-1); y >= 0; y--)
    {
      if (laneFlags[y] == Ground) // if Ground, place obstacles
      {
        for (int x = 0; x < 100; x++)
        {
            if (m_rng.generate(0, 19) == 0)
            {            
                GameObject tile;
                tile.m_lane = y;
                tile.m_type = ObjectType(Tree + m_rng.generate(0, 3));//Obstacle;
                tile.m_x = x * 10;
                tile.m_y = y * 10;
                tile.m_z = heightForType(laneFlags[y]) + 6;// + 6;
                tile.m_w = 10;
                tile.m_h = 10;
                tile.m_d = 10; // height of terrain
                tile.m_dx = 0;
                tile.m_dy = 0;
                tile.m_color = 0xf08080;
                m_populationLevel.push_back(tile);
            }
        }
      }
      else if (laneFlags[y] == Water)
      {
         for (int x = 0; x < 100; x++)
         {
            if (m_rng.generate(0, 19) == 0)
            { 
              GameObject tile;
              tile.m_lane = y;
              tile.m_type = ObjectType(LilyPad + m_rng.generate(0, 1));//Landing
              tile.m_x = x * 10;
              tile.m_y = y * 10;
              tile.m_z = heightForType(laneFlags[y]) + 3;//+ 6;
              tile.m_w = 10;
              tile.m_h = 10;
              tile.m_d = 2; // height of terrain
              tile.m_dx = 0;
              tile.m_dy = 0;
              tile.m_color = 0xf08080;
              m_populationLevel.push_back(tile);
            }
         }
      }
    }

    for (int y = (lanes-1); y >= 0; y--)
    {
      if (laneFlags[y] != Ground && laneFlags[y] != Water) // if not Ground, place vehicles
      {
        ObjectType laneType = laneFlags[y];

        int width = m_rng.generate(1, 4);
        int speed = m_rng.generate(1, 9);
        int count = m_rng.generate(0, 15);
        int direction = m_rng.generate(0, 1);
        int x = m_rng.generate(0, 9);

        if (laneType == Rail)
        {
          width = 30;
          speed = 50;
          count = 1;
          x = m_rng.generate(0, 69);
        }

        int oldWidth = width;
        for (int i = 0; i < count; i++)
        {
            ObjectType vtype = Ground;
            float z = 0.0;

            z = heightForType(laneType) + 3;//+ 6;
            if (laneType == Rail)
              z += 2.0f;

            if (laneType == Road) {
                vtype = ObjectType(Bike + m_rng.generate(0, 3));
            } else if (laneType == Rail) {
                vtype = ObjectType(Train1 + m_rng.generate(0, 3));
            } else if (laneType == WaterLane) {
                vtype = ObjectType(Log + m_rng.generate(0, 2));
                //tile.m_type = (laneType == Water) ? FloatingPlatform : Vehicle;
            }

            switch (vtype)
            {
              case Bike:   width = 15; break;
              case Car:    width = 15; break;
              case Van:    width = 18; break;
              case Truck:  width = 50; break; 
              case Train1: width = 130; break;
              case Train2: width = 130; break;
              case Train3: width = 130; break;
              case Train4: width = 130; break;
              default:
                          width *= 10; break;
            }

           /* 
            if (vtype == Car)
            {
              GameObject tile;
              tile.m_lane = y;
              tile.m_type = vtype;
              tile.m_z = z;
              tile.m_x = x * 10;
              tile.m_y = y * 10;
              tile.m_w = width;
              tile.m_h = 10;
              tile.m_d = 15; // height for terrain
              tile.m_dx = (direction) ? speed : -speed;
              tile.m_dy = 0;
              tile.m_color = (0x80 << 16) | ((0x10*width)<<8) | (0x10*speed);
              m_carObjects.push_back(tile);
            }
            else
            */
            {
                GameObject tile;
                tile.m_lane = y;
                tile.m_type = vtype;
                tile.m_z = z;
                tile.m_x = x * 10;
                tile.m_y = y * 10;
                tile.m_w = width;
                tile.m_h = 10;
                tile.m_d = 15; // height for terrain
                tile.m_dx = (direction) ? speed : -speed;
                tile.m_dy = 0;
                tile.m_color = (0x80 << 16) | ((0x10*width)<<8) | (0x10*speed);
                m_movingObjects.push_back(tile);
            }

            x += width + 1;
            int avgWidthBetweenObjects = 100 / count;
            x += avgWidthBetweenObjects / 2;
            x += m_rng.generate(0, avgWidthBetweenObjects-1);
            if (x > 100)
            {
              break;
            }
            width = oldWidth;
        }
      }
    }

    GameObject tile;
    tile.m_lane = 5;
    tile.m_type = Player;
    tile.m_x = 732;
    tile.m_y = 52;
    tile.m_z = 0;
    tile.m_w = 6;
    tile.m_h = 6;
    tile.m_d = 20; // height for terrain
    tile.m_dx = 0;
    tile.m_dy = 0;//1;
    tile.m_color = 0xffffff;
    m_player.push_back(tile);
}


std::vector<GameObjectList*> GameSim::objectLists()
{
  std::vector<GameObjectList*> objLists;
  objLists.push_back(&m_backgroundLevel);
  objLists.push_back(&m_populationLevel);
  objLists.push_back(&m_movingObjects);
  objLists.push_back(&m_player);
  return objLists;
}


bool GameSim::checkGameOver()
{
    GameObject player = m_player.front();
    if (player.m_x < 0 || player.m_x > 1000)
    {
      return true;
    }

    // reset
    m_player.front().m_dx = 0;
    uint32_t playerLane = player.m_y / 10.0;
    for (GameObject& tile : m_movingObjects)
    {
      if (playerLane == tile.m_lane)
      if (objectsIntersect(m_time, player, tile) /* || objectsIntersect(tile, m_player.front()) */ )
      {
        if (tile.m_type >= Bike && tile.m_type <= Train4)
        {
          return true;
        }
        if (tile.m_type == Crocodile)
        {
          // TODO: more logic if near head of croc
          //if (tile.m_dx > 0 && tile.m_x + )
          return true;
        }
        if (tile.m_type == Log || tile.m_type == Boat)
        {
          // moves with the log or boat
          m_player.front().m_dx = tile.m_dx;
          return false;
        }
      }
    }

    /*
    if (player.m_y > 500)
    {
      m_won = true;
      return true;
    }
     */

    return false;
/*
  // Terrain
  Ground,
  Road,
  Rail,
  WaterLane,
  Water,

  // Obstacle
  Tree,
  Shrub,
  Building,
  Rocks,

  LilyPad,
  SteppingStone,

  // Pickup
  Coin,
  Food,
  Life,
  Bonus,
*/

}


void GameSim::update(float a_dt)
{
  if (m_gameOver)
    return;

  m_time += a_dt;

#ifndef TIME_BASED
  for (GameObject& tile : m_movingObjects)
  {
    tile.m_x += tile.m_dx * a_dt;
    tile.m_y += tile.m_dy * a_dt;
    if (tile.m_dx > 0) 
    {
      if (tile.m_x > 1000)
          tile.m_x -= 1000 + tile.m_w;
    }
    else
    {
      if ((tile.m_x + tile.m_w) < 0)
          tile.m_x += 1000 + tile.m_w;
    }
  }
#endif

  for (GameObject& tile : m_player)
  {
    tile.m_x += tile.m_dx * a_dt;
    tile.m_y += tile.m_dy * a_dt;
  }

  m_gameOver = checkGameOver();
}


