/*
  ASCII Scene import and management class
  ---------------------------------------
  Created by: Lars Englund
              2002-03-25

  Notes [(+) = TODO, (-) = FIXED]
  + Some normals get messed up!? Fix it!
  - Change the geometrics list to a list of pointers instead of a list of
    objects.
*/
#include "../include/sharedIncludes.h"
#include "CASI.h"


CASI::CASI()
{
  // Init default values
  isLoaded = false;
  silentMode = false;
  version = 0;
  comment = NULL;
  sceneFilename = NULL;
  sceneSourceFilename = NULL;
  materialCount = 0;
  materialList = NULL;
  geometricCount = 0;
  geometricList = NULL;
  vertexCount = 0;
  faceCount = 0;
  texVertexCount = 0;
  texFaceCount = 0;
  lightCount = 0;
  lightList = NULL;
  //sceneStatic[3];
  //sceneAmbient[3];
}

CASI::~CASI()
{
  // Free memory
}


void CASI::calcNormals()
{
  Uint32 a,i,i3,n;
  CVector e[3], norm;

  for( a=0; a<geometricCount; a++ )
  {

    for( i=0; i<geometricList[a]->numFaces; i++ )
    {
      i3 = i*3;
      for (n=0;n<3;n++)
      {
        e[n] = CVector( geometricList[a]->vertices[geometricList[a]->faces[i3+n]*3],
                        geometricList[a]->vertices[geometricList[a]->faces[i3+n]*3+1],
                        geometricList[a]->vertices[geometricList[a]->faces[i3+n]*3+2] );
      }
      norm = (e[1]-e[0]) ^ (e[2]-e[0]);
      norm.Normalize();
      geometricList[a]->faceNormals[i3+0] = norm.x;
      geometricList[a]->faceNormals[i3+1] = norm.y;
      geometricList[a]->faceNormals[i3+2] = norm.z;
    }
  }
}


/* Swaps from 3DStudios (X,-Z,Y) to our (X,Y,Z) coordinates. Only for normals!
   Normals exported from 3DS are messed up anyway so this function only exists
   for nostalgic reasons.. */
void CASI::flip3DSCoords()
{
  Uint32 a,i,i3;
  Scalar tmp;
  for( a=0; a<geometricCount; a++ )
  {
    for( i=0; i<geometricList[a]->numVertices; i++ )
    {
      i3 = i*3;
      /* Vertex normals */
      tmp = geometricList[a]->vertexNormals[i3+1];
      geometricList[a]->vertexNormals[i3+1] = geometricList[a]->vertexNormals[i3+2];
      geometricList[a]->vertexNormals[i3+2] = -tmp;
    }
    for( i=0; i<geometricList[a]->numFaces; i++ )
    {
      i3 = i*3;
      /* Face normals */
      tmp = geometricList[a]->faceNormals[i3+1];
      geometricList[a]->faceNormals[i3+1] = geometricList[a]->faceNormals[i3+2];
      geometricList[a]->faceNormals[i3+2] = -tmp;
    }
  }
}


void CASI::import( char *fname )
{
  sceneFilename = strdup(fname);
  if( !silentMode )
  {
    printf("--------------------\n");
    printf("ASCII Scene Importer\n");
    printf(" o Trying to open \"%s\"...\n", sceneFilename);
  }
  in = fopen(sceneFilename, "r");
  if( !in )
  {
    fprintf(stderr, "Cannot open file %s\n", sceneFilename);
    exit (1);
  }

  printf(" o Reading version...\n");
  readVersion( );
  printf(" o Reading scene info...\n");
  readScene( );
  printf(" o Reading materials...\n");
  readMaterialList( );
  printf(" o Reading lights...\n");
  readLightList( );
  printf(" o Reading geometrics...\n");
  readGeometricList( );
  printf("ASE successfully read.\n");
  
  calcNormals();
  isLoaded = true;
}

