// Base class for all drawable objects
//

#include <stdlib.h>
#include <float.h>
#include <math.h>
#include "globals.h"
#include "project.h"
#include "object.h"
#include "matrix.h"
#include "vector.h"
#include "file.h"
#include "lc_application.h"

#define LC_KEY_SAVE_VERSION 1 // LeoCAD 0.73

// =============================================================================
// Static functions

// Returns in (A,B,C,D) the coefficientes of the plane with the three 
// succesive (in counterclockwise order) vertices p1,p2,p3. 
static void GetPolyCoeffs (float x1, float y1, float z1, float x2, float y2, float z2,
			   float x3, float y3, float z3, float *A, float *B, float *C, float *D)
{
  *A = ((y1-y2)*(z3-z2)) - ((z1-z2)*(y3-y2));
  *B = ((z1-z2)*(x3-x2)) - ((x1-x2)*(z3-z2)); 
  *C = ((x1-x2)*(y3-y2)) - ((y1-y2)*(x3-x2));
  *D = - ((*A)*x1) - ((*B)*y1) - ((*C)*z1);
}

// =============================================================================
// ClickLine structure

double LC_CLICKLINE::PointDistance (float *point)
{
  Vector op ((float)(point[0] - a1), (float)(point[1] - b1), (float)(point[2] - c1));
  Vector d ((float)a2, (float)b2, (float)c2);
  float len = d.Length ();
  d.Normalize ();
  float t = op.Dot (d);

  if (t > 0)
  {
    if (t >= len)
      t = 1;
    else
      t /= len;

    d *= (t*len);
    op -= d;
  }

  return op.Length ();
}

// =============================================================================
// Object class

Object::Object (LC_OBJECT_TYPE nType)
{
  //  m_nState = 0;
  //  m_strName[0] = '\0';

  m_pAnimationKeys = NULL;
  m_pInstructionKeys = NULL;

  m_nObjectType = nType;
  m_pKeyValues = NULL;

  //  m_pParent = NULL;
  //  m_pNext = NULL;
  //  m_pNextRender = NULL;
}

Object::~Object ()
{
  delete []m_pKeyValues;
  RemoveKeys ();
}

bool Object::FileLoad (File& file)
{
  lcuint8 version;

  file.ReadByte (&version, 1);
  if (version > LC_KEY_SAVE_VERSION)
    return false;

  lcuint16 time;
  float param[4];
  lcuint8 type;
  lcuint32 n;

  file.ReadLong (&n, 1);
  while (n--)
  {
    file.ReadShort (&time, 1);
    file.ReadFloat (param, 4);
    file.ReadByte (&type, 1);

    ChangeKey (time, false, true, param, type);
  }

  file.ReadLong (&n, 1);
  while (n--)
  {
    file.ReadShort (&time, 1);
    file.ReadFloat (param, 4);
    file.ReadByte (&type, 1);

    ChangeKey (time, true, true, param, type);
  }

  return true;
}

void Object::FileSave (File& file) const
{
  lcuint8 version = LC_KEY_SAVE_VERSION;
  LC_OBJECT_KEY *node;
  lcuint32 n;

  file.WriteByte (&version, 1);

  for (n = 0, node = m_pInstructionKeys; node; node = node->next)
    n++;
  file.WriteLong (&n, 1);

  for (node = m_pInstructionKeys; node; node = node->next)
  {
    file.WriteShort (&node->time, 1);
    file.WriteFloat (node->param, 4);
    file.WriteByte (&node->type, 1);
  }

  for (n = 0, node = m_pAnimationKeys; node; node = node->next)
    n++;
  file.WriteLong (&n, 1);

  for (node = m_pAnimationKeys; node; node = node->next)
  {
    file.WriteShort (&node->time, 1);
    file.WriteFloat (node->param, 4);
    file.WriteByte (&node->type, 1);
  }
}

// =============================================================================
// Key handling

