//  BlockyFroggy
//  Copyright © 2017 John Ryland.
//  All rights reserved.
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include "GameSim.h"


#define TIME_BASED 1


/*

To make simulation deterministic if required, floats should be avoided/removed.
To enforce this, including a header like this first would be helpful to ensure they aren't used.

NoFloat.h
---------
#include "acceptable headers.h"
#define float     errorType
#define double    errorType

*/


using namespace GameSim;


namespace details {


  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
  }


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


  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
  }


}


GameLogic::GameLogic()
{
  m_objLists.push_back(&m_backgroundLevel);
  m_objLists.push_back(&m_populationLevel);
  m_objLists.push_back(&m_movingObjects);
  m_objLists.push_back(&m_player);
  reset();
}


GameLogic::~GameLogic()
{
}


void GameLogic::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;
}


void GameLogic::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 (details::objectsIntersect(m_time, m_player.front(), tile))
        {
            onWater = true;
            break;
        }
    }
  }
  for (GameObject& tile : m_populationLevel)
  {
    if (details::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 GameLogic::getLaneHeight(int y)
{
  int lane = y / 10;
  if (lane < 0 || lane >= m_lanes)
    return 0;
  ObjectType t = m_laneFlags[lane];
  return heightForType(t);
}


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


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

      if (y && m_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++)
      {
        m_laneFlags[y] = laneFlag;
        y++;
        if (y >= m_lanes)
        {
          break;
        }
      }
      y--;
    }

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

    //for (int x = 0; x < 100; x++)
    {
       for (int y = (m_lanes-1); y >= 0; )//y--)
       {
          GameObject tile;
          tile.m_lane = y;
          tile.m_type = m_laneFlags[y];
          //int y1 = y;
          int laneWidth = 0;
          while (y>=0 && m_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 + (m_laneFlags[y]*5);
          m_backgroundLevel.push_back(tile);
       }
    }

    for (int y = (m_lanes-1); y >= 0; y--)
    {
      if (m_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(m_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 (m_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(m_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 = (m_lanes-1); y >= 0; y--)
    {
      if (m_laneFlags[y] != Ground && m_laneFlags[y] != Water) // if not Ground, place vehicles
      {
        ObjectType laneType = m_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);
}


const std::vector<GameObjectList*>& GameLogic::objectLists()
{
  return m_objLists;
}


bool GameLogic::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 (details::objectsIntersect(m_time, player, tile) /* || details::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) //  || tile.m_type == LilyPad || tile.m_type == SteppingStone)
        {
          // moves with the log or boat
          m_player.front().m_dx = tile.m_dx;
          return false;
        }
      }
    }

    // Check if on water
    bool onWater = false;
    for (GameObject& tile : m_backgroundLevel)
    {
      //if (playerLane == tile.m_lane)
      {
        if (tile.m_type == Water || tile.m_type == WaterLane)
        {
          if (details::objectsIntersect(m_time, player, tile))
          {
            onWater = true;
            break;
          }
        }
      }
    }
  
    // Check if on lilypad or similar
    bool safeOnWater = false;
    for (GameObject& tile : m_populationLevel)
    {
      //if (playerLane == tile.m_lane)
      {
        if (tile.m_type == LilyPad || tile.m_type == SteppingStone)
        {
          if (details::objectsIntersect(m_time, player, tile))
          {
            safeOnWater = true;
            break;
          }
        }
      }
    }
  
    if (onWater && !safeOnWater)
    {
      return true;
    }
  
    /*
    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 GameLogic::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();
}