void CASI::readVersion( )
{
  jumpTo( "*3DSMAX_ASCIIEXPORT" );
    readI( &version );

  jumpTo( "*COMMENT" );
    readQS( &comment );
}

void CASI::readScene( )
{
  jumpTo( "*SCENE_FILENAME" );
    readQS( &sceneSourceFilename );
  
  jumpTo( "*SCENE_BACKGROUND_STATIC" );
    read3F( sceneStatic );

  jumpTo( "*SCENE_AMBIENT_STATIC" );
    read3F( sceneAmbient );
}

void CASI::readMaterialList( )
{
  jumpTo( "*MATERIAL_COUNT" );
    readI( &materialCount );
    materialList = new CASIMaterial[materialCount];

  for( Uint32 n=0; n<materialCount; n++ )
  {
    jumpTo( "*MATERIAL_NAME" );
      readQS( &(materialList[n].materialName) );

    jumpTo( "*MATERIAL_CLASS" );
      readQS( &(materialList[n].materialClass) );

    jumpTo( "*MATERIAL_AMBIENT" );
      read3F( materialList[n].ambient );

    jumpTo( "*MATERIAL_DIFFUSE" );
      read3F( materialList[n].diffuse );

    jumpTo( "*MATERIAL_SPECULAR" );
      read3F( materialList[n].specular );

    jumpTo( "*MATERIAL_SHINE" );
      readF( &(materialList[n].shine) );

    jumpTo( "*MATERIAL_SHINESTRENGTH" );
      readF( &(materialList[n].shineStrength) );

    jumpTo( "*MATERIAL_TRANSPARENCY" );
      readF( &(materialList[n].alpha) );

    // Count maps here and allocate right amount of maps
    materialList[n].mapCount = 1;
    materialList[n].mapList = new CASIMap[materialList[n].mapCount];  // one for diffuse
    for( Uint32 m=0; m<materialList[n].mapCount; m++ )
    {
      jumpTo( "*MAP_DIFFUSE" );  // Do a jump to "*MAP_" here instead
      jumpTo( "*MAP_NAME" );
        readQS( &materialList[n].mapList[m].mapName );

      jumpTo( "*MAP_CLASS" );
        readQS( &materialList[n].mapList[m].mapClass );

      jumpTo( "*BITMAP" );
        readQS( &materialList[n].mapList[0].imageFilename );

      jumpTo( "*MAP_TYPE" );
        readS( &materialList[n].mapList[0].mapType );

      jumpTo( "*UVW_U_OFFSET" );
        readF( &materialList[n].mapList[0].uvwUOffset );

      jumpTo( "*UVW_V_OFFSET" );
        readF( &materialList[n].mapList[0].uvwVOffset );

      jumpTo( "*UVW_U_TILING" );
        readF( &materialList[n].mapList[0].uvwUTiling );

      jumpTo( "*UVW_V_TILING" );
        readF( &materialList[n].mapList[0].uvwVTiling );

      jumpTo( "*UVW_ANGLE" );
        readF( &materialList[n].mapList[0].uvwAngle );

      jumpTo( "*BITMAP_FILTER" );
        readQS( &materialList[n].mapList[0].imageFilter );
    }
  }
}

void CASI::readGeometricList( )
{
  rewind( in );
  geometricCount = 0;
  geometricList = (CASIGeometric **) malloc(sizeof(void*) * 5000);

  while( jumpTo( "*GEOMOBJECT" ) )
  {
    geometricCount++;
    geometricList[geometricCount-1] = new CASIGeometric;
    jumpTo( "*NODE_NAME" );
      readQS( &geometricList[geometricCount-1]->name );

    readMESH( geometricList[geometricCount-1] );
    vertexCount     += geometricList[geometricCount-1]->numVertices;
    faceCount       += geometricList[geometricCount-1]->numFaces;
    texVertexCount  += geometricList[geometricCount-1]->numTexVertices;
    texFaceCount    += geometricList[geometricCount-1]->numTexFaces;

    jumpTo( "*MATERIAL_REF" );
      readI( &geometricList[geometricCount-1]->materialReference );
  }
}

