/*==========================================================
  File:  entity.c
  Author:  _pragma

  Description:  Dynamic entity routines.
  ==========================================================*/


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

#include "interp.h"
#include "commands.h"
#include "cvar.h"
#include "game.h"
#include "a3dmath.h"
#include "bsp.h"
#include "ms3d.h"
#include "model.h"
#include "texture.h"
#include "console.h"
#include "entity.h"
#include "config.h"
#include "util.h"

/* This entire file needs to be redone.  The layout and the
   math is all horrible and wrong. */


ent_t *ge_linkedentities = 0;  // only entities in this list will be processed
                               // for movements, collisions, etc

ent_t *ge_entity_list = 0;     // list of all loaded entities

ent_t *ge_player;              // the main player entity
ent_t *ge_entity;              // global entity object (used for loading new entities)

int ge_entity_identifier = 0;  // unique indentifiers for entities

extern BSPmoveData_t gbsp_moveData;
extern BSPLevel_t *gbsp_map;

ent_t *E_createEntity(char *name)
{
  ent_t *entity;

  if(!(entity = malloc(sizeof(ent_t))))
  {
    CON_printf("warning: no memory for ent %s"name);
    return 0;
  }

  memset(entity0sizeof(ent_t));

  if(!(entity->name = strdup(name)))
  {
    CON_printf("warning: no memory for ent %s"name);
    free(entity);
    return 0;
  }

  entity->next = ge_entity_list;
  ge_entity_list = entity;

  entity->id = ++ge_entity_identifier;

  return entity;
}

ent_t *E_findEntityById(int id)
{
  ent_t *entity;

  for(entity = ge_entity_listentityentity = entity->next)
    if(entity->id == id)
      return entity;

  return 0;
}

void E_bindModelToEntity(m_model_t *modelent_t *entity)
{
  entity->model = model;
}

void E_destroyEntity(ent_t **entity)
{
  if(!*entity)
    return;

  CON_printf("Destroying entity %d: %s", (*entity)->id, (*entity)->name ? (*entity)->name : "[null]");

  if((*entity)->name)
    free((*entity)->name);
  free(*entity);
  *entity = 0;
}

void E_destroyAllEntities(void)
{
  ent_t *entity, *next;

  CON_printf("------- Destroying Entities --------");

  for(entity = ge_entity_listentityentity = next)
  {
    next = entity->next;
    E_destroyEntity(&entity);
  }
  CON_printf("------------------------------------");
}

void E_strafeEntity(ent_t *entityfloat speed)
{
  extern cvar_t *cvar_maxSpeed;
  float maxspeed = atof(cvar_maxSpeed->value);

  entity->acceleration[0] = entity->strafe[0] * speed;
  entity->acceleration[2] = entity->strafe[2] * speed;

  entity->velocity[0] += entity->acceleration[0];
  entity->velocity[2] += entity->acceleration[2];

  speed = sqrt(entity->velocity[0] * entity->velocity[0] +
               entity->velocity[2] * entity->velocity[2]);

  if(speed > maxspeed)
  {
    entity->velocity[0] *= maxspeed / speed;
    entity->velocity[2] *= maxspeed / speed;
  }

  entity->move[3] = true;
}

void E_moveEntity(ent_t *entityfloat speed)
{
  float v[3];
  extern cvar_t *cvar_maxSpeed;
  float maxspeed = atof(cvar_maxSpeed->value);

  M_vsubtract(ventity->viewentity->position);
  M_vnormalize(v);

  entity->acceleration[0] = v[0] * speed;
  entity->acceleration[2] = v[2] * speed;

  entity->velocity[0] += entity->acceleration[0];
  entity->velocity[2] += entity->acceleration[2];

  speed = sqrt(entity->velocity[0] * entity->velocity[0] +
               entity->velocity[2] * entity->velocity[2]);

  if(speed > maxspeed)
  {
    entity->velocity[0] *= maxspeed / speed;
    entity->velocity[2] *= maxspeed / speed;
  }

  entity->move[3] = true;
}