static LC_OBJECT_KEY* AddNode (LC_OBJECT_KEY *node, unsigned short nTime, unsigned char nType)
{
  LC_OBJECT_KEY* newnode = (LC_OBJECT_KEY*)malloc (sizeof (LC_OBJECT_KEY));

  if (node)
  {
    newnode->next = node->next;
    node->next = newnode;
  }
  else
    newnode->next = NULL;

  newnode->type = nType;
  newnode->time = nTime;
  newnode->param[0] = newnode->param[1] = newnode->param[2] = newnode->param[3] = 0;

  return newnode;
}

void Object::RegisterKeys (float *values[], LC_OBJECT_KEY_INFO* info, int count)
{
  int i;

  m_pKeyValues = new float* [count];

  for (i = 0; i < count; i++)
    m_pKeyValues[i] = values[i];

  m_pAnimationKeys = AddNode (NULL, 1, 0);
  m_pInstructionKeys = AddNode (NULL, 1, 0);

  for (i = count-1; i > 0; i--)
  {
    AddNode (m_pAnimationKeys, 1, i);
    AddNode (m_pInstructionKeys, 1, i);
  }

  m_pKeyInfo = info;
  m_nKeyInfoCount = count;
}

void Object::RemoveKeys ()
{
  LC_OBJECT_KEY *node, *prev;

  for (node = m_pInstructionKeys; node;)
  {
    prev = node;
    node = node->next;
    free (prev);
  }

  for (node = m_pAnimationKeys; node;)
  {
    prev = node;
    node = node->next;
    free (prev);
  }
}

void Object::ChangeKey (unsigned short nTime, bool bAnimation, bool bAddKey, const float *param, unsigned char nKeyType)
{
  LC_OBJECT_KEY *node, *poskey = NULL, *newpos = NULL;
  if (bAnimation)
    node = m_pAnimationKeys;
  else
    node = m_pInstructionKeys;

  while (node)
  {
    if ((node->time <= nTime) &&
	(node->type == nKeyType))
      poskey = node;

    node = node->next;
  }

  if (bAddKey)
  {
    if (poskey)
    {
      if (poskey->time != nTime)
	newpos = AddNode(poskey, nTime, nKeyType);
    }
    else
      newpos = AddNode(poskey, nTime, nKeyType);
  }

  if (newpos == NULL)
    newpos = poskey;

  for (int i = 0; i < m_pKeyInfo[nKeyType].size; i++)
    newpos->param[i] = param[i];
}

void Object::CalculateKeys (unsigned short nTime, bool bAnimation)
{
//  LC_OBJECT_KEY *next[m_nKeyInfoCount], *prev[m_nKeyInfoCount], *node;
  LC_OBJECT_KEY *next[32], *prev[32], *node;
  int i, empty = m_nKeyInfoCount;

  for (i = 0; i < m_nKeyInfoCount; i++)
    next[i] = NULL;

  if (bAnimation)
    node = m_pAnimationKeys;
  else
    node = m_pInstructionKeys;

  // Get the previous and next keys for each variable
  while (node && empty)
  {
    if (node->time <= nTime)
    {
      prev[node->type] = node;
    }
    else
    {
      if (next[node->type] == NULL)
      {
        next[node->type] = node;
        empty--;
      }
    }

    node = node->next;
  }

  // TODO: USE KEY IN/OUT WEIGHTS
  for (i = 0; i < m_nKeyInfoCount; i++)
  {
    LC_OBJECT_KEY *n = next[i], *p = prev[i];

    if (bAnimation && (n != NULL) && (p->time != nTime))
    {
      float t = (float)(nTime - p->time)/(n->time - p->time);

      for (int j = 0; j < m_pKeyInfo[i].size; j++)
        m_pKeyValues[i][j] = p->param[j] + (n->param[j] - p->param[j])*t;
    }
    else
      for (int j = 0; j < m_pKeyInfo[i].size; j++)
        m_pKeyValues[i][j] = p->param[j];
  }
}

