#include "stdafx.h"

bool extract_TGP(const char* filename);
bool process_MDL(const char* filename);
bool parse(const char* data, deque<string>& list);
string strip(const string& data, char c);
string strip_trailing_spaces(const string& data);

class Exporter {
 private :
  struct Material {
   string name;
   string type;
   deque<unsigned int> diffuse_texture_index;
   float ambient[3];
   float diffuse[3];
   float specular[3];
   float emissive[3];
   float bump[3];
   float opacity;
   float shininess;
   float reflectivity;
  };
  struct Texture {
   string name;
   string type;
   string filename;
  };
  struct Skeleton {
   string parent;
   string type;
   string order;
   float translation[3];
   float rotation[3];
   float scale[3];
  };
  struct Polygon {
   deque<unsigned int> material_index;
   unsigned int face_index[3];
   unsigned int uv_index[3];
   unsigned int color[3];
  };
  struct Weight {
   size_t index;
   float value;
  };
  struct WeightMap {
   size_t size;
   string name;
   deque<Weight> data;
  };
  struct Mesh {
   string name;
   deque<Polygon> polygon_list;
   deque<vector3D<float>> vertex_list;
   deque<WeightMap> weight_list;
   deque<vector3D<float>> normal_list;
   deque<vector2D<float>> uv_list;
   deque<unsigned int> material_index_list;
  };
 private :
  deque<string> line_list;
  size_t curr_line;
 private :
  deque<Material> material_list;
  deque<Texture> texture_list;
  map<string, Skeleton> skeleton_list;
  deque<Mesh> mesh_list;
 private :
  void clear(void);
 public :
  bool ProcessMaterialList(size_t n_materials);
  bool ProcessMaterial(void);
 public :
  bool ProcessSkeletonList(size_t n_nodes);
  bool ProcessSkeleton(void);
 public :
  bool ProcessMeshList(size_t n_meshes);
  bool ProcessMesh(void);
  bool ProcessPolygonList(size_t n_polygons, Mesh& mesh);
  bool ProcessPolygon(Mesh& mesh);
  bool ProcessVertexList(size_t n_points, Mesh& mesh);
  bool ProcessWeightList(size_t n_weights, Mesh& mesh);
  bool ProcessWeight(Mesh& mesh);
  bool ProcessNormalList(size_t n_normals, Mesh& mesh);
  bool ProcessUVList(size_t n_points, Mesh& mesh);
  bool ProcessMaterialIndexList(size_t n_items, Mesh& mesh);
 public :
  bool ProcessTextureList(size_t n_textures);
  bool ProcessTexture(void);
 public :
  bool process(const char* filename, const char* savename, const char* editname);
 public :
  Exporter();
 ~Exporter();
 private :
  Exporter(const Exporter&);
  void operator =(const Exporter&);
};

int main()
{
 // create namelist
 deque<string> namelist;
 if(get_filename_list(namelist, "tgp")) {
    for(size_t i = 0; i < namelist.size(); i++)
        extract_TGP(namelist[i].c_str());
   }

 // create model namelist
 deque<string> filelist;
 deque<string> savelist;
 deque<string> editlist;

 if(!get_filename_list(filelist, "mdl"))
    return -1;

 for(size_t i = 0; i < filelist.size(); i++)
    {
     // extract filename parameters
     char param1[MAX_PATH];
     char param2[MAX_PATH];
     char param3[MAX_PATH];
     char param4[MAX_PATH];
     _splitpath(filelist[i].c_str(), param1, param2, param3, param4);

     // change extension
     string savename(param1);
     savename += param2;
     savename += param3;
     savename += ".ls";
     savelist.push_back(savename);

     // change extension
     string editname(param1);
     editname += param2;
     editname += param3;
     editname += ".txt";
     editlist.push_back(editname);
    }

 // process model files
 for(size_t i = 0; i < filelist.size(); i++) {
     cout << "Processing " << filelist[i].c_str() << "..." << endl;
     Exporter e;
     e.process(filelist[i].c_str(), savelist[i].c_str(), editlist[i].c_str());
    }

 return -1;
}

Exporter::Exporter()
{
}

Exporter::~Exporter()
{
}

void Exporter::clear(void)
{
 // clear data
 material_list.clear();
 texture_list.clear();
 skeleton_list.clear();
 mesh_list.clear();

 // clear file data
 line_list.clear();
 curr_line = 0;
}