void E_moveEntityTo(ent_t *entityfloat *position)
{
  entity->view[0] += position[0] - entity->position[0];
  entity->view[1] += position[1] - entity->position[1];
  entity->view[2] += position[2] - entity->position[2];

  entity->position[0] = position[0];
  entity->position[1] = position[1];
  entity->position[2] = position[2];
}

void E_rotateEntity(ent_t *entityfloat anglefloat xfloat yfloat z)
{
  float view[3], r[3];
  float cosTheta = cos(angle), sinTheta = sin(angle);

  M_vsubtract(viewentity->viewentity->position);

  // Find the new x position for the new rotated point
  r[0]  = (cosTheta + (1 - cosTheta) * x * x)       * view[0];
  r[0] += ((1 - cosTheta) * x * y - z * sinTheta)   * view[1];
  r[0] += ((1 - cosTheta) * x * z + y * sinTheta)   * view[2];

  // Find the new y position for the new rotated point
  r[1]  = ((1 - cosTheta) * x * y + z * sinTheta)   * view[0];
  r[1] += (cosTheta + (1 - cosTheta) * y * y)       * view[1];
  r[1] += ((1 - cosTheta) * y * z - x * sinTheta)   * view[2];

  // Find the new z position for the new rotated point
  r[2]  = ((1 - cosTheta) * x * z - y * sinTheta)   * view[0];
  r[2] += ((1 - cosTheta) * y * z + x * sinTheta)   * view[1];
  r[2] += (cosTheta + (1 - cosTheta) * z * z)       * view[2];

  r[0] += entity->position[0];
  r[1] += entity->position[1];
  r[2] += entity->position[2];

  entity->view[0] = r[0];
  entity->view[1] = r[1];
  entity->view[2] = r[2];
}

void E_lookHoriz(ent_t *entityfloat speed)
{
  E_rotateEntity(entityspeed010);
}

void E_lookVert(ent_t *entityfloat speed)
{
  float r[3], a[3];  // axis vector

  M_vsubtract(rentity->viewentity->position);
  M_vcross(arentity->up);
  M_vnormalize(a);

  /* rotate around the ZX axis */
  E_rotateEntity(entityspeeda[0], a[1], a[2]);
}

void E_updateEntity(ent_t *entityint msec)
{
  float v[3], r[3], time;
  extern int game_state;

  if(game_state != GAME_STATE_PLAYING)
    return;

  M_vsubtract(ventity->viewentity->position);
  M_vcross(rventity->up);
  M_vnormalize(r);

  entity->strafe[0] = r[0];
  entity->strafe[1] = r[1];
  entity->strafe[2] = r[2];

  time = (float)msec * 0.001f;

  E_updateEntityPhysics(entitytime);

  if(entity->move[3])
    E_moveEntityCollisionBSP(entitygbsp_map);
}

