/*
   CMSC 427 - Term Project Candidate
   ---------------------------------

   Beth Weinstein - bweinste@wam.umd.edu
   Ben Stewart - stewartb@wam.umd.edu
   Nawfel Tricha - Topaz@endorproductions.com

   Minimal OpenGL project using Microsoft Visual C++.
   This project is a premilinary candidate for the term 
   project required by CMSC 427.

   ABSTRACT:
      In this project, we will develop several fast 
      rendering and collision routines which would 
      require C/C++ speed and accuracy and then 
      implement a VisualJ++ interface.

   MODULE:
      General Edict/Entity module.
*/

#include "Global.h"

#define SHORT_RANGE     6
#define CRITICAL_UPDATE_TIME     800

/*
   NT: 
   Allocate new edict from list, initialize and return to caller:      
*/
edict_t *EdictSpawn()
{
   int x = 0;
   while (g_edicts[x].taken && x < G_MAX_EDICTS)
      x++;
   if (x == G_MAX_EDICTS)
      {
      EngineError("Edict Allocation Failure: Edict List is full, please free more edicts.\n");
      return NULL;
      }
   memset (&g_edicts[x], 0, sizeof(g_edicts[0]));   
   g_edicts[x].taken = true;
   if (x >= eng.num_edicts)
      eng.num_edicts++;
   return &g_edicts[x];
}

/*
   NT: 
   Free edict from list and return ent = null:
*/
void EdictFree(edict_t *ent)
{
   int x = 0;
   while (&g_edicts[x] != ent)
      x++;
   g_edicts[x].taken = false;
}

/*
   NT: 
   Allocate new edict from list, initialize and return to caller:      
*/
edict_t *tEdictSpawn()
{
   int x = 0;
   while (g_tedicts[x].taken && x < G_MAX_EDICTS)
      x++;
   if (x == G_MAX_EDICTS)
      {
      EngineError("Edict Allocation Failure: Edict List is full, please free more edicts.\n");
      return NULL;
      }
   memset (&g_tedicts[x], 0, sizeof(g_tedicts[0]));
   g_tedicts[x].taken = true;
   if (x >= eng.num_tedicts)
      eng.num_tedicts++;
   return &g_tedicts[x];
}

/*
   NT: 
   Free edict from list and return ent = null:
*/
void tEdictFree(edict_t *ent)
{
   int x = 0;
   while (&g_tedicts[x] != ent)
      x++;
   g_tedicts[x].taken = false;
}

/*
   NT: 
   Run the critical physics frame on all entities:
*/
void CriticalFrame()
{
   edict_t *ent = &g_edicts[0];
   DWORD next_critical_update = 0;

   vec3_t direction_vector;
   for (; ent < &g_edicts[eng.num_edicts]; ent++)
      {
      if (!ent->taken)
         continue;
      if (ent->die && (ent->die < eng.time))
         {
         EdictFree(ent);
         continue;
         }
      if (ent->state & ENT_ST_PHYS)
         {
         ent->velocity[1] -= ent->mass;
         VectorAdd(ent->origin, ent->velocity, ent->origin);
         VectorAdd(ent->angle, ent->avelocity, ent->angle);
         }
      // Move entities to their target destination:
      if (ent->state & ENT_ST_MOVING)
         {
         VectorSubtract(ent->destination, ent->origin, direction_vector);
         // See if the length is not too big:
         if (VectorNormalize(direction_vector) > SHORT_RANGE)
            {
            if (next_critical_update < eng.time)
               {               
               ent->velocity[0] = direction_vector[0] * ent->speed;
               ent->velocity[1] = direction_vector[1] * ent->speed;
               ent->velocity[2] = direction_vector[2] * ent->speed;
               VectorAngles(ent->velocity, ent->angle);
               }
            }
         else
            {
            ent->state &= ~ENT_ST_MOVING;
            VectorClear(ent->velocity);
            if (ent->reached)
               ent->reached(ent);
            }
         }
      }
   for (ent = &g_tedicts[0]; ent < &g_tedicts[eng.num_tedicts]; ent++)
      {
      if (!ent->taken)
         continue;
      if (ent->die && (ent->die < eng.time))
         {
         tEdictFree(ent);
         continue;
         }
      if (ent->state & ENT_ST_PHYS)
         {
         ent->velocity[1] -= ent->mass;
         VectorAdd(ent->origin, ent->velocity, ent->origin);
         VectorAdd(ent->angle, ent->avelocity, ent->angle);
         }
      }     

   if (next_critical_update < eng.time)
      next_critical_update = eng.time + CRITICAL_UPDATE_TIME;
}