bool Exporter::process(const char* filename, const char* savename, const char* editname)
{
 // clear data
 clear();

 // open file
 ifstream ifile(filename, ios::binary);
 if(!ifile) return error("Cannot open file.");

 // edit file
 ofstream efile(editname);
 if(!ifile) return error("Cannot open file.");

 // load data line-by-line
 for(;;)
    {
     // read line
     char buffer[2048];
     ifile.getline(buffer, 2048);
     if(ifile.fail()) break;

     // must have data
     string data(buffer);
     if(data.length() == 0) continue;
     if(data.length() == 1 && data[0] == 0x0A) continue;
     if(data.length() == 1 && data[0] == 0x0D) continue;

     // remove comments
     size_t position = data.find('#');
     if(position == 0) continue;
     if(position != string::npos) data = data.substr(0, position);
     if(!data.length()) continue;

     // remove beginning spaces
     size_t j = 0;
     for(size_t i = 0; i < data.length(); i++) {
         if(data[i] != 0x09) break;
         else j++;
        }
     data = data.substr(j);
     if(data.length() == 0) continue;
     if(data.length() == 1 && data[0] == 0x0A) continue;
     if(data.length() == 1 && data[0] == 0x0D) continue;

     // remove trailing spaces
     data = strip_trailing_spaces(data);

     // insert line
     line_list.push_back(data);
     efile << data.c_str() << endl;
    }

 // process data line-by-line
 curr_line = 0;
 do {
    // parse line
    deque<string> list;
    if(parse(line_list[curr_line].c_str(), list) == false) break;

    // process root nodes
    if(list[0] == "TGL Model Format Text Ver 0.8") curr_line++;
    else if(list[0] == "MaterialList") {
       if(list.size() < 2) return error("<MaterialList>: Expecting at least two arguments.");
       cout << " <MaterialList>" << endl;
       ProcessMaterialList(stoi(list[1].c_str()));
      }
    else if(list[0] == "MeshList") {
       cout << " <MeshList>" << endl;
       if(list.size() < 2) return error("<MeshList>: Expecting at least two arguments.");
       ProcessMeshList(stoi(list[1].c_str()));
      }
    else if(list[0] == "SkeltonList") {
       cout << " <SkeltonList>" << endl;
       if(list.size() < 2) return error("<SkeletonList>: Expecting at least two arguments.");
       ProcessSkeletonList(stoi(list[1].c_str()));
      }
    else if(list[0] == "TextureList") {
       cout << " <TextureList>" << endl;
       if(list.size() < 2) return error("<TextureList>: Expecting at least two arguments.");
       ProcessTextureList(stoi(list[1].c_str()));
      }
    else curr_line++;
 }
 while(curr_line < line_list.size());

 // create lscript file
 ofstream ofile(savename);
 ofile << "main" << endl;
 ofile << "{" << endl;
 ofile << " editbegin();" << endl;

 // for each mesh
 for(size_t i = 0; i < mesh_list.size(); i++)
    {
     // store texture
     ofile << " editbegin();" << endl;
     ofile << " uvmap = VMap(\"texture\", " << mesh_list[i].name << ", 2);" << endl;
     ofile << " editend();" << endl;

     // set layer and surface
     ofile << " lyrsetfg(" << (i + 1) << ");" << endl;
     ofile << " setsurface(" << mesh_list[i].name  << ");" << endl;

     // save triangles
     ofile << " editbegin();" << endl;
     size_t point_index = 1;
     for(size_t j = 0; j < mesh_list[i].polygon_list.size(); j++)
        {
         // point indeces
         size_t a_index = point_index++;
         size_t b_index = point_index++;
         size_t c_index = point_index++;

         // get vertex index
         unsigned short a_vertex_index = mesh_list[i].polygon_list[j].face_index[0];
         unsigned short b_vertex_index = mesh_list[i].polygon_list[j].face_index[1];
         unsigned short c_vertex_index = mesh_list[i].polygon_list[j].face_index[2];

         // get vertex value
         vector3D<float> a_vertex = mesh_list[i].vertex_list[a_vertex_index];
         vector3D<float> b_vertex = mesh_list[i].vertex_list[b_vertex_index];
         vector3D<float> c_vertex = mesh_list[i].vertex_list[c_vertex_index];

         // get uv index
         unsigned short a_uv_index = mesh_list[i].polygon_list[j].uv_index[0];
         unsigned short b_uv_index = mesh_list[i].polygon_list[j].uv_index[1];
         unsigned short c_uv_index = mesh_list[i].polygon_list[j].uv_index[2];

         // get uv value
         vector2D<float> a_uv_value = mesh_list[i].uv_list[a_uv_index];
         vector2D<float> b_uv_value = mesh_list[i].uv_list[b_uv_index];
         vector2D<float> c_uv_value = mesh_list[i].uv_list[c_uv_index];

         // save points
         ofile << " pointlist[" << a_index << "]" << " = " << "addpoint(" << a_vertex[0] << "," << a_vertex[1] << "," << a_vertex[2] << ");" << endl;
         ofile << " pointlist[" << b_index << "]" << " = " << "addpoint(" << b_vertex[0] << "," << b_vertex[1] << "," << b_vertex[2] << ");" << endl;
         ofile << " pointlist[" << c_index << "]" << " = " << "addpoint(" << c_vertex[0] << "," << c_vertex[1] << "," << c_vertex[2] << ");" << endl;

         // save uv map
         ofile << " uvmap.setValue(pointlist[" << a_index << "]," << "@" << a_uv_value[0] << "," << -a_uv_value[1] << "@);" << endl;
         ofile << " uvmap.setValue(pointlist[" << b_index << "]," << "@" << b_uv_value[0] << "," << -b_uv_value[1] << "@);" << endl;
         ofile << " uvmap.setValue(pointlist[" << c_index << "]," << "@" << c_uv_value[0] << "," << -c_uv_value[1] << "@);" << endl;

         // save triangle
         ofile << " addtriangle(pointlist[" << a_index << "], pointlist[" << b_index << "], pointlist[" << c_index << "]);" << endl;
        }
     ofile << " editend();" << endl;
    }

 // finish lscript file
 ofile << " editend();" << endl;
 ofile << "}" << endl;

 return true;
}

