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

  Description:  Loads and maintains models.
  ==========================================================*/


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

#include "ms3d.h"
#include "model.h"
#include "texture.h"
#include "console.h"
#include "util.h"

m_model_t *gm_model_list = 0;  // list of loaded models
long gm_model_identifier = 0;  // unique model identification numbers

void M_destroyModels(void)
{
  m_model_t *model, *next;

  CON_printf("-------- Destroying Models ---------");

  for(model = gm_model_list; model; model = next)
  {
    next = model->next;
    M_destroyModel(model);
  }

  CON_printf("------------------------------------");
}

void M_destroyModel(m_model_t *model)
{
  int i;

  if(model)
  {
    if(model->name)
    {
      CON_printf("Destroying model %s", model->name);
      free(model->name);
    }
    if(model->vertices)
      free(model->vertices);
    if(model->triangles)
      free(model->triangles);
    if(model->groups)
    {
      for(i = 0; i < model->numGroups; i++)
        if(model->groups[i].triangleIndices)
          free(model->groups[i].triangleIndices);
      free(model->groups);
    }
    if(model->materials)
      free(model->materials);
    free(model);
    model = 0;
  }
}

m_model_t *M_findModelByName(char *name)
{
  m_model_t *model;

  for(model = gm_model_list; model; model = model->next)
    if(!strcmp(name, model->name))
      return model;

  return 0;
}

m_model_t *M_findModelById(int id)
{
  m_model_t *model;

  for(model = gm_model_list; model; model = model->next)
    if(model->id == id)
      return model;

  return 0;
}

m_model_t *M_loadModelMS3D(char *filename)
{
  ms3d_model_t *ms3d_model;
  m_model_t *model;

  // determine if the model has been loaded already
  if((model = M_findModelByName(filename)))
  {
    CON_printf("Model \'%s\' already loaded as %d", filename, model->id);
    return model;
  }

  if(chdir("data/models") == -1)
  {
    CON_printf("Model data directory does not exist, aborting.");
    return 0;
  }

  if(!(ms3d_model = MS3D_loadModel(filename)))
  {
    CON_printf("Failed to load MS3D model");
    chdir("../..");
    return 0;
  }

  if(!(model = M_convertMS3D(ms3d_model)))
  {
    CON_printf("Failed to conver model");
    MS3D_destroyModel(ms3d_model);
    chdir("../..");
    return 0;
  }
  chdir("../..");

  MS3D_destroyModel(ms3d_model);  

  if(!(model->name = strdup(filename)))
  {
    M_destroyModel(model);
    CON_printf("Out of memory for model name");
    return 0;
  }

  // assigned model an unique identifier 
  // and add to our list of loaded models

  model->id = ++gm_model_identifier;

  model->next = gm_model_list;
  gm_model_list = model;

  return model;
}

