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

  Car.cpp

  2009 Shamus Young

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

  This creates the little two-triangle cars and moves them around the map.

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

#define DEAD_ZONE       200
#define STUCK_TIME      230
#define UPDATE_INTERVAL 50 //milliseconds
#define MOVEMENT_SPEED  0.61f
#define CAR_SIZE        3.0f

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

#include "glTypes.h"
#include "Car.h"
#include "Camera.h"
#include "Entity.h"
#include "GetTickCount.h"
#include "Macro.h"
#include "Math.h"
#include "Random.h"
#include "PixelCity.h"
#include "Texture.h"
#include "World.h"
#include "Visible.h"

static GLvector           direction[] = 
{
 {  0.0f, 0.0f, -1.0f },
 {  1.0f, 0.0f,  0.0f },
 {  0.0f, 0.0f,  1.0f },
 { -1.0f, 0.0f,  0.0f },
};

static int                dangles[] = { 0, 90, 180, 270};

static GLvector2          angles[360];
static bool               angles_done;
static unsigned char      carmap[WORLD_SIZE][WORLD_SIZE];
static CCar*              head;
static unsigned           next_update;
static int                count;

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

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

int CarCount ()
{

  return count;

}

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

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

void CarClear ()
{

  CCar*       c;

  for (c = head; c; c = c->m_next)
    c->Park ();
  memset (carmap, 0, sizeof (carmap));
  count = 0;

}

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

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

void CarRender ()
{

  CCar*       c;

  if (!angles_done) {
    for (int i = 0 ;i < 360; i++) {
      angles[i].x = cosf ((float)i * DEGREES_TO_RADIANS) * CAR_SIZE;
      angles[i].y = sinf ((float)i * DEGREES_TO_RADIANS) * CAR_SIZE;
    }
  }
  glDepthMask (false);
  glEnable (GL_BLEND);
  glDisable (GL_CULL_FACE);
  glBlendFunc (GL_ONE, GL_ONE);
  glBindTexture (GL_TEXTURE_2D, 0);
  glBindTexture(GL_TEXTURE_2D, TextureId (TEXTURE_HEADLIGHT));
  for (c = head; c; c = c->m_next)
    c->Render ();
  glDepthMask (true);

}


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

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

void CarUpdate ()
{

  CCar*       c;
  unsigned    now;

  if (!TextureReady () || !EntityReady ())
    return;
  now = GetTickCount ();
  if (next_update > now)
    return;
  next_update = now + UPDATE_INTERVAL;
  for (c = head; c; c = c->m_next)
    c->Update ();

}


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

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

CCar::CCar ()
{

  m_ready = false;
  m_next = head;
  head = this;
  count++;

}

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

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

bool CCar::TestPosition (int row, int col)
{

  //test the given position and see if it's already occupied
  if (carmap[row][col])
    return false;
  //now make sure that the lane is going the right direction
  if (WorldCell (row, col) != WorldCell (m_row, m_col))
    return false;
  return true;

}
 