bool Exporter::ProcessMaterialList(size_t n_materials)
{
 // parse line
 deque<string> parse_list;

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<MaterialList>: Empty token list.");
 if(parse_list[0] != "{") return error("<MaterialList>: Expecting '{' token.");

 // process textures
 for(size_t i = 0; i < n_materials; i++)
     if(!ProcessMaterial()) return false;

 // line should be ''
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<MaterialList>: Empty token list.");
 if(parse_list[0] != "}") return error("<MaterialList>: Expecting '}' token.");

 // success
 return true;
}

bool Exporter::ProcessMaterial(void)
{
 // variables
 deque<string> parse_list;
 Material material;

 // line should be 'Material "name"'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Material>: Empty token list.");
 if(parse_list.size() < 2) return error("<Material>: Expecting <Material> <name>.");
 material.name = parse_list[1];

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Material>: Empty token list.");
 if(parse_list[0] != "{") return error("<Material>: Expecting '{' token.");

 // process lines
 for(;;)
    {
     // parse line
     curr_line++;
     parse_list.clear();
     if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Material>: Empty token list.");

     // process tokens
     if(parse_list.size() == 1) {
        if(parse_list[0] == "}") break;
        else return error("<Material>: Unexpected token.");
       }
     else if(parse_list.size() == 2) {
        if(parse_list[0] == "type") material.type = parse_list[1];
        else if(parse_list[0] == "Opacity") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[1] = strip(parse_list[1], ')');
           material.opacity = stof(parse_list[1]);
          }
        else if(parse_list[0] == "Shininess") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[1] = strip(parse_list[1], ')');
           material.shininess = stof(parse_list[1]);
          }
        else if(parse_list[0] == "Reflectivity") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[1] = strip(parse_list[1], ')');
           material.reflectivity = stof(parse_list[1]);
          }
        else if(parse_list[0] == "DiffuseTextureIndex")
          {
           // number of indices
           size_t n_indices = stoi(parse_list[1].c_str());

           // line should be '{'
           curr_line++;
           parse_list.clear();
           if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<DiffuseTextureIndex>: Empty token list.");
           if(parse_list[0] != "{") return error("<DiffuseTextureIndex>: Expecting '{' token.");

           // process indicies
           for(size_t i = 0; i < n_indices; i++) {
               curr_line++;
               parse_list.clear();
               if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<DiffuseTextureIndex>: Empty token list.");
               material.diffuse_texture_index.push_back(stoi(parse_list[0].c_str()));
              }

           // line should be '}'
           curr_line++;
           parse_list.clear();
           if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<DiffuseTextureIndex>: Empty token list.");
           if(parse_list[0] != "}") return error("<DiffuseTextureIndex>: Expecting '}' token.");
          }
        else return error("<Material>: Unexpected token.");
       }
     else if(parse_list.size() == 4) {
        if(parse_list[0] == "Ambient") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[3] = strip(parse_list[3], ')');
           material.ambient[0] = stof(parse_list[1]);
           material.ambient[1] = stof(parse_list[2]);
           material.ambient[2] = stof(parse_list[3]);
          }
        else if(parse_list[0] == "Diffuse") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[3] = strip(parse_list[3], ')');
           material.diffuse[0] = stof(parse_list[1]);
           material.diffuse[1] = stof(parse_list[2]);
           material.diffuse[2] = stof(parse_list[3]);
          }
        else if(parse_list[0] == "Specular") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[3] = strip(parse_list[3], ')');
           material.specular[0] = stof(parse_list[1]);
           material.specular[1] = stof(parse_list[2]);
           material.specular[2] = stof(parse_list[3]);
          }
        else if(parse_list[0] == "Emissive") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[3] = strip(parse_list[3], ')');
           material.emissive[0] = stof(parse_list[1]);
           material.emissive[1] = stof(parse_list[2]);
           material.emissive[2] = stof(parse_list[3]);
          }
        else if(parse_list[0] == "Bump") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[3] = strip(parse_list[3], ')');
           material.bump[0] = stof(parse_list[1]);
           material.bump[1] = stof(parse_list[2]);
           material.bump[2] = stof(parse_list[3]);
          }
        else return error("<Material>: Unexpected token.");
       }
    }

 // debug
 //cout << "MATERIAL" << endl;
 //cout << " name = " << material.name << endl;
 //cout << " type = " << material.type << endl;
 //cout << " ambient = " << material.ambient[0] << "," << material.ambient[1] << "," << material.ambient[2] << endl;
 //cout << " diffuse = " << material.diffuse[0] << "," << material.diffuse[1] << "," << material.diffuse[2] << endl;
 //cout << " specular = " << material.specular[0] << "," << material.specular[1] << "," << material.specular[2] << endl;
 //cout << " emissive = " << material.emissive[0] << "," << material.emissive[1] << "," << material.emissive[2] << endl;
 //cout << " bump = " << material.bump[0] << "," << material.bump[1] << "," << material.bump[2] << endl;
 //cout << " opacity = " << material.opacity << endl;
 //cout << " shininess = " << material.shininess << endl;
 //cout << " reflectivity = " << material.reflectivity << endl;

 // insert texture
 material_list.push_back(material);
 return true;
}

