//-----------------------------------------------------------------------------
// Product:     OpenCTM
// File:        openctm.c
// Description: API functions.
//-----------------------------------------------------------------------------
// Copyright (c) 2009-2010 Marcus Geelnard
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//     1. The origin of this software must not be misrepresented; you must not
//     claim that you wrote the original software. If you use this software
//     in a product, an acknowledgment in the product documentation would be
//     appreciated but is not required.
//
//     2. Altered source versions must be plainly marked as such, and must not
//     be misrepresented as being the original software.
//
//     3. This notice may not be removed or altered from any source
//     distribution.
//-----------------------------------------------------------------------------

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include "openctm.h"
#include "internal.h"


// The C99 macro isfinite() is not supported on all platforms (specifically,
// MS Visual Studio does not support C99)
#if !defined(isfinite) && defined(_MSC_VER)
  #include <float.h>
  #define isfinite(x) _finite(x)
#endif


//-----------------------------------------------------------------------------
// _ctmFreeMapList() - Free a float map list.
//-----------------------------------------------------------------------------
static void _ctmFreeMapList(_CTMcontext * self, _CTMfloatmap * aMapList)
{
  _CTMfloatmap * map, * nextMap;
  map = aMapList;
  while(map)
  {
    // Free internally allocated array (if we are in import mode)
    if((self->mMode == CTM_IMPORT) && map->mValues)
      free(map->mValues);

    // Free map name
    if(map->mName)
      free(map->mName);

    // Free file name
    if(map->mFileName)
      free(map->mFileName);

    nextMap = map->mNext;
    free(map);
    map = nextMap;
  }
}

//-----------------------------------------------------------------------------
// _ctmClearMesh() - Clear the mesh in a CTM context.
//-----------------------------------------------------------------------------
static void _ctmClearMesh(_CTMcontext * self)
{
  // Free internally allocated mesh arrays
  if(self->mMode == CTM_IMPORT)
  {
    if(self->mVertices)
      free(self->mVertices);
    if(self->mIndices)
      free(self->mIndices);
    if(self->mNormals)
      free(self->mNormals);
  }

  // Clear externally assigned mesh arrays
  self->mVertices = (CTMfloat *) 0;
  self->mVertexCount = 0;
  self->mIndices = (CTMuint *) 0;
  self->mTriangleCount = 0;
  self->mNormals = (CTMfloat *) 0;

  // Free UV coordinate map list
  _ctmFreeMapList(self, self->mUVMaps);
  self->mUVMaps = (_CTMfloatmap *) 0;
  self->mUVMapCount = 0;

  // Free attribute map list
  _ctmFreeMapList(self, self->mAttribMaps);
  self->mAttribMaps = (_CTMfloatmap *) 0;
  self->mAttribMapCount = 0;
}

//-----------------------------------------------------------------------------
// _ctmCheckMeshIntegrity() - Check if a mesh is valid (i.e. is non-empty, and
// contains valid data).
//-----------------------------------------------------------------------------

static CTMint _ctmCheckMeshIntegrity(_CTMcontext * self)
{
  CTMuint i;
  _CTMfloatmap * map;

  // Check that we have all the mandatory data
  if(!self->mVertices || !self->mIndices || (self->mVertexCount < 1) ||
     (self->mTriangleCount < 1))
  {
    return CTM_FALSE;
  }

  // Check that all indices are within range
  for(i = 0; i < (self->mTriangleCount * 3); ++ i)
  {
    if(self->mIndices[i] >= self->mVertexCount)
    {
      return CTM_FALSE;
    }
  }

  // Check that all vertices are finite (non-NaN, non-inf)
  for(i = 0; i < self->mVertexCount * 3; ++ i)
  {
    if(!isfinite(self->mVertices[i]))
    {
      return CTM_FALSE;
    }
  }

  // Check that all normals are finite (non-NaN, non-inf)
  if(self->mNormals)
  {
    for(i = 0; i < self->mVertexCount * 3; ++ i)
    {
      if(!isfinite(self->mNormals[i]))
      {
        return CTM_FALSE;
      }
    }
  }

  // Check that all UV maps are finite (non-NaN, non-inf)
  map = self->mUVMaps;
  while(map)
  {
    for(i = 0; i < self->mVertexCount * 2; ++ i)
    {
      if(!isfinite(map->mValues[i]))
      {
        return CTM_FALSE;
      }
    }
    map = map->mNext;
  }

  // Check that all attribute maps are finite (non-NaN, non-inf)
  map = self->mAttribMaps;
  while(map)
  {
    for(i = 0; i < self->mVertexCount * 4; ++ i)
    {
      if(!isfinite(map->mValues[i]))
      {
        return CTM_FALSE;
      }
    }
    map = map->mNext;
  }

  return CTM_TRUE;
}