void Object::CalculateSingleKey (unsigned short nTime, bool bAnimation, int keytype, float *value) const
{
	LC_OBJECT_KEY *next = NULL, *prev = NULL, *node;

	if (bAnimation)
		node = m_pAnimationKeys;
	else
		node = m_pInstructionKeys;

	while (node)
	{
		if (node->type == keytype)
		{
			if (node->time <= nTime)
				prev = node;
			else
			{
				if (next == NULL)
				{
					next = node;
					break;
				}
			}
		}

		node = node->next;
	}

	// TODO: USE KEY IN/OUT WEIGHTS
	if (bAnimation && (next != NULL) && (prev->time != nTime))
	{
		float t = (float)(nTime - prev->time)/(next->time - prev->time);

		for (int j = 0; j < m_pKeyInfo[keytype].size; j++)
			value[j] = prev->param[j] + (next->param[j] - prev->param[j])*t;
	}
	else
		for (int j = 0; j < m_pKeyInfo[keytype].size; j++)
			value[j] = prev->param[j];
}

void Object::InsertTime (unsigned short start, bool animation, unsigned short time)
{
  LC_OBJECT_KEY *node, *prev = NULL;
  unsigned short last;
  bool end[32];
  int i;

  for (i = 0; i < m_nKeyInfoCount; i++)
    end[i] = false;

  if (animation)
  {
    node = m_pAnimationKeys;
    last = lcGetActiveProject()->GetTotalFrames ();
  }
  else
  {
    node = m_pInstructionKeys;
    last = 255;
  }

  for (; node != NULL; prev = node, node = node->next)
  {
    // skip everything before the start time
    if ((node->time < start) || (node->time == 1))
      continue;

    // there's already a key at the end, delete this one
    if (end[node->type])
    {
      prev->next = node->next;
      free (node);
      node = prev;

      continue;
    }

    node->time += time;
    if (node->time >= last)
    {
      node->time = last;
      end[node->type] = true;
    }
  }
}

void Object::RemoveTime (unsigned short start, bool animation, unsigned short time)
{
  LC_OBJECT_KEY *node, *prev = NULL;

  if (animation)
    node = m_pAnimationKeys;
  else
    node = m_pInstructionKeys;

  for (; node != NULL; prev = node, node = node->next)
  {
    // skip everything before the start time
    if ((node->time < start) || (node->time == 1))
      continue;

    if (node->time < (start + time))
    {
      // delete this key
      prev->next = node->next;
      free (node);
      node = prev;

      continue;
    }

    node->time -= time;
    if (node->time < 1)
      node->time = 1;
  }
}

// =============================================================================
// BoundingBox stuff

// Find the distance from the object to the beginning of the "click line".
double Object::BoundingBoxIntersectDist (LC_CLICKLINE* pLine) const
{
  double x, y, z;

  if (BoundingBoxIntersectionbyLine (pLine->a1, pLine->b1, pLine->c1, pLine->a2, pLine->b2, pLine->c2, &x, &y, &z))
    return (float)sqrt ((pLine->a1-x)*(pLine->a1-x)+(pLine->b1-y)*(pLine->b1-y)+(pLine->c1-z)*(pLine->c1-z));

  return DBL_MAX;
}

// Returns TRUE if the specified point is inside the bounding box of this object.
bool Object::BoundingBoxPointInside(double x, double y, double z) const
{
  int i = 0;
  while (i < 6 && ((m_fBoxPlanes[0][i]*x + m_fBoxPlanes[1][i]*y + 
		    m_fBoxPlanes[2][i]*z + m_fBoxPlanes[3][i]) <= 0.001)) 
    i++;
  return (i == 6);
}