/*
   NT: 
   Run the think function frame on all entities:
   Think functions are functions that are called every ent->next_think time by the 
   entity itself. Think functions are very helpful because they are little programs
   that apply independently to every single entity. Think functions give life to 
   an entity. 
*/
void RunFrame()
{
   edict_t *ent = &g_edicts[0];
   for (; ent < &g_edicts[eng.num_edicts]; ent++)
      {
      if (ent->taken && ent->next_think)
         {
         if (ent->next_think < eng.time)
            {
            ent->next_think = 0;
            ent->think(ent);
            }
         }
      }  
}

/*
   NT: 
   Draw all entities that are visible (Triangles only for now):
*/
void DrawEdicts()
{   
   edict_t *ent = &g_edicts[0];

   //NT: Reset texture cache:
   last_texture = NOT_FOUND;
   // NT: Blending/Depth hack for now:
   glDisable(GL_DEPTH_TEST);

   for (; ent < &g_edicts[eng.num_edicts]; ent++) 
      {
      // NT: Only visible entities:
      if (ent->taken && (ent->state & ENT_ST_VISIBLE) && ent->render)
         {
         glPushMatrix();
         glTranslatef(ent->origin[0], ent->origin[1], ent->origin[2]);
         glRotatef(ent->angle[YAW],   0, 1, 0); 
         glRotatef(ent->angle[PITCH], 1, 0, 0); 
         glRotatef(ent->angle[ROLL],  0, 0, 1);
         glScalef(ent->scale, ent->scale, ent->scale);
         glColor4fv(ent->rgba);
         ent->render(ent);
         glPopMatrix();
         }
      }
}

/*
   NT: 
   Draw all entities that are visible (Triangles only for now):
*/
void DrawtEdicts()
{   
   edict_t *ent = &g_edicts[0];
   //NT: Reset texture cache:
   last_texture = NOT_FOUND;
    // NT: Transparency code:
   glDepthMask(false);
   glEnable(GL_DEPTH_TEST);
   for (ent = &g_tedicts[0]; ent < &g_tedicts[eng.num_tedicts]; ent++) 
      {
      // NT: Only visible entities:
      if (ent->taken && (ent->state & ENT_ST_VISIBLE) && ent->render)
         {
         glPushMatrix();
         glTranslatef(ent->origin[0], ent->origin[1], ent->origin[2]);
         // Orthogonal projection (Billboarding):
         if (ent->state & ENT_ST_ORTHO)
            {
            glRotatef(cam.angle[YAW],   0, 1, 0);
            glRotatef(cam.angle[PITCH],   1, 0, 0);
            glRotatef(ent->angle[ROLL],  0, 0, 1);
            }
         else
            {
            glRotatef(ent->angle[PITCH], 1, 0, 0); 
            glRotatef(ent->angle[YAW],   0, 1, 0);
            glRotatef(ent->angle[ROLL],  0, 0, 1);
            }
         glScalef(ent->scale, ent->scale, ent->scale);
         glColor4fv(ent->rgba);
         ent->render(ent);
         glPopMatrix();
         }
      }
   glDepthMask(true);
   glDisable(GL_DEPTH_TEST);
}

/*
   NT:
   RunEdict - Run entity towards target
*/

void RunEdict(edict_t *ent)
{
   vec3_t direction_vector;
   ent->state |= ENT_ST_MOVING;
   VectorSubtract(ent->destination, ent->origin, direction_vector);
   // See if the length is not too big:
   if (VectorNormalize(direction_vector) > SHORT_RANGE)
      {
      ent->velocity[0] = direction_vector[0] * ent->speed;
      ent->velocity[1] = direction_vector[1] * ent->speed;
      ent->velocity[2] = direction_vector[2] * ent->speed;
      VectorAngles(ent->velocity, ent->angle);
      }
   else
      ent->state &= ~ENT_ST_MOVING;
}