//-----------------------------------------------------------------------------
// ctmNewContext()
//-----------------------------------------------------------------------------
CTMEXPORT CTMcontext CTMCALL ctmNewContext(CTMenum aMode)
{
  _CTMcontext * self;

  // Allocate memory for the new structure
  self = (_CTMcontext *) malloc(sizeof(_CTMcontext));

  // Initialize structure (set null pointers and zero array lengths)
  memset(self, 0, sizeof(_CTMcontext));
  self->mMode = aMode;
  self->mError = CTM_NONE;
  self->mMethod = CTM_METHOD_MG1;
  self->mCompressionLevel = 1;
  self->mVertexPrecision = 1.0f / 1024.0f;
  self->mNormalPrecision = 1.0f / 256.0f;

  return (CTMcontext) self;
}

//-----------------------------------------------------------------------------
// ctmFreeContext()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmFreeContext(CTMcontext aContext)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  if(!self) return;

  // Free all mesh resources
  _ctmClearMesh(self);

  // Free the file comment
  if(self->mFileComment)
    free(self->mFileComment);

  // Free the context
  free(self);
}

//-----------------------------------------------------------------------------
// ctmGetError()
//-----------------------------------------------------------------------------
CTMEXPORT CTMenum CTMCALL ctmGetError(CTMcontext aContext)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  CTMenum err;

  if(!self) return CTM_INVALID_CONTEXT;

  // Get error code and reset error state
  err = self->mError;
  self->mError = CTM_NONE;
  return err;
}

//-----------------------------------------------------------------------------
// ctmErrorString()
//-----------------------------------------------------------------------------
CTMEXPORT const char * CTMCALL ctmErrorString(CTMenum aError)
{
  switch(aError)
  {
    case CTM_INVALID_CONTEXT:
      return "CTM_INVALID_CONTEXT";
    case CTM_INVALID_ARGUMENT:
      return "CTM_INVALID_ARGUMENT";
    case CTM_INVALID_OPERATION:
      return "CTM_INVALID_OPERATION";
    case CTM_INVALID_MESH:
      return "CTM_INVALID_MESH";
    case CTM_OUT_OF_MEMORY:
      return "CTM_OUT_OF_MEMORY";
    case CTM_FILE_ERROR:
      return "CTM_FILE_ERROR";
    case CTM_BAD_FORMAT:
      return "CTM_BAD_FORMAT";
    case CTM_LZMA_ERROR:
      return "CTM_LZMA_ERROR";
    case CTM_INTERNAL_ERROR:
      return "CTM_INTERNAL_ERROR";
    case CTM_UNSUPPORTED_FORMAT_VERSION:
      return "CTM_UNSUPPORTED_FORMAT_VERSION";
    default:
      return "Unknown error code";
  }
}

//-----------------------------------------------------------------------------
// ctmGetInteger()
//-----------------------------------------------------------------------------
CTMEXPORT CTMuint CTMCALL ctmGetInteger(CTMcontext aContext, CTMenum aProperty)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  if(!self) return 0;

  switch(aProperty)
  {
    case CTM_VERTEX_COUNT:
      return self->mVertexCount;

    case CTM_TRIANGLE_COUNT:
      return self->mTriangleCount;

    case CTM_UV_MAP_COUNT:
      return self->mUVMapCount;

    case CTM_ATTRIB_MAP_COUNT:
      return self->mAttribMapCount;

    case CTM_HAS_NORMALS:
      return self->mNormals ? CTM_TRUE : CTM_FALSE;

    case CTM_COMPRESSION_METHOD:
      return (CTMuint) self->mMethod;

    default:
      self->mError = CTM_INVALID_ARGUMENT;
  }

  return 0;
}

//-----------------------------------------------------------------------------
// ctmGetFloat()
//-----------------------------------------------------------------------------
CTMEXPORT CTMfloat CTMCALL ctmGetFloat(CTMcontext aContext, CTMenum aProperty)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  if(!self) return 0.0f;

  switch(aProperty)
  {
    case CTM_VERTEX_PRECISION:
      return self->mVertexPrecision;

    case CTM_NORMAL_PRECISION:
      return self->mNormalPrecision;

    default:
      self->mError = CTM_INVALID_ARGUMENT;
  }

  return 0.0f;
}