void E_updateEntityPhysics(ent_t *entityfloat time)
{
  float v[3];
  int i;
  extern cvar_t *cvar_noclip, *cvar_friction, *cvar_gravity;
  float friction = atof(cvar_friction->value),
        gravity  = atof(cvar_gravity->value);
  int noclip = atoi(cvar_noclip->value);

 /* Gravity.
  * Checks if we 'fall' down without hitting anything.  If so,
  * we increase the Y axis velocity by the gravity constant * !noclip.
  * Otherwise, if we 'fall' down and strike a plane, we store that plane
  * as our ground plane for future calculations.
  */


  if(!noclip && gbsp_map)
  {
    v[0] = entity->position[0];
    v[1] = entity->position[1] - entity->boundingSphereRadius;
    v[2] = entity->position[2];

    BSP_rayTraceSphere(gbsp_mapentity->positionventity->boundingSphereRadius);

    if(gbsp_moveData.fraction > 0)
    {
      entity->velocity[1] += gravity;
      entity->move[3] = true;
      entity->groundPlaneIndex = -1;
    }
    else
    {
      if(entity->action_flags & PAF_JUMPING)
        entity->action_flags &= ~PAF_JUMPING;

      entity->groundPlaneIndex = gbsp_moveData.collisionPlaneIndex;
    }
  }

 /* Friction.
  * If no clip is enabled, we force friction on all axises whether
  * entity is on the ground or not (free flying noclip).  Otherwise
  * we apply friction on the X and Z axises if the entity is on the
  * ground.
  */


  if(noclip)
  {
    // noclip is enabled, force friction on all axises
    for(i = 0i < 3i++)
      if(entity->velocity[i])
      {
        entity->velocity[i] = friction * entity->velocity[i];
        entity->move[3] = true;
      }
  }
  else
  {
    // noclip is disabled, normal action

    if(entity->groundPlaneIndex >= 0)
    {
      // entity is on the ground
      for(i = 0i < 3i++)
      {
        // skip Y axis (up/down) and velocities without force
        if(i == 1 || entity->velocity[i] == 0
          continue;

        entity->velocity[i] = friction * entity->velocity[i];

        if(fabs(entity->velocity[i]) < 1)
          entity->velocity[i] = 0;
        else
          entity->move[3] = true;
      }  
    }
    else
    {
      // entity is in the air - todo: air friction?
      // currently no action
    }
  }

 /*
  * Update the move vector for the move we wish to make.
  */


  entity->move[0] = entity->velocity[0] * time;
  entity->move[1] = entity->velocity[1] * time;
  entity->move[2] = entity->velocity[2] * time;
}

void E_positionEntity(ent_t *entityfloat pxfloat pyfloat pz
                                     float vxfloat vyfloat vz
                                     float uxfloat uyfloat uz)
{
  entity->position[0] = px;
  entity->position[1] = py;
  entity->position[2] = pz;

  entity->view[0] = vx;
  entity->view[1] = vy;
  entity->view[2] = vz;

  entity->up[0] = ux;
  entity->up[1] = uy;
  entity->up[2] = uz;
}

void E_moveEntityCollisionBSP(ent_t *entityBSPLevel_t *bsplevel)
{
  extern cvar_t *cvar_noclip;
  float v[3];

  if((atoi(cvar_noclip->value) && entity == ge_player))
  {
    v[0] = entity->position[0] + entity->move[0];
    v[1] = entity->position[1] + entity->move[1];
    v[2] = entity->position[2] + entity->move[2];
    E_moveEntityTo(entityv);
  }
  else
    E_moveEntityNewPositionBSP(entitybsplevel);

  entity->move[3] = false;
}

void E_moveEntityNewPositionBSP(ent_t *entityBSPLevel_t *bsplevel)
{
  float v[3], dot;

  if(!bsplevel)
    return;

  // set up end point of our move attempt
  v[0] = entity->position[0] + entity->move[0];
  v[1] = entity->position[1] + entity->move[1];
  v[2] = entity->position[2] + entity->move[2];

  // trace ray to end point and fill gbsp_moveData structure with results
  BSP_rayTraceSphere(bsplevelentity->positionventity->boundingSphereRadius);

  if(gbsp_moveData.fraction > 0)
  {
    E_moveEntityTo(entitygbsp_moveData.endPoint);
    return;
  }

  while(gbsp_moveData.fraction == 0)
  {
    M_vnormalize(gbsp_moveData.collisionPlaneNormal);
    dot = M_vdot(gbsp_moveData.collisionPlaneNormalentity->move);

    v[0] += gbsp_moveData.collisionPlaneNormal[0] * -dot;
    v[1] += gbsp_moveData.collisionPlaneNormal[1] * -dot;
    v[2] += gbsp_moveData.collisionPlaneNormal[2] * -dot;

    BSP_rayTraceSphere(bsplevelentity->positionventity->boundingSphereRadius);

    if(gbsp_moveData.fraction == 1)
      E_moveEntityTo(entityv);
  }


 //=======================================================================// 
 // Player specifics.                                                     //
 // The following functions are specific to the main local player entity. //
 //=======================================================================//

int E_initPlayerEntity(void)
{
  if(!(ge_player = E_createEntity("Player")))
    return false;

  ge_player->health = 100;
  ge_player->boundingSphereRadius = 20;

  ge_player->model = M_loadModelMS3D("game/player.ms3d");

  E_positionEntity(ge_player050050020010); 

  return true;
}

void E_updatePlayerEntity(ent_t *entityint msec)
{
  float v[3], r[3], timespeed;
  extern int game_stategt_curtime;
  extern cvar_t *cvar_moveForward,
                *cvar_moveBack,
                *cvar_moveLeft,
                *cvar_moveRight,
                *cvar_moveDown,
                *cvar_moveUp,
                *cvar_moveSpeed,
                *cvar_lookLeft,
                *cvar_lookRight,
                *cvar_yawSpeed,
                *cvar_lookUp,
                *cvar_lookDown,
                *cvar_pitchSpeed,
                *cvar_noclip,
                *cvar_thirdperson;
  int noclip = atoi(cvar_noclip->value),
      thirdperson = atoi(cvar_thirdperson->value) ? -1 : 1;
  float is_on_ground;

  if(game_state != GAME_STATE_PLAYING)
    return;

  M_vsubtract(ventity->viewentity->position);
  M_vcross(rventity->up);
  M_vnormalize(r);

  entity->strafe[0] = r[0];
  entity->strafe[1] = r[1];
  entity->strafe[2] = r[2];

  time = (float)msec * 0.001f;
  speed = atof(cvar_moveSpeed->value);
  is_on_ground = entity->groundPlaneIndex >= 0;

  entity->acceleration[0] = 0;
  entity->acceleration[1] = 0;
  entity->acceleration[2] = 0;

  if(atoi(cvar_moveUp->value))
  {
    if(noclip)
    {
      // noclip is enabled, allow free flying
      entity->velocity[1] += speed;
      entity->move[3] = true;
    }
    else
    {
      // noclip is disabled, normal world action
      if(!(entity->action_flags & PAF_JUMPING) && is_on_ground && 
         gt_curtime - entity->jump_time > 1000)
      {
        // entity is not jumping and entity is on ground 
        // and at least 1 sec has elapsed since last jump
        entity->velocity[1] = sqrt(entity->velocity[0] * entity->velocity[0] +
                                   entity->velocity[2] * entity->velocity[2]);
        entity->action_flags |= PAF_JUMPING;
        entity->groundPlaneIndex = -1;
        entity->jump_time = gt_curtime;
        entity->move[3] = true;
      }
    }
  }

  if(atoi(cvar_moveDown->value))
  {
    if(noclip)
    {
      // noclip is enabled, allow free flying
      entity->velocity[1] -= speed;
      entity->move[3] = true;
    }
    else
    {
      // currently does nothing, todo fixme: crouching
    }
  }

  // if noclip is enabled, pretend the entity is always on the
  // ground to allow momentum
  if(noclip)
    is_on_ground = 1;
  else
  // if the entity is not on the ground and noclip is disabled
  // allow it to move through air, but only at 5% of the speed
  if(!is_on_ground)
    is_on_ground = 0.05f

  if(atoi(cvar_moveForward->value)) E_moveEntity(entityspeed * is_on_ground * thirdperson);
  if(atoi(cvar_moveBack->value))    E_moveEntity(entity, -speed * is_on_ground * thirdperson);
  if(atoi(cvar_moveLeft->value))    E_strafeEntity(entity, -speed * is_on_ground * thirdperson);
  if(atoi(cvar_moveRight->value))   E_strafeEntity(entityspeed * is_on_ground * thirdperson);

  if(atoi(cvar_lookRight->value))   E_lookHoriz(entity, -atof(cvar_yawSpeed->value));
  if(atoi(cvar_lookLeft->value))    E_lookHoriz(entityatof(cvar_yawSpeed->value));
  if(atoi(cvar_lookUp->value))      E_lookVert(entityatof(cvar_pitchSpeed->value));
  if(atoi(cvar_lookDown->value))    E_lookVert(entity, -atof(cvar_pitchSpeed->value));

  E_updateEntityPhysics(entitytime);

  if(entity->move[3])
    E_moveEntityCollisionBSP(entitygbsp_map);
}

 //=======================================================================// 
 // Dynamic Entities                                                      //
 // The following functions are specific to the entity editor and whatnot //
 //=======================================================================//

COMMAND(CMD_listEntities)
{
  ent_t *entity;
  int i = 0;

  for(entity = ge_entity_listentityentity = entity->next)
  {
    CON_printf("^14;%-30s ^3[^14;%2d^3] : (^8;%5.3f^3, ^8;%5.3f^3, ^8;%5.3f^3)"entity->nameentity->id
                               entity->position[0], entity->position[1], entity->position[2]);
    CON_printf("Model^3: ^14;%s"entity->model ? entity->model->name ? entity->model->name : "^3[^14no name model^3]" : "^3[^14no model^3]"); 
    i++;
  }
  CON_printf("%d entities"i);
}

ent_t *E_loadEntity(char *name)
{
  ent_t *entity;
  FILE *fp;

  // First we make sure the entity file exists... (hackish)
  if(!(fp = fopen(name"r")))
  {
    CON_printf("loadEntity: Could not open '%s'"name);
    return 0;
  }
  fclose(fp);

  // Now that we know it exists, we can create the
  // entity object.
  if(!(entity = E_createEntity(name)))
  {
    CON_printf("Out of memory for entity '%s'"name);
    return 0;
  }

  // Set the current working entity to the newly created object.
  ge_entity = entity;

  // Read in and execute commands related to this entity
  // from the entity file.
  config_read(name);

  return entity;
}

COMMAND(CMD_loadEntity)
{
  ent_t *entity;

  if(!(entity = E_loadEntity(arguments)))
    CON_printf("^9Failed^15 to load ^3'^14;%s^3'"arguments);
  else
  {
    CON_printf("^3'^14;%s^3' ^15loaded as ^14;%d"argumentsentity->id);
    CON_printf("Selected ^3'^14;%s^3' at ^3(^8;%.3f^3, ^8;%.3f^3, ^8;%.3f^3)"
                entity->nameentity->position[0], entity->position[1], entity->position[2]);
  }
}

// fixme: model should be a clone
COMMAND(CMD_setEntityModel)
{
  m_model_t *model;

  if(!(model = M_findModelByName(arguments)))
    if(!(model = M_loadModelMS3D(arguments)))
    {
      CON_printf("setEntityModel: could not load ^3'^14;%s^3'"arguments);
      return;
    }

  ge_entity->model = model;
}

COMMAND(CMD_selectEntity)
{
  ent_t *entity;

  if(!(entity = E_findEntityById(atoi(arguments))))
  {
    CON_printf("selectEntity^3:^15 could not find ^14;%d"atoi(arguments));
    return;
  }

  ge_entity = entity;
  CON_printf("Selected ^3'^14%s^3'^15 at ^3(^14;%.3f^3,^14 %.3f^3,^14 %.3f^3)"
    ge_entity->namege_entity->position[0], ge_entity->position[1], ge_entity->position[2]);
}



COMMAND(CMD_positionEntity)
{
  char bufx[100], bufy[100], bufz[100], buf[100];
  float xyz;

  I_halfChop(argumentsbufxbuf);
  I_halfChop(bufbufybufz);

  if(!*bufx || !*bufy || !*bufz)
  {
    CON_printf("Usage:  position x y z");
    return;
  }

  x = atof(bufx);
  y = atof(bufy);
  z = atof(bufz);

  ge_entity->velocity[0] = 0;
  ge_entity->velocity[1] = 0;
  ge_entity->velocity[2] = 0;

  E_positionEntity(ge_entityxyz,
             x,
             y,
             z + 60,
             010);
}