// Returns TRUE if the line is intersecting any of the planes of the bounding
// box and if this point is also inside this bounding box.
bool Object::BoundingBoxIntersectionbyLine (double a1, double b1, double c1, double a2, double b2,
					    double c2, double *x, double *y, double *z) const
{
  double curr_t = DBL_MAX;
  double t, t1, t2;

  for (int i = 0; i < 6; i++)
  {
    t1 = (m_fBoxPlanes[0][i]*a1 + m_fBoxPlanes[1][i]*b1 + m_fBoxPlanes[2][i]*c1 + m_fBoxPlanes[3][i]);
    t2 = (m_fBoxPlanes[0][i]*a2 + m_fBoxPlanes[1][i]*b2 + m_fBoxPlanes[2][i]*c2);

    if (t1!=0 && t2!=0)
    {
      t = -( t1 / t2 );
      if (t>=0)
      {
	*x=a1+a2*t;
	*y=b1+b2*t;
	*z=c1+c2*t;

	if (BoundingBoxPointInside(*x,*y,*z))
	  if (t < curr_t)
	    curr_t = t;
      }
    }
  }

  if (curr_t != DBL_MAX)
  {
    *x=a1+a2*curr_t;
    *y=b1+b2*curr_t;
    *z=c1+c2*curr_t;
    return true;
  }
  else
    return false;
}

// For pieces
void Object::BoundingBoxCalculate (Matrix *mat, float Dimensions[6])
{
  //   BASE       TOP
  // 1------3  .------4  ^ X
  // |      |  |      |  |
  // |      |  |      |  |   Y
  // 0------.  2------5  .--->

  float pts[18] = {
    Dimensions[0], Dimensions[1], Dimensions[5],
    Dimensions[3], Dimensions[1], Dimensions[5],
    Dimensions[0], Dimensions[1], Dimensions[2],
    Dimensions[3], Dimensions[4], Dimensions[5],
    Dimensions[3], Dimensions[4], Dimensions[2],
    Dimensions[0], Dimensions[4], Dimensions[2] };

  mat->TransformPoints(pts, 6);

  GetPolyCoeffs (pts[3], pts[4], pts[5],  pts[0], pts[1], pts[2],  pts[6], pts[7], pts[8],
		 &m_fBoxPlanes[0][0], &m_fBoxPlanes[1][0], &m_fBoxPlanes[2][0], &m_fBoxPlanes[3][0]); //1,0,2
  GetPolyCoeffs (pts[9], pts[10],pts[11], pts[12],pts[13],pts[14], pts[15],pts[16],pts[17],
		 &m_fBoxPlanes[0][1], &m_fBoxPlanes[1][1], &m_fBoxPlanes[2][1], &m_fBoxPlanes[3][1]); //3,4,5
  GetPolyCoeffs (pts[15],pts[16],pts[17], pts[6], pts[7], pts[8],  pts[0], pts[1], pts[2],
		 &m_fBoxPlanes[0][2], &m_fBoxPlanes[1][2], &m_fBoxPlanes[2][2], &m_fBoxPlanes[3][2]); //5,2,0
  GetPolyCoeffs (pts[12],pts[13],pts[14], pts[9], pts[10],pts[11], pts[3], pts[4], pts[5],
		 &m_fBoxPlanes[0][3], &m_fBoxPlanes[1][3], &m_fBoxPlanes[2][3], &m_fBoxPlanes[3][3]); //4,3,1
  GetPolyCoeffs (pts[6], pts[7], pts[8],  pts[15],pts[16],pts[17], pts[12],pts[13],pts[14],
		 &m_fBoxPlanes[0][4], &m_fBoxPlanes[1][4], &m_fBoxPlanes[2][4], &m_fBoxPlanes[3][4]); //2,5,4
  GetPolyCoeffs (pts[0], pts[1], pts[2],  pts[3], pts[4], pts[5],  pts[9], pts[10],pts[11],
		 &m_fBoxPlanes[0][5], &m_fBoxPlanes[1][5], &m_fBoxPlanes[2][5], &m_fBoxPlanes[3][5]); //0,1,3
}