//-----------------------------------------------------------------------------
// ctmGetIntegerArray()
//-----------------------------------------------------------------------------
CTMEXPORT const CTMuint * CTMCALL ctmGetIntegerArray(CTMcontext aContext,
  CTMenum aProperty)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  if(!self) return (CTMuint *) 0;

  switch(aProperty)
  {
    case CTM_INDICES:
      return self->mIndices;

    default:
      self->mError = CTM_INVALID_ARGUMENT;
  }

  return (CTMuint *) 0;
}

//-----------------------------------------------------------------------------
// ctmGetFloatArray()
//-----------------------------------------------------------------------------
CTMEXPORT const CTMfloat * CTMCALL ctmGetFloatArray(CTMcontext aContext,
  CTMenum aProperty)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  CTMuint i;
  if(!self) return (CTMfloat *) 0;

  // Did the user request a UV map?
  if((aProperty >= CTM_UV_MAP_1) &&
     ((CTMuint)(aProperty - CTM_UV_MAP_1) < self->mUVMapCount))
  {
    map = self->mUVMaps;
    i = CTM_UV_MAP_1;
    while(map && (i != aProperty))
    {
      map = map->mNext;
      ++ i;
    }
    if(!map)
    {
      self->mError = CTM_INTERNAL_ERROR;
      return (CTMfloat *) 0;
    }
    return map->mValues;
  }

  // Did the user request an attribute map?
  if((aProperty >= CTM_ATTRIB_MAP_1) &&
     ((CTMuint)(aProperty - CTM_ATTRIB_MAP_1) < self->mAttribMapCount))
  {
    map = self->mAttribMaps;
    i = CTM_ATTRIB_MAP_1;
    while(map && (i != aProperty))
    {
      map = map->mNext;
      ++ i;
    }
    if(!map)
    {
      self->mError = CTM_INTERNAL_ERROR;
      return (CTMfloat *) 0;
    }
    return map->mValues;
  }

  switch(aProperty)
  {
    case CTM_VERTICES:
      return self->mVertices;

    case CTM_NORMALS:
      return self->mNormals;

    default:
      self->mError = CTM_INVALID_ARGUMENT;
  }

  return (CTMfloat *) 0;
}

//-----------------------------------------------------------------------------
// ctmGetNamedUVMap()
//-----------------------------------------------------------------------------
CTMEXPORT CTMenum CTMCALL ctmGetNamedUVMap(CTMcontext aContext,
  const char * aName)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  CTMuint result;
  if(!self) return CTM_NONE;

  map = self->mUVMaps;
  result = CTM_UV_MAP_1;
  while(map && (strcmp(aName, map->mName) != 0))
  {
    map = map->mNext;
    ++ result;
  }
  if(!map)
  {
    return CTM_NONE;
  }
  return result;
}

//-----------------------------------------------------------------------------
// ctmGetUVMapString()
//-----------------------------------------------------------------------------
CTMEXPORT const char * CTMCALL ctmGetUVMapString(CTMcontext aContext,
  CTMenum aUVMap, CTMenum aProperty)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  CTMuint i;
  if(!self) return (const char *) 0;

  // Find the indicated map
  map = self->mUVMaps;
  i = CTM_UV_MAP_1;
  while(map && (i != aUVMap))
  {
    ++ i;
    map = map->mNext;
  }
  if(!map)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return (const char *) 0;
  }

  // Get the requested string
  switch(aProperty)
  {
    case CTM_NAME:
      return (const char *) map->mName;

    case CTM_FILE_NAME:
      return (const char *) map->mFileName;

    default:
      self->mError = CTM_INVALID_ARGUMENT;
  }

  return (const char *) 0;
}

//-----------------------------------------------------------------------------
// ctmGetUVMapFloat()
//-----------------------------------------------------------------------------
CTMEXPORT CTMfloat CTMCALL ctmGetUVMapFloat(CTMcontext aContext,
  CTMenum aUVMap, CTMenum aProperty)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  CTMuint i;
  if(!self) return 0.0f;

  // Find the indicated map
  map = self->mUVMaps;
  i = CTM_UV_MAP_1;
  while(map && (i != aUVMap))
  {
    ++ i;
    map = map->mNext;
  }
  if(!map)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return 0.0f;
  }

  // Get the requested string
  switch(aProperty)
  {
    case CTM_PRECISION:
      return map->mPrecision;

    default:
      self->mError = CTM_INVALID_ARGUMENT;
  }

  return 0.0f;
}