void CASI::readMESH(CASIGeometric *geom)
{
  Uint32 n,i,t;
  jumpTo( "*MESH_NUMVERTEX" );
	  readI( &geom->numVertices );
    geom->vertices = new Scalar[geom->numVertices * 3];

  jumpTo( "*MESH_NUMFACES" );
    readI( &geom->numFaces );
    geom->faces = new Uint32[geom->numFaces * 3];
    geom->numFaceNormals = geom->numFaces;
    geom->numVertexNormals = geom->numFaceNormals * 3;

  jumpTo( "*MESH_VERTEX_LIST" );
    for( n=0; n<geom->numVertices; n++ )
    {
      jumpTo( "*MESH_VERTEX" );
        readI( &i );
        read3F( &geom->vertices[i*3] );
    }

  jumpTo( "*MESH_FACE_LIST" );
    for( n=0; n<geom->numFaces; n++ )
    {
      jumpTo( "*MESH_FACE" );
        readI( &i );
      jumpTo( "A:" );
        readI( &geom->faces[i*3] );
      jumpTo( "B:" );
        readI( &geom->faces[i*3+1] );
      jumpTo( "C:" );
        readI( &geom->faces[i*3+2] );
    }

  jumpTo( "*MESH_NUMTVERTEX" );
    readI( &geom->numTexVertices );
    geom->texVertices = new Scalar[geom->numTexVertices * 3];

  jumpTo( "*MESH_TVERTLIST" );
    for( n=0; n<geom->numTexVertices; n++ )
    {
      jumpTo( "*MESH_TVERT" );
        readI( &i );
        read3F( &geom->texVertices[i*3] );
    }

  jumpTo( "*MESH_NUMTVFACES" );
    readI( &geom->numTexFaces );
    geom->texFaces = new Uint32[geom->numTexFaces * 3];

  jumpTo( "*MESH_TFACELIST" );
    for( n=0; n<geom->numTexFaces; n++ )
    {
      jumpTo( "*MESH_TFACE" );
        readI( &i );
        read3I( &geom->texFaces[n*3] );
    }

  jumpTo( "*MESH_NORMALS" );
    geom->faceNormals = new float[geom->numFaceNormals * 3];
    geom->vertexNormals = new float[geom->numVertexNormals * 3];
    for( n=0; n<geom->numFaceNormals; n++ )
    {
      jumpTo( "*MESH_FACENORMAL" );
        readI( &i );
        read3F( &geom->faceNormals[i*3] );
      for( Uint32 v=0; v<3; v++ )
      {
        jumpTo( "*MESH_VERTEXNORMAL" );
          readI( &t );
          read3F( &geom->vertexNormals[i*9+v*3] );
      }
      continue;
    }
  printf("%d, ", geom->numVertices);
}


void CASI::readLightmapUVWs( char *fname )
{
  Uint32 n,i;
  in = fopen(fname, "r");
  if( !in )
  {
    fprintf(stderr, "Cannot open file %s\n", sceneFilename);
    exit (1);
  }

  jumpTo( "*MESH_NUMTVERTEX" );
    readI( &numLMTexVertices );
    lmTexVertices = new Scalar[numLMTexVertices * 3];

  jumpTo( "*MESH_TVERTLIST" );
    for( n=0; n<numLMTexVertices; n++ )
    {
      jumpTo( "*MESH_TVERT" );
        readI( &i );
        read3F( &lmTexVertices[i*3] );
    }

  jumpTo( "*MESH_NUMTVFACES" );
    readI( &numLMTexFaces );
    lmTexFaces = new Uint32[numLMTexFaces * 3];

  jumpTo( "*MESH_TFACELIST" );
    for( n=0; n<numLMTexFaces; n++ )
    {
      jumpTo( "*MESH_TFACE" );
        readI( &i );
        read3I( &lmTexFaces[n*3] );
    }
  fclose( in );
}