// Cameras
void Object::BoundingBoxCalculate (Matrix *mat)
{
  float normals[6][3] = {
    { 1,0,0 }, { 0,1,0 }, { 0,0,1 },
    { -1,0,0 }, { 0,-1,0 }, { 0,0,-1 } };
  float x,y,z,dist;

  if (IsCamera ())
    dist = 0.3f;
  else
    dist = 0.2f;

  mat->GetTranslation(&x,&y,&z);
  mat->SetTranslation(0,0,0);
  mat->TransformPoints(&normals[0][0], 6);

  for (int i = 0; i < 6; i++)
  {
    m_fBoxPlanes[0][i] = normals[i][0];
    m_fBoxPlanes[1][i] = normals[i][1];
    m_fBoxPlanes[2][i] = normals[i][2];

    float pt[3];
    pt[0] = dist*normals[i][0] + x;
    pt[1] = dist*normals[i][1] + y;
    pt[2] = dist*normals[i][2] + z;

    m_fBoxPlanes[3][i] = -(pt[0]*normals[i][0]+pt[1]*normals[i][1]+pt[2]*normals[i][2]);
  }
}

// Light
void Object::BoundingBoxCalculate (float pos[3])
{
  float pts[18] = {
     0.3f+pos[0],  0.3f+pos[1], -0.3f+pos[2],
    -0.3f+pos[0],  0.3f+pos[1], -0.3f+pos[2],
     0.3f+pos[0],  0.3f+pos[1],  0.3f+pos[2],
    -0.3f+pos[0], -0.3f+pos[1], -0.3f+pos[2],
    -0.3f+pos[0], -0.3f+pos[1],  0.3f+pos[2],
     0.3f+pos[0], -0.3f+pos[1],  0.3f+pos[2] };

  GetPolyCoeffs (pts[3], pts[4], pts[5],  pts[0], pts[1], pts[2],  pts[6], pts[7], pts[8],
		 &m_fBoxPlanes[0][0], &m_fBoxPlanes[1][0], &m_fBoxPlanes[2][0], &m_fBoxPlanes[3][0]); //1,0,2
  GetPolyCoeffs (pts[9], pts[10],pts[11], pts[12],pts[13],pts[14], pts[15],pts[16],pts[17],
		 &m_fBoxPlanes[0][1], &m_fBoxPlanes[1][1], &m_fBoxPlanes[2][1], &m_fBoxPlanes[3][1]); //3,4,5
  GetPolyCoeffs (pts[15],pts[16],pts[17], pts[6], pts[7], pts[8],  pts[0], pts[1], pts[2],
		 &m_fBoxPlanes[0][2], &m_fBoxPlanes[1][2], &m_fBoxPlanes[2][2], &m_fBoxPlanes[3][2]); //5,2,0
  GetPolyCoeffs (pts[12],pts[13],pts[14], pts[9], pts[10],pts[11], pts[3], pts[4], pts[5],
		 &m_fBoxPlanes[0][3], &m_fBoxPlanes[1][3], &m_fBoxPlanes[2][3], &m_fBoxPlanes[3][3]); //4,3,1
  GetPolyCoeffs (pts[6], pts[7], pts[8],  pts[15],pts[16],pts[17], pts[12],pts[13],pts[14],
		 &m_fBoxPlanes[0][4], &m_fBoxPlanes[1][4], &m_fBoxPlanes[2][4], &m_fBoxPlanes[3][4]); //2,5,4
  GetPolyCoeffs (pts[0], pts[1], pts[2],  pts[3], pts[4], pts[5],  pts[9], pts[10],pts[11],
		 &m_fBoxPlanes[0][5], &m_fBoxPlanes[1][5], &m_fBoxPlanes[2][5], &m_fBoxPlanes[3][5]); //0,1,3
}