//-----------------------------------------------------------------------------
// ctmGetAttribMapString()
//-----------------------------------------------------------------------------
CTMEXPORT const char * CTMCALL ctmGetAttribMapString(CTMcontext aContext,
  CTMenum aAttribMap, CTMenum aProperty)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  CTMuint i;
  if(!self) return (const char *) 0;

  // Find the indicated map
  map = self->mAttribMaps;
  i = CTM_ATTRIB_MAP_1;
  while(map && (i != aAttribMap))
  {
    ++ i;
    map = map->mNext;
  }
  if(!map)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return (const char *) 0;
  }

  // Get the requested string
  switch(aProperty)
  {
    case CTM_NAME:
      return (const char *) map->mName;

    default:
      self->mError = CTM_INVALID_ARGUMENT;
  }

  return (const char *) 0;
}

//-----------------------------------------------------------------------------
// ctmGetAttribMapFloat()
//-----------------------------------------------------------------------------
CTMEXPORT CTMfloat CTMCALL ctmGetAttribMapFloat(CTMcontext aContext,
  CTMenum aAttribMap, CTMenum aProperty)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  CTMuint i;
  if(!self) return 0.0f;

  // Find the indicated map
  map = self->mAttribMaps;
  i = CTM_ATTRIB_MAP_1;
  while(map && (i != aAttribMap))
  {
    ++ i;
    map = map->mNext;
  }
  if(!map)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return 0.0f;
  }

  // Get the requested string
  switch(aProperty)
  {
    case CTM_PRECISION:
      return map->mPrecision;

    default:
      self->mError = CTM_INVALID_ARGUMENT;
  }

  return 0.0f;
}

//-----------------------------------------------------------------------------
// ctmGetNamedAttribMap()
//-----------------------------------------------------------------------------
CTMEXPORT CTMenum CTMCALL ctmGetNamedAttribMap(CTMcontext aContext,
  const char * aName)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  CTMuint result;
  if(!self) return CTM_NONE;

  map = self->mAttribMaps;
  result = CTM_ATTRIB_MAP_1;
  while(map && (strcmp(aName, map->mName) != 0))
  {
    map = map->mNext;
    ++ result;
  }
  if(!map)
  {
    return CTM_NONE;
  }
  return result;
}

//-----------------------------------------------------------------------------
// ctmGetString()
//-----------------------------------------------------------------------------
CTMEXPORT const char * CTMCALL ctmGetString(CTMcontext aContext,
  CTMenum aProperty)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  if(!self) return 0;

  switch(aProperty)
  {
    case CTM_FILE_COMMENT:
      return (const char *) self->mFileComment;

    default:
      self->mError = CTM_INVALID_ARGUMENT;
  }

  return (const char *) 0;
}

//-----------------------------------------------------------------------------
// ctmCompressionMethod()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmCompressionMethod(CTMcontext aContext,
  CTMenum aMethod)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  if(!self) return;

  // You are only allowed to change compression attributes in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Check arguments
  if((aMethod != CTM_METHOD_RAW) && (aMethod != CTM_METHOD_MG1) &&
     (aMethod != CTM_METHOD_MG2))
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return;
  }

  // Set method
  self->mMethod = aMethod;
}

//-----------------------------------------------------------------------------
// ctmCompressionLevel()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmCompressionLevel(CTMcontext aContext,
  CTMuint aLevel)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  if(!self) return;

  // You are only allowed to change compression attributes in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Check arguments
  if(aLevel > 9)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return;
  }

  // Set the compression level
  self->mCompressionLevel = aLevel;
}

//-----------------------------------------------------------------------------
// ctmVertexPrecision()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmVertexPrecision(CTMcontext aContext,
  CTMfloat aPrecision)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  if(!self) return;

  // You are only allowed to change compression attributes in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Check arguments
  if(aPrecision <= 0.0f)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return;
  }

  // Set precision
  self->mVertexPrecision = aPrecision;
}