/*-----------------------------------------------------------------------------

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

void CCar::Update (void)
{

  int       new_row, new_col;
  GLvector  old_pos;    
  GLvector  camera;

  //If the car isn't ready, place it on the map and get it moving
  camera = CameraPosition ();
  if (!m_ready) {
    //if the car isn't ready, we need to place it somewhere on the map
    m_row = DEAD_ZONE + RandomVal (WORLD_SIZE - DEAD_ZONE * 2);
    m_col = DEAD_ZONE + RandomVal (WORLD_SIZE - DEAD_ZONE * 2);
    //if there is already a car here, forget it.  
    if (carmap[m_row][m_col] > 0)
      return;
    //if this spot is not a road, forget it
    if (!(WorldCell (m_row, m_col) & CLAIM_ROAD)) 
      return;
    if (!Visible (glVector ((float)m_row, 0.0f, (float)m_col)))
      return;
    //good spot. place the car
    m_position = glVector ((float)m_row, 0.1f, (float)m_col);
    m_drive_position = m_position;
    m_ready = true;
    if (WorldCell (m_row, m_col) & MAP_ROAD_NORTH) 
      m_direction = NORTH;
    if (WorldCell (m_row, m_col) & MAP_ROAD_EAST) 
      m_direction = EAST;
    if (WorldCell (m_row, m_col) & MAP_ROAD_SOUTH) 
      m_direction = SOUTH;
    if (WorldCell (m_row, m_col) & MAP_ROAD_WEST) 
      m_direction = WEST;
    m_drive_angle = dangles[m_direction];
    m_max_speed = (float)(4 + RandomVal (6)) / 10.0f;
    m_speed = 0.0f;
    m_change = 3;
    m_stuck = 0;
    carmap[m_row][m_col]++;
  }
  //take the car off the map and move it
  carmap[m_row][m_col]--;
  old_pos = m_position;
  m_speed += m_max_speed * 0.05f;
  m_speed = MIN (m_speed, m_max_speed);
  m_position += direction[m_direction] * MOVEMENT_SPEED * m_speed;
  //If the car has moved out of view, there's no need to keep simulating it. 
  if (!Visible (glVector ((float)m_row, 0.0f, (float)m_col))) 
    m_ready = false;
  //if the car is far away, remove it.  We use manhattan units because buildings almost always
  //block views of cars on the diagonal.
  if (fabs (camera.x - m_position.x) + fabs (camera.z - m_position.z) > RenderFogDistance ())
    m_ready = false;
  //if the car gets too close to the edge of the map, take it out of play
  if (m_position.x < DEAD_ZONE || m_position.x > (WORLD_SIZE - DEAD_ZONE))
    m_ready = false;
  if (m_position.z < DEAD_ZONE || m_position.z > (WORLD_SIZE - DEAD_ZONE))
    m_ready = false;
  if (m_stuck >= STUCK_TIME)
    m_ready = false;
  if (!m_ready)
    return;
  //Check the new position and make sure its not in another car
  new_row = (int)m_position.x;
  new_col = (int)m_position.z;
  if (new_row != m_row || new_col != m_col) {
    //see if the new position places us on top of another car
    if (carmap[new_row][new_col]) {
      m_position = old_pos;
      m_speed = 0.0f;
      m_stuck++;
    } else {
      //look at the new position and decide if we're heading towards or away from the camera
      m_row = new_row;
      m_col = new_col;
      m_change--;
      m_stuck = 0;
      if (m_direction == NORTH)
        m_front = camera.z < m_position.z;
      else if (m_direction == SOUTH)
        m_front = camera.z > m_position.z;
      else if (m_direction == EAST)
        m_front = camera.x > m_position.x;
      else 
        m_front = camera.x < m_position.x;
    }
  }
  m_drive_position = (m_drive_position + m_position) / 2.0f;
  //place the car back on the map
  carmap[m_row][m_col]++;

}


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

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

void CCar::Render ()
{

  GLvector  pos;
  int       angle;
  int       turn;
  float     top;

  if (!m_ready)
    return;
  if (!Visible (m_drive_position))
    return;
  if (m_front) {
    glColor3f (1, 1, 0.8f);
    top = CAR_SIZE;
  } else {
    glColor3f (0.5, 0.2f, 0);
    top = 0.0f;
  }

  glBegin (GL_QUADS);

  angle = dangles[m_direction];
  pos = m_drive_position;// 
  angle = 360 - (int)MathAngle (m_position.x, m_position.z, pos.x, pos.z);
  angle %= 360;
  turn = (int)MathAngleDifference ((float)m_drive_angle, (float)angle);
  m_drive_angle += SIGN (turn);
  pos += glVector (0.5f, 0.0f, 0.5f);
  
  glTexCoord2f (0, 0);   
  glVertex3f (pos.x + angles[angle].x, -CAR_SIZE, pos.z + angles[angle].y);
  glTexCoord2f (1, 0);   
  glVertex3f (pos.x - angles[angle].x, -CAR_SIZE, pos.z - angles[angle].y);
  glTexCoord2f (1, 1);   
  glVertex3f (pos.x - angles[angle].x,  top, pos.z - angles[angle].y);
  glTexCoord2f (0, 1);   
  glVertex3f (pos.x + angles[angle].x,  top, pos.z +  angles[angle].y);
  
  glEnd ();

}