bool Exporter::ProcessSkeletonList(size_t n_nodes)
{
 // parse line
 deque<string> parse_list;

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<SkeletonList>: Empty token list.");
 if(parse_list[0] != "{") return error("<SkeletonList>: Expecting '{' token.");

 // process textures
 for(size_t i = 0; i < n_nodes; i++)
     if(!ProcessSkeleton()) return false;

 // line should be ''
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<SkeletonList>: Empty token list.");
 if(parse_list[0] != "}") return error("<SkeletonList>: Expecting '}' token.");

 // success
 return true;
}

bool Exporter::ProcessSkeleton(void)
{
 // variables
 deque<string> parse_list;
 Skeleton skeleton;

 // line should be 'Texture "name"'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Skeleton>: Empty token list.");
 if(parse_list.size() < 2) return error("<Skeleton>: Expecting <Skeleton> <name>.");
 string skeleton_name = parse_list[1];

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Skeleton>: Empty token list.");
 if(parse_list[0] != "{") return error("<Skeleton>: Expecting '{' token.");

 // process lines
 for(;;)
    {
     // parse line
     curr_line++;
     parse_list.clear();
     if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Skeleton>: Empty token list.");

     // process tokens
     if(parse_list.size() == 1) {
        if(parse_list[0] == "}") break;
        else return error("<Skeleton>: Unexpected token.");
       }
     else if(parse_list.size() == 2) {
        if(parse_list[0] == "Parent") skeleton.parent = parse_list[1];
        else if(parse_list[0] == "Type") skeleton.type = parse_list[1];
        else if(parse_list[0] == "RotationOrder") skeleton.order = parse_list[1];
        else return error("<Skeleton>: Unexpected token.");
       }
     else if(parse_list.size() == 4) {
        if(parse_list[0] == "Trans") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[3] = strip(parse_list[3], ')');
           skeleton.translation[0] = stof(parse_list[1]);
           skeleton.translation[1] = stof(parse_list[2]);
           skeleton.translation[2] = stof(parse_list[3]);
          }
        else if(parse_list[0] == "Rotate") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[3] = strip(parse_list[3], ')');
           skeleton.rotation[0] = stof(parse_list[1]);
           skeleton.rotation[1] = stof(parse_list[2]);
           skeleton.rotation[2] = stof(parse_list[3]);
          }
        else if(parse_list[0] == "Scale") {
           parse_list[1] = strip(parse_list[1], '(');
           parse_list[3] = strip(parse_list[3], ')');
           skeleton.scale[0] = stof(parse_list[1]);
           skeleton.scale[1] = stof(parse_list[2]);
           skeleton.scale[2] = stof(parse_list[3]);
          }
        else return error("<Skeleton>: Unexpected token.");
       }
     else return error("<Skeleton>: Unexpected number of tokens.");
    }

 // insert texture
 typedef map<string, Skeleton>::value_type value_type;
 skeleton_list.insert(value_type(skeleton_name, skeleton));

 // debug
 //cout << "SKELETON" << endl;
 //cout << " parent = " << skeleton.parent << endl;
 //cout << " type = " << skeleton.type << endl;
 //cout << " order = " << skeleton.order << endl;
 //cout << " translation = " << skeleton.translation[0] << "," << skeleton.translation[1] << "," << skeleton.translation[2] << endl;
 //cout << " rotation = " << skeleton.rotation[0] << "," << skeleton.rotation[1] << "," << skeleton.rotation[2] << endl;
 //cout << " scale = " << skeleton.scale[0] << "," << skeleton.scale[1] << "," << skeleton.scale[2] << endl;
 //cout << endl;

 return true;
}