//-----------------------------------------------------------------------------
// ctmVertexPrecisionRel()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmVertexPrecisionRel(CTMcontext aContext,
  CTMfloat aRelPrecision)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  CTMfloat avgEdgeLength, * p1, * p2;
  CTMuint edgeCount, i, j;
  if(!self) return;

  // You are only allowed to change compression attributes in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Check arguments
  if(aRelPrecision <= 0.0f)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return;
  }

  // Calculate the average edge length (Note: we actually sum up all the half-
  // edges, so in a proper solid mesh all connected edges are counted twice)
  avgEdgeLength = 0.0f;
  edgeCount = 0;
  for(i = 0; i < self->mTriangleCount; ++ i)
  {
    p1 = &self->mVertices[self->mIndices[i * 3 + 2] * 3];
    for(j = 0; j < 3; ++ j)
    {
      p2 = &self->mVertices[self->mIndices[i * 3 + j] * 3];
      avgEdgeLength += sqrtf((p2[0] - p1[0]) * (p2[0] - p1[0]) +
                             (p2[1] - p1[1]) * (p2[1] - p1[1]) +
                             (p2[2] - p1[2]) * (p2[2] - p1[2]));
      p1 = p2;
      ++ edgeCount;
    }
  }
  if(edgeCount == 0)
  {
    self->mError = CTM_INVALID_MESH;
    return;
  }
  avgEdgeLength /= (CTMfloat) edgeCount;

  // Set precision
  self->mVertexPrecision = aRelPrecision * avgEdgeLength;
}

//-----------------------------------------------------------------------------
// ctmNormalPrecision()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmNormalPrecision(CTMcontext aContext,
  CTMfloat aPrecision)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  if(!self) return;

  // You are only allowed to change compression attributes in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Check arguments
  if(aPrecision <= 0.0f)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return;
  }

  // Set precision
  self->mNormalPrecision = aPrecision;
}

//-----------------------------------------------------------------------------
// ctmUVCoordPrecision()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmUVCoordPrecision(CTMcontext aContext,
  CTMenum aUVMap, CTMfloat aPrecision)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  CTMuint i;
  if(!self) return;

  // You are only allowed to change compression attributes in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Check arguments
  if(aPrecision <= 0.0f)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return;
  }

  // Find the indicated map
  map = self->mUVMaps;
  i = CTM_UV_MAP_1;
  while(map && (i != aUVMap))
  {
    ++ i;
    map = map->mNext;
  }
  if(!map)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return;
  }

  // Update the precision
  map->mPrecision = aPrecision;
}

//-----------------------------------------------------------------------------
// ctmAttribPrecision()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmAttribPrecision(CTMcontext aContext,
  CTMenum aAttribMap, CTMfloat aPrecision)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  CTMuint i;
  if(!self) return;

  // You are only allowed to change compression attributes in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Check arguments
  if(aPrecision <= 0.0f)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return;
  }

  // Find the indicated map
  map = self->mAttribMaps;
  i = CTM_ATTRIB_MAP_1;
  while(map && (i != aAttribMap))
  {
    ++ i;
    map = map->mNext;
  }
  if(!map)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return;
  }

  // Update the precision
  map->mPrecision = aPrecision;
}

//-----------------------------------------------------------------------------
// ctmFileComment()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmFileComment(CTMcontext aContext,
  const char * aFileComment)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  int len;
  if(!self) return;

  // You are only allowed to change file attributes in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Free the old comment string, if necessary
  if(self->mFileComment)
  {
    free(self->mFileComment);
    self->mFileComment = (char *) 0;
  }

  // Get length of string (if empty, do nothing)
  if(!aFileComment)
    return;
  len = strlen(aFileComment);
  if(!len)
    return;

  // Copy the string
  self->mFileComment = (char *) malloc(len + 1);
  if(!self->mFileComment)
  {
    self->mError = CTM_OUT_OF_MEMORY;
    return;
  }
  strcpy(self->mFileComment, aFileComment);
}

//-----------------------------------------------------------------------------
// ctmDefineMesh()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmDefineMesh(CTMcontext aContext,
  const CTMfloat * aVertices, CTMuint aVertexCount, const CTMuint * aIndices,
  CTMuint aTriangleCount, const CTMfloat * aNormals)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  if(!self) return;

  // You are only allowed to (re)define the mesh in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Check arguments
  if(!aVertices || !aIndices || !aVertexCount || !aTriangleCount)
  {
    self->mError = CTM_INVALID_ARGUMENT;
    return;
  }

  // Clear the old mesh, if any
  _ctmClearMesh(self);

  // Set vertex array pointer
  self->mVertices = (CTMfloat *) aVertices;
  self->mVertexCount = aVertexCount;

  // Set index array pointer
  self->mIndices = (CTMuint *) aIndices;
  self->mTriangleCount = aTriangleCount;

  // Set normal array pointer
  self->mNormals = (CTMfloat *) aNormals;
}

