/*==========================================================
  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_listmodelmodel = 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 = 0i < model->numGroupsi++)
        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_listmodelmodel = model->next)
    if(!strcmp(namemodel->name))
      return model;

  return 0;
}

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

  for(model = gm_model_listmodelmodel = 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"filenamemodel->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(model0sizeof(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 = 0i < model->numVerticesi++)
  {
    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 = 0i < model->numTrianglesi++)
  {
    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 = 0j < 3j++)
      for(k = 0k < 3k++)
        model->triangles[i].vertexNormals[j][k] = ms3d_model->triangles[i].vertexNormals[j][k];

    for(j = 0j < 3j++)
    {
      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 = 0i < model->numGroupsi++)
  {
    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 = 0j < model->groups[i].numTrianglesj++)
      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 = 0i < model->numMaterialsi++)
  {
    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(filenamems3d_model->materials[i].texture);

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

void M_renderModel(m_model_t *model)
{
  int ijk;

  // fixme:  consider this function for optimization

  for(i = 0i < model->numGroupsi++)
  {
    if(model->groups[i].materialIndex >= 0)
    {
      glMaterialfv(GL_FRONTGL_AMBIENT
                   model->materials[model->groups[i].materialIndex].ambient);
      glMaterialfv(GL_FRONTGL_DIFFUSE
                   model->materials[model->groups[i].materialIndex].diffuse);
      glMaterialfv(GL_FRONTGL_SPECULAR
                   model->materials[model->groups[i].materialIndex].specular);
      glMaterialfv(GL_FRONTGL_EMISSION
                   model->materials[model->groups[i].materialIndex].emissive);
      glMaterialf(GL_FRONTGL_SHININESS
                   model->materials[model->groups[i].materialIndex].shininess);

      if(model->materials[model->groups[i].materialIndex].texture > 0)
      {
        glBindTexture(GL_TEXTURE_2Dmodel->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 = 0j < model->groups[i].numTrianglesj++)
        for(k = 0k < 3k++)
        {
          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);
}