bool Exporter::ProcessMeshList(size_t n_meshes)
{
 // parse line
 deque<string> parse_list;

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<MeshList>: Empty token list.");
 if(parse_list[0] != "{") return error("<MeshList>: Expecting '{' token.");

 // process textures
 for(size_t i = 0; i < n_meshes; i++)
     if(!ProcessMesh()) return false;

 // line should be '}'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<MeshList>: Empty token list.");
 if(parse_list[0] != "}") return error("<MeshList>: Expecting '}' token.");

 // success
 return true;
}

bool Exporter::ProcessMesh(void)
{
 // variables
 deque<string> parse_list;
 Mesh mesh;

 // line should be 'Mesh "name"'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Mesh>: Empty token list.");
 if(parse_list.size() < 2) return error("<Mesh>: Expecting <Mesh> <name>.");
 mesh.name = parse_list[1];

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Mesh>: Empty token list.");
 if(parse_list[0] != "{") return error("<Mesh>: Expecting '{' token.");

 // process lines
 for(;;)
    {
     // parse line
     curr_line++;
     parse_list.clear();
     if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Mesh>: Empty token list.");

     // process tokens
     if(parse_list.size() == 1) {
        if(parse_list[0] == "}") break;
        else return error("<Mesh>: Unexpected token.");
       }
     else if(parse_list.size() == 2) {
        if(parse_list[0] == "PolygonList") {
           if(!ProcessPolygonList(stoi(parse_list[1]), mesh))
              return false;
          }
        else if(parse_list[0] == "VertexList") {
           if(!ProcessVertexList(stoi(parse_list[1]), mesh))
              return false;
          }
        else if(parse_list[0] == "WeightList") {
           if(!ProcessWeightList(stoi(parse_list[1]), mesh))
              return false;
          }
        else if(parse_list[0] == "NormalList") {
           if(!ProcessNormalList(stoi(parse_list[1]), mesh))
              return false;
          }
        else if(parse_list[0] == "UVList") {
           if(!ProcessUVList(stoi(parse_list[1]), mesh))
              return false;
          }
        else if(parse_list[0] == "MaterialIndexList") {
           if(!ProcessMaterialIndexList(stoi(parse_list[1]), mesh))
              return false;
          }
        else return error("<Mesh>: Unexpected token.");
       }
    }

 // debug
 //cout << "Mesh" << endl;

 // insert texture
 mesh_list.push_back(mesh);
 return true;
}

bool Exporter::ProcessPolygonList(size_t n_polygons, Mesh& mesh)
{
 // parse line
 deque<string> parse_list;

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<PolygonList>: Empty token list.");
 if(parse_list[0] != "{") return error("<PolygonList>: Expecting '{' token.");

 // process textures
 for(size_t i = 0; i < n_polygons; i++)
     if(!ProcessPolygon(mesh)) return false;

 // line should be '}'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<PolygonList>: Empty token list.");
 if(parse_list[0] != "}") return error("<PolygonList>: Expecting '}' token.");

 // success
 return true;
}