//-----------------------------------------------------------------------------
// _ctmAddFloatMap()
//-----------------------------------------------------------------------------
static _CTMfloatmap * _ctmAddFloatMap(_CTMcontext * self,
  const CTMfloat * aValues, const char * aName, const char * aFileName,
  _CTMfloatmap ** aList)
{
  _CTMfloatmap * map;
  CTMuint len;

  // Allocate memory for a new map list item and append it to the list
  if(!*aList)
  {
    *aList = (_CTMfloatmap *) malloc(sizeof(_CTMfloatmap));
    map = *aList;
  }
  else
  {
    map = *aList;
    while(map->mNext)
      map = map->mNext;
    map->mNext = (_CTMfloatmap *) malloc(sizeof(_CTMfloatmap));
    map = map->mNext;
  }
  if(!map)
  {
    self->mError = CTM_OUT_OF_MEMORY;
    return (_CTMfloatmap *) 0;
  }

  // Init the map item
  memset(map, 0, sizeof(_CTMfloatmap));
  map->mPrecision = 1.0f / 1024.0f;
  map->mValues = (CTMfloat *) aValues;

  // Set name of the map
  if(aName)
  {
    // Get length of string (if empty, do nothing)
    len = strlen(aName);
    if(len)
    {
      // Copy the string
      map->mName = (char *) malloc(len + 1);
      if(!map->mName)
      {
        self->mError = CTM_OUT_OF_MEMORY;
        free(map);
        return (_CTMfloatmap *) 0;
      }
      strcpy(map->mName, aName);
    }
  }

  // Set file name reference for the map
  if(aFileName)
  {
    // Get length of string (if empty, do nothing)
    len = strlen(aFileName);
    if(len)
    {
      // Copy the string
      map->mFileName = (char *) malloc(len + 1);
      if(!map->mFileName)
      {
        self->mError = CTM_OUT_OF_MEMORY;
        if(map->mName)
          free(map->mName);
        free(map);
        return (_CTMfloatmap *) 0;
      }
      strcpy(map->mFileName, aFileName);
    }
  }

  return map;
}

//-----------------------------------------------------------------------------
// ctmAddUVMap()
//-----------------------------------------------------------------------------
CTMEXPORT CTMenum CTMCALL ctmAddUVMap(CTMcontext aContext,
  const CTMfloat * aUVCoords, const char * aName, const char * aFileName)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  if(!self) return CTM_NONE;

  // Add a new UV map to the UV map list
  map = _ctmAddFloatMap(self, aUVCoords, aName, aFileName, &self->mUVMaps);
  if(!map)
    return CTM_NONE;
  else
  {
    // The default UV coordinate precision is 2^-12
    map->mPrecision = 1.0f / 4096.0f;
    ++ self->mUVMapCount;
    return CTM_UV_MAP_1 + self->mUVMapCount - 1;
  }
}

//-----------------------------------------------------------------------------
// ctmAddAttribMap()
//-----------------------------------------------------------------------------
CTMEXPORT CTMenum CTMCALL ctmAddAttribMap(CTMcontext aContext,
  const CTMfloat * aAttribValues, const char * aName)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  _CTMfloatmap * map;
  if(!self) return CTM_NONE;

  // Add a new attribute map to the attribute map list
  map = _ctmAddFloatMap(self, aAttribValues, aName, (const char *) 0,
                        &self->mAttribMaps);
  if(!map)
    return CTM_NONE;
  else
  {
    // The default vertex attribute precision is 2^-8
    map->mPrecision = 1.0f / 256.0f;
    ++ self->mAttribMapCount;
    return CTM_ATTRIB_MAP_1 + self->mAttribMapCount - 1;
  }
}

//-----------------------------------------------------------------------------
// _ctmDefaultRead()
//-----------------------------------------------------------------------------
static CTMuint CTMCALL _ctmDefaultRead(void * aBuf, CTMuint aCount,
  void * aUserData)
{
  return (CTMuint) fread(aBuf, 1, (size_t) aCount, (FILE *) aUserData);
}

//-----------------------------------------------------------------------------
// ctmLoad()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmLoad(CTMcontext aContext, const char * aFileName)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  FILE * f;
  if(!self) return;

  // You are only allowed to load data in import mode
  if(self->mMode != CTM_IMPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Open file stream
  f = fopen(aFileName, "rb");
  if(!f)
  {
    self->mError = CTM_FILE_ERROR;
    return;
  }

  // Load the file
  ctmLoadCustom(self, _ctmDefaultRead, (void *) f);

  // Close file stream
  fclose(f);
}