void CASI::readLightList( )
{
  rewind( in );
  lightCount = 0;
  while( jumpTo( "*LIGHTOBJECT" ) )
  {
    lightCount++;
    lightList = (CASILight *) realloc(lightList, sizeof(CASILight) * lightCount);

    jumpTo( "*NODE_NAME" );
      readQS( &lightList[lightCount-1].name );

    jumpTo( "*LIGHT_TYPE" );
      readS( &lightList[lightCount-1].lightType );

    readNODE( &lightList[lightCount-1].node );

    jumpTo( "*LIGHT_COLOR" );
      read3F( lightList[lightCount-1].color );

    jumpTo( "*LIGHT_INTENS" );
      readF( &lightList[lightCount-1].intensity );
  }
}

void CASI::readNODE(CASINodeTM *node)
{
  jumpTo( "*NODE_NAME" );
    readQS( &node->name );

  jumpTo( "*TM_POS" );
    read3F( node->pos );

  jumpTo( "*TM_ROTAXIS" );
    read3F( node->rotAxis );

  jumpTo( "*TM_ROTANGLE" );
    readF( &node->rotAngle );
}


int CASI::jumpTo( char *key )
{
  lastFilePos = ftell( in );
  char chunk[256];
  while (!feof(in))
  {
    chunk[255] = (char) 0;
    fscanf(in, " %s ", &chunk);

    if (!strcmp(chunk,key))
    {
      return 1;
    }
  }
  if( fseek( in, lastFilePos, 0 ) == -1 )
    printf("Cannot reset filepointer!\n");
  printf("Key \"%s\" not found!\n", key);
  return 0;
}

void CASI::readS( char **container )
{
  char chunk[256];
  chunk[255] = (char) 0;
	fscanf(in, " %s ", &chunk);
  *container = strdup(chunk);
}

void CASI::readQS( char **container )
{
  char chunk[256];
  chunk[255] = (char) 0;
	fscanf(in, " \"%[^\"]s ", &chunk);
  *container = strdup(chunk);
}

void CASI::readI( Uint32 *container )
{
  fscanf(in, " %d ", container);
}

void CASI::read3I( Uint32 *container )
{
  fscanf(in, " %d %d %d ", &container[0], &container[1], &container[2]);
}

void CASI::readF( float *container )
{
  fscanf(in, " %f ", container);
}

void CASI::read3F( float *container )
{
  fscanf(in, " %f %f %f ", &container[0], &container[1], &container[2]);
}


void CASI::printStats()
{
  Uint32 n;
  if(isLoaded)
  {
    printf("-----------------------------------\n");
    printf("Statistics for ASCII Scene Importer\n");
    printf(" o AsciiExport Version: %d\n", version);
    printf(" o Comment: %s\n", comment);
    printf(" o Scene file: %s\n", sceneFilename);
    printf(" o 3DStudio MAX file: %s\n", sceneSourceFilename);
    printf(" o Scene static: (%.4f, %.4f, %.4f)\n", sceneStatic[0], sceneStatic[1], sceneStatic[2]);
    printf(" o Scene ambient: (%.4f, %.4f, %.4f)\n", sceneAmbient[0], sceneAmbient[1], sceneAmbient[2]);

    printf(" o Scene data statistics\n");
    printf("   -Materials:   %d\n", materialCount);
    printf("   -Lights:      %d\n", lightCount);
    printf("   -Geometrics:  %d\n", geometricCount);
    printf("   -Vertices:    %d\n", vertexCount);
    printf("   -Faces:       %d\n", faceCount);
    printf("   -TexVertices: %d\n", texVertexCount);
    printf("   -TexFaces:    %d\n", texFaceCount);
  
    for( n=0; n<materialCount; n++)
      materialList[n].printStats();

    for( n=0; n<lightCount; n++)
      lightList[n].printStats();

    for( n=0; n<geometricCount; n++)
      geometricList[n]->printStats();
  }
  else
  {
    printf("-----------------------------------\n");
    printf("Statistics for ASCII Scene Importer\n");
    printf(" o No scene loaded! Use method \"import(char *filename)\" to load.\n");
  }
}