bool Exporter::ProcessPolygon(Mesh& mesh)
{
 // variables
 deque<string> parse_list;
 Polygon polygon;

 // line should be <Polygon> <n>
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Polygon>: Empty token list.");
 if(parse_list.size() < 2) return error("<Polygon>: Expecting <Polygon> <n>.");

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Polygon>: Empty token list.");
 if(parse_list[0] != "{") return error("<Polygon>: Expecting '{' token.");

 // process lines
 for(;;)
    {
     // parse line
     curr_line++;
     parse_list.clear();
     if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Polygon>: Empty token list.");

     // process tokens
     if(parse_list[0] == "}") break;
     else if(parse_list[0] == "Material")
       {
        size_t n_indices = stoi(parse_list[1]);

        // line should be '{'
        curr_line++;
        parse_list.clear();
        if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Polygon>: Empty token list.");
        if(parse_list[0] != "{") return error("<Polygon>: Expecting '{' token.");

        // read indices
        for(size_t i = 0; i < n_indices; i++) {
            curr_line++;
            parse_list.clear();
            if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Polygon>: Empty token list.");
            polygon.material_index.push_back(stoi(parse_list[0].c_str()));
           }

        // line should be '}'
        curr_line++;
        parse_list.clear();
        if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Polygon>: Empty token list.");
        if(parse_list[0] != "}") return error("<Polygon>: Expecting '}' token.");
       }
     else if(parse_list[0] == "Face")
       {
        parse_list[2] = strip(parse_list[2], '(');
        parse_list[4] = strip(parse_list[4], ')');
        polygon.face_index[0] = stoi(parse_list[2]);
        polygon.face_index[1] = stoi(parse_list[3]);
        polygon.face_index[2] = stoi(parse_list[4]);
       }
     else if(parse_list[0] == "UV")
       {
        parse_list[2] = strip(parse_list[2], '(');
        parse_list[4] = strip(parse_list[4], ')');
        polygon.uv_index[0] = stoi(parse_list[2]);
        polygon.uv_index[1] = stoi(parse_list[3]);
        polygon.uv_index[2] = stoi(parse_list[4]);
       }
     else if(parse_list[0] == "Color")
       {
        parse_list[2] = strip(parse_list[2], '(');
        parse_list[4] = strip(parse_list[4], ')');
        polygon.color[0] = stoul(parse_list[2]); // careful here! crashes for stoi
        polygon.color[1] = stoul(parse_list[3]); // careful here! crashes for stoi
        polygon.color[2] = stoul(parse_list[4]); // careful here! crashes for stoi
       }
     else
        return error("<Polygon>: Unexpected token.");
    }

 // insert texture
 mesh.polygon_list.push_back(polygon);
 return true;
}

bool Exporter::ProcessVertexList(size_t n_points, Mesh& mesh)
{
 // parse line
 deque<string> parse_list;

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<VertexList>: Empty token list.");
 if(parse_list[0] != "{") return error("<VertexList>: Expecting '{' token.");

 // process points
 for(size_t i = 0; i < n_points; i++)
    {
     // parse line
     curr_line++;
     parse_list.clear();
     if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<VertexList>: Empty token list.");
     if(parse_list.size() != 3) return error("<VertexList>: Invalid vertex.");

     // add vertex
     vector3D<float> v;
     v[0] = stof(parse_list[0]);
     v[1] = stof(parse_list[1]);
     v[2] = stof(parse_list[2]);
     mesh.vertex_list.push_back(v);
    }

 // line should be '}'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<VertexList>: Empty token list.");
 if(parse_list[0] != "}") return error("<VertexList>: Expecting '}' token.");

 // success
 return true;
}

bool Exporter::ProcessWeightList(size_t n_weights, Mesh& mesh)
{
 // parse line
 deque<string> parse_list;

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<WeightList>: Empty token list.");
 if(parse_list[0] != "{") return error("<WeightList>: Expecting '{' token.");

 // process points
 for(size_t i = 0; i < n_weights; i++)
     ProcessWeight(mesh);

 // line should be '}'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<WeightList>: Empty token list.");
 if(parse_list[0] != "}") return error("<WeightList>: Expecting '}' token.");

 // success
 return true;
}