//-----------------------------------------------------------------------------
// _ctmAllocateFloatMaps()
//-----------------------------------------------------------------------------
static CTMuint _ctmAllocateFloatMaps(_CTMcontext * self,
  _CTMfloatmap ** aMapListPtr, CTMuint aCount, CTMuint aChannels)
{
  _CTMfloatmap ** mapListPtr;
  CTMuint i, size;

  mapListPtr = aMapListPtr;
  for(i = 0; i < aCount; ++ i)
  {
    // Allocate & clear memory for this map
    *mapListPtr = (_CTMfloatmap *) malloc(sizeof(_CTMfloatmap));
    if(!*mapListPtr)
    {
      self->mError = CTM_OUT_OF_MEMORY;
      return CTM_FALSE;
    }
    memset(*mapListPtr, 0, sizeof(_CTMfloatmap));

    // Allocate & clear memory for the float array
    size = aChannels * sizeof(CTMfloat) * self->mVertexCount;
    (*mapListPtr)->mValues = (CTMfloat *) malloc(size);
    if(!(*mapListPtr)->mValues)
    {
      self->mError = CTM_OUT_OF_MEMORY;
      return CTM_FALSE;
    }
    memset((*mapListPtr)->mValues, 0, size);

    // Next map...
    mapListPtr = &(*mapListPtr)->mNext;
  }

  return CTM_TRUE;
}

//-----------------------------------------------------------------------------
// ctmLoadCustom()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmLoadCustom(CTMcontext aContext, CTMreadfn aReadFn,
  void * aUserData)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  CTMuint formatVersion, flags, method;
  if(!self) return;

  // You are only allowed to load data in import mode
  if(self->mMode != CTM_IMPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Initialize stream
  self->mReadFn = aReadFn;
  self->mUserData = aUserData;

  // Clear any old mesh arrays
  _ctmClearMesh(self);

  // Read header from stream
  if(_ctmStreamReadUINT(self) != FOURCC("OCTM"))
  {
    self->mError = CTM_BAD_FORMAT;
    return;
  }
  formatVersion = _ctmStreamReadUINT(self);
  if(formatVersion != _CTM_FORMAT_VERSION)
  {
    self->mError = CTM_UNSUPPORTED_FORMAT_VERSION;
    return;
  }
  method = _ctmStreamReadUINT(self);
  if(method == FOURCC("RAW\0"))
    self->mMethod = CTM_METHOD_RAW;
  else if(method == FOURCC("MG1\0"))
    self->mMethod = CTM_METHOD_MG1;
  else if(method == FOURCC("MG2\0"))
    self->mMethod = CTM_METHOD_MG2;
  else
  {
    self->mError = CTM_BAD_FORMAT;
    return;
  }
  self->mVertexCount = _ctmStreamReadUINT(self);
  if(self->mVertexCount == 0)
  {
    self->mError = CTM_BAD_FORMAT;
    return;
  }
  self->mTriangleCount = _ctmStreamReadUINT(self);
  if(self->mTriangleCount == 0)
  {
    self->mError = CTM_BAD_FORMAT;
    return;
  }
  self->mUVMapCount = _ctmStreamReadUINT(self);
  self->mAttribMapCount = _ctmStreamReadUINT(self);
  flags = _ctmStreamReadUINT(self);
  _ctmStreamReadSTRING(self, &self->mFileComment);

  // Allocate memory for the mesh arrays
  self->mVertices = (CTMfloat *) malloc(self->mVertexCount * sizeof(CTMfloat) * 3);
  if(!self->mVertices)
  {
    self->mError = CTM_OUT_OF_MEMORY;
    return;
  }
  self->mIndices = (CTMuint *) malloc(self->mTriangleCount * sizeof(CTMuint) * 3);
  if(!self->mIndices)
  {
    _ctmClearMesh(self);
    self->mError = CTM_OUT_OF_MEMORY;
    return;
  }
  if(flags & _CTM_HAS_NORMALS_BIT)
  {
    self->mNormals = (CTMfloat *) malloc(self->mVertexCount * sizeof(CTMfloat) * 3);
    if(!self->mNormals)
    {
      _ctmClearMesh(self);
      self->mError = CTM_OUT_OF_MEMORY;
      return;
    }
  }

  // Allocate memory for the UV and attribute maps (if any)
  if(!_ctmAllocateFloatMaps(self, &self->mUVMaps, self->mUVMapCount, 2))
  {
    _ctmClearMesh(self);
    self->mError = CTM_OUT_OF_MEMORY;
    return;
  }
  if(!_ctmAllocateFloatMaps(self, &self->mAttribMaps, self->mAttribMapCount, 4))
  {
    _ctmClearMesh(self);
    self->mError = CTM_OUT_OF_MEMORY;
    return;
  }

  // Uncompress from stream
  switch(self->mMethod)
  {
    case CTM_METHOD_RAW:
      _ctmUncompressMesh_RAW(self);
      break;

    case CTM_METHOD_MG1:
      _ctmUncompressMesh_MG1(self);
      break;

    case CTM_METHOD_MG2:
      _ctmUncompressMesh_MG2(self);
      break;

    default:
      self->mError = CTM_INTERNAL_ERROR;
  }

  // Check mesh integrity
  if(!_ctmCheckMeshIntegrity(self))
  {
    self->mError = CTM_INVALID_MESH;
    return;
  }
}