m_model_t *M_convertMS3D(ms3d_model_t *ms3d_model)
{
  m_model_t *model;
  int i,j,k;
  char filename[256];

  if(!(model = malloc(sizeof(m_model_t))))
  {
    CON_printf("M_convertMS3D: failed to alloc memory");
    perror("M_convertMS3D malloc");
    return 0;
  }

  memset(model, 0, sizeof(m_model_t));

  model->numVertices = ms3d_model->numVertices;

  if(!(model->vertices = malloc(sizeof(m_vertex_t)*model->numVertices)))
  {
    CON_printf("M_convertMS3D: failed to alloc memory");
    perror("M_convertMS3D malloc");
    free(model);
    return 0;
  }

  for(i = 0; i < model->numVertices; i++)
  {
    model->vertices[i].vertex[0] = ms3d_model->vertices[i].vertex[0];
    model->vertices[i].vertex[1] = ms3d_model->vertices[i].vertex[1];
    model->vertices[i].vertex[2] = ms3d_model->vertices[i].vertex[2];

    model->vertices[i].boneId = ms3d_model->vertices[i].boneId;
  }

  model->numTriangles = ms3d_model->numTriangles;

  if(!(model->triangles = malloc(sizeof(m_triangle_t)*model->numTriangles)))
  {
    CON_printf("M_convertMS3D: failed to alloc memory");
    perror("M_convertMS3D malloc");
    M_destroyModel(model);
    return 0;
  }
  
  for(i = 0; i < model->numTriangles; i++)
  {
    model->triangles[i].vertexIndices[0] = ms3d_model->triangles[i].vertexIndices[0];
    model->triangles[i].vertexIndices[1] = ms3d_model->triangles[i].vertexIndices[1];
    model->triangles[i].vertexIndices[2] = ms3d_model->triangles[i].vertexIndices[2];

    for(j = 0; j < 3; j++)
      for(k = 0; k < 3; k++)
        model->triangles[i].vertexNormals[j][k] = ms3d_model->triangles[i].vertexNormals[j][k];

    for(j = 0; j < 3; j++)
    {
      model->triangles[i].s[j] = ms3d_model->triangles[i].s[j];
      model->triangles[i].t[j] = ms3d_model->triangles[i].t[j];
    }
  }

  model->numGroups = ms3d_model->numGroups;

  if(!(model->groups = malloc(sizeof(m_group_t)*model->numGroups)))
  {
    CON_printf("M_convertMS3D: failed to alloc memory");
    perror("M_convertMS3D malloc");
    M_destroyModel(model);
    return 0;
  }

  for(i = 0; i < model->numGroups; i++)
  {
    model->groups[i].numTriangles = ms3d_model->groups[i].numTriangles;

    if(!(model->groups[i].triangleIndices = malloc(sizeof(int) * model->groups[i].numTriangles)))
    {
      CON_printf("M_convertMS3D: failed to alloc memory");
      perror("M_convertMS3D malloc");
      M_destroyModel(model);
      return 0;
    }

    for(j = 0; j < model->groups[i].numTriangles; j++)
      model->groups[i].triangleIndices[j] = ms3d_model->groups[i].triangleIndices[j];
   
    model->groups[i].materialIndex = ms3d_model->groups[i].materialIndex;
  }

  model->numMaterials = ms3d_model->numMaterials;

  if(!(model->materials = malloc(sizeof(m_material_t)*model->numMaterials)))
  {
    CON_printf("M_convertMS3D: failed to alloc memory");
    perror("M_convertMS3D malloc");
    M_destroyModel(model);
    return 0;
  }
  
  for(i = 0; i < model->numMaterials; i++)
  {
    memcpy(model->materials[i].ambient, 
           ms3d_model->materials[i].ambient,  
           sizeof(float)*4);
    memcpy(model->materials[i].diffuse, 
           ms3d_model->materials[i].diffuse,  
           sizeof(float)*4);
    memcpy(model->materials[i].specular, 
           ms3d_model->materials[i].specular,  
           sizeof(float)*4);
    memcpy(model->materials[i].emissive, 
           ms3d_model->materials[i].emissive,  
           sizeof(float)*4);
    model->materials[i].shininess = ms3d_model->materials[i].shininess;

    if(strlen(ms3d_model->materials[i].texture) > 2 && 
       ms3d_model->materials[i].texture[0] == '.' && 
       ms3d_model->materials[i].texture[1] != '.')
      sprintf(filename, "../textures/%s", ms3d_model->materials[i].texture);
    else
      strcpy(filename, ms3d_model->materials[i].texture);

    model->materials[i].texture = T_loadTextureTGA(filename, true);
  }
  
  return model;
}

void M_renderModel(m_model_t *model)
{
  int i, j, k;

  // fixme:  consider this function for optimization

  for(i = 0; i < model->numGroups; i++)
  {
    if(model->groups[i].materialIndex >= 0)
    {
      glMaterialfv(GL_FRONT, GL_AMBIENT, 
                   model->materials[model->groups[i].materialIndex].ambient);
      glMaterialfv(GL_FRONT, GL_DIFFUSE, 
                   model->materials[model->groups[i].materialIndex].diffuse);
      glMaterialfv(GL_FRONT, GL_SPECULAR, 
                   model->materials[model->groups[i].materialIndex].specular);
      glMaterialfv(GL_FRONT, GL_EMISSION, 
                   model->materials[model->groups[i].materialIndex].emissive);
      glMaterialf(GL_FRONT, GL_SHININESS, 
                   model->materials[model->groups[i].materialIndex].shininess);

      if(model->materials[model->groups[i].materialIndex].texture > 0)
      {
        glBindTexture(GL_TEXTURE_2D, model->materials[model->groups[i].materialIndex].texture);
        glEnable(GL_TEXTURE_2D);
      }
      else
        glDisable(GL_TEXTURE_2D);
    }
    else
      glDisable(GL_TEXTURE_2D);

    glBegin(GL_TRIANGLES);
      for(j = 0; j < model->groups[i].numTriangles; j++)
        for(k = 0; k < 3; k++)
        {
          glNormal3fv(model->triangles[model->groups[i].triangleIndices[j]].vertexNormals[k]);
          glTexCoord2f(model->triangles[model->groups[i].triangleIndices[j]].s[k],
                       model->triangles[model->groups[i].triangleIndices[j]].t[k]);
          glVertex3fv(model->vertices[model->triangles[model->groups[i].triangleIndices[j]].vertexIndices[k]].vertex);
        }
    glEnd();
  }
  glEnable(GL_TEXTURE_2D);
}