bool Exporter::ProcessWeight(Mesh& mesh)
{
 // variables
 deque<string> parse_list;
 WeightMap wmap;

 // line should be <Weight> <n> <name>
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Weight>: Empty token list.");
 if(parse_list.size() != 3) return error("<Weight>: Expecting <Weight> <n> <name>.");

 wmap.size = stoi(parse_list[1]);
 wmap.name = parse_list[2];

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Weight>: Empty token list.");
 if(parse_list[0] != "{") return error("<Weight>: Expecting '{' token.");

 // process weights
 for(size_t i = 0; i < wmap.size; i++)
    {
     // parse line
     curr_line++;
     parse_list.clear();
     if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Weight>: Empty token list.");

     // insert weight
     Weight w;
     w.index = stoi(parse_list[0].c_str());
     w.value = stof(parse_list[0].c_str());
     wmap.data.push_back(w);
    }

 // line should be '}'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Weight>: Empty token list.");
 if(parse_list[0] != "}") return error("<Weight>: Expecting '}' token.");

 // insert texture
 mesh.weight_list.push_back(wmap);
 return true;
}

bool Exporter::ProcessNormalList(size_t n_normals, Mesh& mesh)
{
 // parse line
 deque<string> parse_list;

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<NormalList>: Empty token list.");
 if(parse_list[0] != "{") return error("<NormalList>: Expecting '{' token.");

 // process points
 for(size_t i = 0; i < n_normals; i++)
    {
     // parse line
     curr_line++;
     parse_list.clear();
     if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<NormalList>: Empty token list.");
     if(parse_list.size() != 3) return error("<NormalList>: Invalid vertex.");

     // add vertex
     vector3D<float> v;
     v[0] = stof(parse_list[0]);
     v[1] = stof(parse_list[1]);
     v[2] = stof(parse_list[2]);
     mesh.normal_list.push_back(v);
    }

 // line should be '}'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<NormalList>: Empty token list.");
 if(parse_list[0] != "}") return error("<NormalList>: Expecting '}' token.");

 // success
 return true;
}

bool Exporter::ProcessUVList(size_t n_points, Mesh& mesh)
{
 // parse line
 deque<string> parse_list;

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<UVList>: Empty token list.");
 if(parse_list[0] != "{") return error("<UVList>: Expecting '{' token.");

 // process points
 for(size_t i = 0; i < n_points; i++)
    {
     // parse line
     curr_line++;
     parse_list.clear();
     if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<UVList>: Empty token list.");
     if(parse_list.size() != 2) return error("<UVList>: Invalid vertex.");

     // add vertex
     vector2D<float> v;
     v[0] = stof(parse_list[0]);
     v[1] = stof(parse_list[1]);
     mesh.uv_list.push_back(v);
    }

 // line should be '}'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<UVList>: Empty token list.");
 if(parse_list[0] != "}") return error("<UVList>: Expecting '}' token.");

 // success
 return true;
}

bool Exporter::ProcessMaterialIndexList(size_t n_items, Mesh& mesh)
{
 // parse line
 deque<string> parse_list;

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<MaterialIndexList>: Empty token list.");
 if(parse_list[0] != "{") return error("<MaterialIndexList>: Expecting '{' token.");

 // process material indices
 for(size_t i = 0; i < n_items; i++)
    {
     // parse line
     curr_line++;
     parse_list.clear();
     if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<MaterialIndexList>: Empty token list.");
     if(parse_list.size() != 1) return error("<MaterialIndexList>: Invalid index.");

     // add material index
     mesh.material_index_list.push_back(stoi(parse_list[0].c_str()));
    }

 // line should be '}'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<MaterialIndexList>: Empty token list.");
 if(parse_list[0] != "}") return error("<MaterialIndexList>: Expecting '}' token.");

 // success
 return true;
}

bool Exporter::ProcessTextureList(size_t n_textures)
{
 // parse line
 deque<string> parse_list;

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<TextureList>: Empty token list.");
 if(parse_list[0] != "{") return error("<TextureList>: Expecting '{' token.");

 // process textures
 for(size_t i = 0; i < n_textures; i++)
     if(!ProcessTexture()) return false;

 // line should be '}'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<TextureList>: Empty token list.");
 if(parse_list[0] != "}") return error("<TextureList>: Expecting '}' token.");

 // success
 return true;
}