//-----------------------------------------------------------------------------
// _ctmDefaultWrite()
//-----------------------------------------------------------------------------
static CTMuint CTMCALL _ctmDefaultWrite(const void * aBuf, CTMuint aCount,
  void * aUserData)
{
  return (CTMuint) fwrite(aBuf, 1, (size_t) aCount, (FILE *) aUserData);
}

//-----------------------------------------------------------------------------
// ctmSave()
//-----------------------------------------------------------------------------
CTMEXPORT void CTMCALL ctmSave(CTMcontext aContext, const char * aFileName)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  FILE * f;
  if(!self) return;

  // You are only allowed to save data in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Open file stream
  f = fopen(aFileName, "wb");
  if(!f)
  {
    self->mError = CTM_FILE_ERROR;
    return;
  }

  // Save the file
  ctmSaveCustom(self, _ctmDefaultWrite, (void *) f);

  // Close file stream
  fclose(f);
}

//-----------------------------------------------------------------------------
// ctmSaveCustom()
//-----------------------------------------------------------------------------
void CTMCALL ctmSaveCustom(CTMcontext aContext, CTMwritefn aWriteFn,
  void * aUserData)
{
  _CTMcontext * self = (_CTMcontext *) aContext;
  CTMuint flags;
  if(!self) return;

  // You are only allowed to save data in export mode
  if(self->mMode != CTM_EXPORT)
  {
    self->mError = CTM_INVALID_OPERATION;
    return;
  }

  // Check mesh integrity
  if(!_ctmCheckMeshIntegrity(self))
  {
    self->mError = CTM_INVALID_MESH;
    return;
  }

  // Initialize stream
  self->mWriteFn = aWriteFn;
  self->mUserData = aUserData;

  // Determine flags
  flags = 0;
  if(self->mNormals)
    flags |= _CTM_HAS_NORMALS_BIT;

  // Write header to stream
  _ctmStreamWrite(self, (void *) "OCTM", 4);
  _ctmStreamWriteUINT(self, _CTM_FORMAT_VERSION);
  switch(self->mMethod)
  {
    case CTM_METHOD_RAW:
      _ctmStreamWrite(self, (void *) "RAW\0", 4);
      break;

    case CTM_METHOD_MG1:
      _ctmStreamWrite(self, (void *) "MG1\0", 4);
      break;

    case CTM_METHOD_MG2:
      _ctmStreamWrite(self, (void *) "MG2\0", 4);
      break;

    default:
      self->mError = CTM_INTERNAL_ERROR;
      return;
  }
  _ctmStreamWriteUINT(self, self->mVertexCount);
  _ctmStreamWriteUINT(self, self->mTriangleCount);
  _ctmStreamWriteUINT(self, self->mUVMapCount);
  _ctmStreamWriteUINT(self, self->mAttribMapCount);
  _ctmStreamWriteUINT(self, flags);
  _ctmStreamWriteSTRING(self, self->mFileComment);

  // Compress to stream
  switch(self->mMethod)
  {
    case CTM_METHOD_RAW:
      _ctmCompressMesh_RAW(self);
      break;

    case CTM_METHOD_MG1:
      _ctmCompressMesh_MG1(self);
      break;

    case CTM_METHOD_MG2:
      _ctmCompressMesh_MG2(self);
      break;

    default:
      self->mError = CTM_INTERNAL_ERROR;
      return;
  }
}