void CASI::testDraw()
{
  Uint32 a,i,i3,i9,n;

  glPushAttrib(GL_ENABLE_BIT);
  glDisable(GL_TEXTURE_2D);
  glEnable(GL_COLOR_MATERIAL);
  /* Draw geometry */
  for( a=0; a<geometricCount; a++ )
  {
    glColor3f(0.6,0.6,0.6);
	  glBegin(GL_TRIANGLES);
      for( i=0; i<geometricList[a]->numFaces; i++ )
      {
        i3 = i*3;
        i9 = i*9;
        glNormal3f( geometricList[a]->faceNormals[i3+0],
                    geometricList[a]->faceNormals[i3+1],
                    geometricList[a]->faceNormals[i3+2] );

        for (n=0;n<3;n++)
        {
          glTexCoord3f( geometricList[a]->texVertices[geometricList[a]->texFaces[i3+n]*3],
                        geometricList[a]->texVertices[geometricList[a]->texFaces[i3+n]*3+1],
                        geometricList[a]->texVertices[geometricList[a]->texFaces[i3+n]*3+2]);

          glVertex3f(   geometricList[a]->vertices[geometricList[a]->faces[i3+n]*3],
                        geometricList[a]->vertices[geometricList[a]->faces[i3+n]*3+1],
                        geometricList[a]->vertices[geometricList[a]->faces[i3+n]*3+2]);
        }
      }
    glEnd();
  }

  glDisable(GL_LIGHTING);
  glDisable(GL_COLOR_MATERIAL);
  CVector norm, pos[3], v1, v2, c;
  Scalar normLen = 3.0;  // How long normals should be drawn
  /* Draw normals */
  for( a=0; a<geometricCount; a++ )
  {
    glColor3f(1.0,0.1,0.1);
    for( i=0; i<geometricList[a]->numFaces; i++ )
    {
      i3 = i*3;
      i9 = i*9;
      norm = CVector( geometricList[a]->faceNormals[i3+0],
                      geometricList[a]->faceNormals[i3+1],
                      geometricList[a]->faceNormals[i3+2]);
      for (n=0;n<3;n++)
      {
        pos[n] = CVector( geometricList[a]->vertices[geometricList[a]->faces[i3+n]*3],
                          geometricList[a]->vertices[geometricList[a]->faces[i3+n]*3+1],
                          geometricList[a]->vertices[geometricList[a]->faces[i3+n]*3+2]);
      }
      /* Find center of triangle (roughly) */
      v1 = 0.5 * (pos[2]-pos[1]);
      v2 = 0.5 * (pos[0]-(pos[1]+v1));
      c = pos[1] + v1 + v2;
      glBegin(GL_LINES);
        glVertex3f( c.x, c.y, c.z );
        c = c + normLen * norm;
        glVertex3f( c.x, c.y, c.z );
      glEnd();
    }
  }

  /* Should be set to the same value that is to be used when rendering lightmaps
     if one wants to see how far the lights in the scene will reach. */
  static Scalar lightRangeScale = 20;
  glBlendFunc(GL_SRC_ALPHA, GL_ONE); 
  /* Draw lights */
  for( a=0; a<lightCount; a++ )
  {
    glPushMatrix();
      glTranslatef(lightList[a].node.pos[0], lightList[a].node.pos[1], lightList[a].node.pos[2]);
      glColor3fv(lightList[a].color);
      glutSolidSphere(2, 8, 6);

      glEnable(GL_BLEND);
      glDepthMask(GL_FALSE);
      glColor4f(lightList[a].color[0], lightList[a].color[1], lightList[a].color[2], 0.4);
      glutSolidSphere( lightList[a].intensity*lightRangeScale, 10, 8 );
      glDepthMask(GL_TRUE);
      glDisable(GL_BLEND);
    glPopMatrix();
  }
  glPopAttrib();
}