bool Exporter::ProcessTexture(void)
{
 // variables
 deque<string> parse_list;
 Texture texture;

 // line should be 'Texture "name"'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Texture>: Empty token list.");
 if(parse_list.size() < 2) return error("<Texture>: Expecting <Texture> <name>.");
 texture.name = parse_list[1];

 // line should be '{'
 curr_line++;
 parse_list.clear();
 if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Texture>: Empty token list.");
 if(parse_list[0] != "{") return error("<Texture>: Expecting '{' token.");

 // process lines
 for(;;)
    {
     // parse line
     curr_line++;
     parse_list.clear();
     if(parse(line_list[curr_line].c_str(), parse_list) == false) return error("<Texture>: Empty token list.");

     // process tokens
     if(parse_list.size() == 1) {
        if(parse_list[0] == "}") break;
        else return error("<Texture>: Unexpected token.");
       }
     else if(parse_list.size() == 2) {
        if(parse_list[0] == "type") texture.type = parse_list[1];
        else if(parse_list[0] == "file") texture.filename = parse_list[1];
        else return error("<Texture>: Unexpected token.");
       }
    }

 // insert texture
 texture_list.push_back(texture);
 return true;
}

bool extract_TGP(const char* filename)
{
 ifstream ifile(filename, ios::binary);
 if(!ifile) return error("Cannot open file.");

 unsigned int h01 = LE_read_uint32(ifile); // magic
 unsigned int h02 = LE_read_uint32(ifile); // 1
 unsigned int h03 = LE_read_uint32(ifile); // 0
 unsigned int h04 = LE_read_uint32(ifile); // offset to data

 // read filelist
 struct filedata {
  string name;
  unsigned int offset;
  unsigned int length;
 };
 deque<filedata> filelist;
 for(size_t i = 0; i < h04; i++)
    {
     // read filename
     char buffer[1024];
     ifile.read(&buffer[0], 0x60);

     // read offsets
     unsigned int o1, waste01;
     unsigned int o2, waste02;
     ifile.read((char*)&o1, sizeof(o1));
     ifile.read((char*)&waste01, sizeof(waste01));
     ifile.read((char*)&o2, sizeof(o2));
     ifile.read((char*)&waste02, sizeof(waste02));

     // add item
     filedata fd = { buffer, o1, o2 };
     filelist.push_back(fd);
    }

 // get full path filename
 char full_filename[1024];
 GetFullPathNameA(filename, 1024, full_filename, NULL);
 cout << full_filename << endl;

 // get full path
 char drive[1024];
 char dir[1024];
 _splitpath(full_filename, drive, dir, NULL, NULL);
 string full_pathname = drive;
 full_pathname += dir;
 cout << full_pathname.c_str() << endl;

 for(size_t i = 0; i < filelist.size(); i++)
    {
     // get full path filename
     string fname = full_pathname;
     fname += filelist[i].name;

     // get full path
     char drive[1024];
     char dir[1024];
     _splitpath(fname.c_str(), drive, dir, NULL, NULL);
     string fpath = drive;
     fpath += dir;

     // create path if not already created
     SHCreateDirectoryEx(NULL, fpath.c_str(), NULL);

     // create file
     ofstream ofile(fname, ios::binary);
     if(!ofile) return error("Could not create file.");
     cout << "Processing " << filelist[i].name.c_str() << "..." << endl;

     // move to offset and read data
     ifile.seekg(filelist[i].offset);
     char* data = new char[filelist[i].length];
     ifile.read(data, filelist[i].length);

     // save data
     ofile.write(data, filelist[i].length);

     // delete data
     delete[] data;
     data = 0;
    }

 return true;
}

bool parse(const char* data, deque<string>& list)
{
 // must have data
 size_t size = strlen(data);
 if(size == 0) return false;

 // count spaces
 size_t spaces = 0;
 for(size_t i = 0; i < size; i++) if(data[i] == ' ') spaces++;
 if(spaces == size) return false;

 // parse
 size_t i = 0;
 while(i < size)
      {
       // find first non-whitespace character
       while(data[i] == ' ') i++;
       if(i == size) return true;
       
       // find next whitespace character
       size_t j = i + 1;
       while(data[j] != '\0' && data[j] != ' ') j++;
       
       // add string
       string temp(data);
       list.push_back(temp.substr(i, j - i));

       // increment
       i = j + 1;
      }

 return true;
}

string strip(const string& data, char c)
{
 string retval = "";
 for(size_t i = 0; i < data.length(); i++) {
     if(data[i] == c) continue;
     else retval += data[i];
    }
 return retval;
}

string strip_trailing_spaces(const string& data)
{
 string retval = "";
 for(size_t i = 0; i < data.length(); i++) {
     size_t j = (data.length() - 1) - i;
     if(data[j] == 0x09 || data[j] == 0x0A || data[j] == 0x0D) continue;
     else retval.push_back(data[j]);
    }
 std::reverse(retval.begin(), retval.end());
 return retval;
}