/*
    This file is part of the Boson game
    Copyright (C) 2005-2006 Andreas Beckermann (b_mann@gmx.de)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "loader-ac.h"

#include "debug.h"
#include "mesh.h"
#include "material.h"
#include "lod.h"
#include "model.h"
#include "frame.h"
#include "texture.h"

#include <qptrlist.h>
#include <qstringlist.h>
#include <qvaluevector.h>
#include <qfile.h>
#include <qtextstream.h>


#define SURFACE_SHADED (1<<4)
#define SURFACE_TWOSIDED (1<<5)


// Small helper classes to hold some values
class ACFace
{
  public:
    ACFace()
    {
      smooth = false;
      twosided = false;
      material = 0;
      numpoints = 0;
      vertexindex = 0;
      texu = 0;
      texv = 0;
    }
    ~ACFace()
    {
      delete[] vertexindex;
      delete[] texu;
      delete[] texv;
    }

    bool smooth;
    bool twosided;
    int material;

    int numpoints;
    int* vertexindex;
    float* texu;
    float* texv;
};

class ACObject
{
  public:
    ACObject()
    {
      texrepX = 1.0;
      texrepY = 1.0;
      texoffX = 0.0;
      texoffY = 0.0;
      loc.set(0, 0, 0);
      numvert = 0;
      vertices = 0;
      numfaces = 0;
      faces = 0;
      numkids = 0;
      kids = 0;
      type = Poly;
    }
    ~ACObject()
    {
      delete[] vertices;
      delete[] faces;
    }

    enum Type { World = 1, Group, Poly };


    QString name;
    Type type;

    QString texture;

    float texrepX;
    float texrepY;
    float texoffX;
    float texoffY;

    BoVector3Float loc;

    int numvert;
    BoVector3Float* vertices;

    int numfaces;
    ACFace* faces;

    int numkids;
    ACObject* kids;
};


QValueVector<QString> splitString(const QString& str)
{
 QValueVector<QString> tokens;  // All tokens
 QString current;  // Token being processed atm

 for (unsigned int i = 0; i < str.length(); i++) {
	if (!str[i].isSpace()) {
		if (str[i] == '"') {
			// Quoted word(s). Loop until we find ending quote.
			i++;
			while (str[i] != '"') {
				current += str[i];
				i++;
			}
			// Token will be added once we find delimiter.
			// This means that e.g. my"f o o"bar will be treated as one token.
		} else if (str[i] == '\'') {
			// Same as above
			i++;
			while (str[i] != '\'') {
				current += str[i];
				i++;
			}
		} else {
			// Normal character. Just add it to current
			current += str[i];
		}
	} else {
		// We have a space (= delimiter)
		if (!current.isEmpty()) {
			tokens.append(current);
			current = QString();
		}
	}
 }
 // Add last token
 if (!current.isEmpty()) {
	tokens.append(current);
 }

 return tokens;
}




LoaderAC::LoaderAC(Model* m, LOD* l, const QString& file) : Loader(m, l, file)
{
  boDebug(100) << k_funcinfo << endl;
}

LoaderAC::~LoaderAC()
{
  boDebug(100) << k_funcinfo << endl;
}


bool LoaderAC::load()
{
  boDebug(100) << k_funcinfo << endl;
  if(filename().isEmpty())
  {
    boError(100) << k_funcinfo << "No file has been specified for loading" << endl;
    return false;
  }
  if(!model())
  {
    BO_NULL_ERROR(model());
    return false;
  }

  // Code taken from AC3DLoader
  QFile f(filename());
  if(!f.open(IO_ReadOnly))
  {
    boError() << k_funcinfo << "can't open " << filename() << endl;;
    return false;
  }

  QTextStream stream(&f);
  QString line;
  int i = 1;
  line = stream.readLine(); // line of text excluding '\n'
  i++;

  if(line.left(4) != "AC3D")
  {
    boError() << k_funcinfo << filename() << "is not a valid AC3D file." << endl;
    f.close();
    return false;
  }

  // Base (world) object
  ACObject* obj = new ACObject;
  bool objloaded = false;

  // Load AC3D file into memory
  while(!stream.atEnd())
  {
    // Read next line
    line = stream.readLine();

    if(line.left(8) == "MATERIAL")
    {
      mMaterialLines.append(line);
    }
    else if(line.left(6) == "OBJECT")
    {
      // Load object
      if(objloaded)
      {
        boError() << k_funcinfo << "Multiple world objects?!" << endl;
        return false;
      }
      if(!loadObject(stream, obj))
      {
        return false;
      }
      objloaded = true;
    }

    i++;
  }

  f.close();


  // Convert AC3D objects into meshes and adds them to a frame
  // Note that materials are already BoMaterials - we don't use special temporary
  //  objects for them
  lod()->createFrame();
  Frame* frame = lod()->frame(0);
  if(!frame)
  {
    BO_NULL_ERROR(frame);
    return false;
  }

  int numobjects = 0;
  countObjects(obj, &numobjects);
  frame->allocateNodes(numobjects);


  translateObject(obj, BoVector3Float());

  int index = 0;
  convertIntoMesh(frame, &index, obj);


  // Delete temporary objects
  delete obj;


  boDebug(100) << k_funcinfo << "loaded from " << filename() << endl;
  return true;
}

bool LoaderAC::loadObject(QTextStream& stream, ACObject* obj)
{
  QString line;
  QValueVector<QString> tokens;

  while(!stream.atEnd())
  {
    // Read next line
    line = stream.readLine();
    tokens = splitString(line);
    if(tokens.count() == 0)
    {
      // Probably an empty line
      continue;
    }

    if(tokens[0].lower() == "data")
    {
      if(tokens.count() != 2)
      {
        boError() << k_funcinfo << "expected 'data <number>'" << endl;
      }
      else
      {
        int len = tokens[1].toInt();
        if (len > 0)
        {
          char* str = new char[len + 1];
          stream.readRawBytes(str, len);
          delete[] str;
        }
      }
    }
    else if(tokens[0].lower() == "name")
    {
      if(tokens.count() != 2)
      {
        boError() << k_funcinfo << "expected quoted name" << endl;
      }
      else
      {
        obj->name = tokens[1];
      }
    }
    else if(tokens[0].lower() == "texture")
    {
      if(tokens.count() != 2)
      {
        boError() << k_funcinfo << "expected quoted texture name" << endl;
      }
      else
      {
        obj->texture = tokens[1];
      }
    }
    else if(tokens[0].lower() == "texrep")
    {
      if(tokens.count() != 3)
      {
        boError() << k_funcinfo << "expected 'texrep <float> <float>'" << endl;
      }
      else
      {
        obj->texrepX = tokens[1].toFloat();
        obj->texrepY = tokens[1].toFloat();
      }
    }
    else if(tokens[0].lower() == "texoff")
    {
      if(tokens.count() != 3)
      {
        boError() << k_funcinfo << "expected 'texoff <float> <float>'" << endl;
      }
      else
      {
        obj->texoffX = tokens[1].toFloat();
        obj->texoffY = tokens[1].toFloat();
      }
    }
    else if(tokens[0].lower() == "rot")
    {
      boError() << k_funcinfo << "'rot' is not yet supported!!!" << endl;
    }
    else if(tokens[0].lower() == "loc")
    {
      if(tokens.count() != 4)
      {
        boError() << k_funcinfo << "expected 'loc <float> <float> <float>'" << endl;
      }
      else
      {
        obj->loc = BoVector3Float(tokens[1].toFloat(), tokens[2].toFloat(), tokens[3].toFloat());
      }
    }
    else if(tokens[0].lower() == "numvert")
    {
      if(tokens.count() != 2)
      {
        boError() << k_funcinfo << "expected 'numvert <int>'" << endl;
      }
      else
      {
        obj->numvert = tokens[1].toInt();
        obj->vertices = new BoVector3Float[obj->numvert];
        for(int i = 0; i < obj->numvert; i++)
        {
          float x, y, z;
          stream >> x >> y >> z;
          obj->vertices[i].set(x, y, z);
        }
        // Read end of the line
        line = stream.readLine();
      }
    }
    else if(tokens[0].lower() == "numsurf")
    {
      if(tokens.count() != 2)
      {
        boError() << k_funcinfo << "expected 'numsurf <int>'" << endl;
      }
      else
      {
        obj->numfaces = tokens[1].toInt();
        obj->faces = new ACFace[obj->numfaces];
        for(int i = 0; i < obj->numfaces; i++)
        {
          if(!loadFace(stream, &obj->faces[i]))
          {
            return false;
          }
        }
      }
    }
    else if(tokens[0].lower() == "kids")
    {
      if(tokens.count() != 2)
      {
        boError() << k_funcinfo << "expected 'kids <num>'" << endl;
      }
      else
      {
        obj->numkids = tokens[1].toInt();
        if(obj->numkids > 0)
        {
          obj->kids = new ACObject[obj->numkids];
          for(int i = 0; i < obj->numkids; i++)
          {
            // Load 'OBJECT <type>' line (type isn't used yet)
            line = stream.readLine();
            if(!loadObject(stream, &obj->kids[i]))
            {
              return false;
            }
          }
        }
      }
      // Kids token ends object section, so we're done with this object
      return true;
    }

  }

  return true;
}

bool LoaderAC::loadFace(QTextStream& stream, ACFace* face)
{
  QString line;
  QValueVector<QString> tokens;

  while(!stream.atEnd())
  {
    // Read next line
    line = stream.readLine();
    tokens = splitString(line);
    if(tokens.count() == 0) {
      // Probably an empty line
      continue;
    }

    if(tokens[0].lower() == "surf")
    {
      if(tokens.count() != 2)
      {
        boError() << k_funcinfo << "expected 'SURF <flags>'" << endl;
      }
      else
      {
        long int flags;
        // AC3D saves flags as e.g. "0x20", but we have to cut 0x from the
        //  beginning to make QString::toLong() work
        if(tokens[1][1] == 'x')
        {
          flags = tokens[1].right(tokens[1].length() - 2).toLong(0, 16);
        }
        else
        {
          flags = tokens[1].toLong(0, 16);
        }

        // flags:
        // The first 4 bits (flags & 0xF) is the type
        // (0 = polygon, 1 = closedline, 2 = line)
        // -> see http://www.ac3d.org/ac3d/man/ac3dfileformat.html
        int type = (flags & 0xF);
        if(type != 0)
        {
          boError() << k_funcinfo << "type != polygon (0) not supported!" << endl;
          return false;
        }
        if(flags & SURFACE_SHADED)
        {
          face->smooth = true;
        }
        if(flags & SURFACE_TWOSIDED)
        {
          face->twosided = true;
        }
      }
    }
    else if(tokens[0].lower() == "mat")
    {
      if(tokens.count() != 2)
      {
        boError() << k_funcinfo << "expected 'mat <index>'" << endl;
      }
      else
      {
        face->material = tokens[1].toInt();
      }
    }
    else if(tokens[0].lower() == "refs")
    {
      if(tokens.count() != 2)
      {
        boError() << k_funcinfo << "expected 'refs <num>'" << endl;
      }
      else
      {
        face->numpoints = tokens[1].toInt();
        face->vertexindex = new int[face->numpoints];
        face->texu = new float[face->numpoints];
        face->texv = new float[face->numpoints];
        int vertex;
        float u, v;
        for(int i = 0; i < face->numpoints; i++)
        {
          stream >> vertex >> u >> v;
          face->vertexindex[i] = vertex;
          face->texu[i] = u;
          face->texv[i] = v;
        }
        // Read end of the line
        line = stream.readLine();
      }
      // Refs entry _should_ be the last entry (line) of a surface in a file.
      //  Next line should already belong to the next surface or to something
      //  else. Problem here is that there's no way to check if it really is so.
      // FIXME: can we somehow check if this surface really ends here?
      return true;
    }

  }

  return true;
}

Material* LoaderAC::requestMaterial(const QString& line, const QString& texture)
{
  if(!mMaterialLines.contains(line))
  {
    boError() << k_funcinfo << "requested unknown material line" << endl;
    return 0;
  }
  QValueList<Material*> list = mLine2Materials[line];
  for(QValueList<Material*>::iterator it = list.begin(); it != list.end(); ++it)
  {
    if(!(*it)->texture() && texture.isEmpty())
    {
      return *it;
    }
    if((*it)->texture() && (*it)->texture()->filename() == texture)
    {
      return *it;
    }
  }

  if(!loadMaterial(line))
  {
    boError() << k_funcinfo << "could not load material from line " << line << endl;
    return 0;
  }
  Material* mat = model()->material(model()->materialCount() - 1);
  mat->setTexture(model()->getTexture(texture));
  mLine2Materials[line].append(mat);
  return mat;
}

bool LoaderAC::loadMaterial(const QString& line)
{
  // Parse AC3D material
  QValueVector<QString> tokens = splitString(line);

  if(tokens.count() != 22)
  {
    boError() << k_funcinfo << "expected 21 params after \"MATERIAL\" - line " << line << endl;
    return false;
  }
  else
  {
    Material* mat = new Material;
    BoVector4Float color;
    mat->setName(tokens[1]);
    color.set(tokens[3].toFloat(), tokens[4].toFloat(), tokens[5].toFloat(), 1.0);
    mat->setDiffuse(color);

    color.set(tokens[7].toFloat(), tokens[8].toFloat(), tokens[9].toFloat(), 1.0);
    mat->setAmbient(color);

    // Emissive color isn't supported by boson atm

    color.set(tokens[15].toFloat(), tokens[16].toFloat(), tokens[17].toFloat(), 1.0);
    mat->setSpecular(color);

    mat->setShininess(tokens[19].toFloat());
#warning FIXME: transparency
#if 0
    mat->setTransparency(tokens[21].toFloat());
#endif

    model()->addMaterial(mat);
  }
  return true;
}

bool LoaderAC::convertIntoMesh(Frame* f, int* index, ACObject* obj)
{
  if(!f) {
    BO_NULL_ERROR(f);
    return false;
  }
  if(!index) {
    BO_NULL_ERROR(index);
    return false;
  }
  if(!obj) {
    BO_NULL_ERROR(obj);
    return false;
  }
  if(!lod()) {
    BO_NULL_ERROR(lod());
    return false;
  }
  if(!model()) {
    BO_NULL_ERROR(model());
    return false;
  }
  // First load children
  for(int i = 0; i < obj->numkids; i++)
  {
    if(!convertIntoMesh(f, index, &obj->kids[i]))
    {
      return false;
    }
  }

  // Make sure ACObject has some faces/vertices
  // Note that it's ok to have none - e.g. if object is a group
  if(obj->numfaces == 0)
  {
    return true;
  }
  if(obj->numvert == 0)
  {
    boError() << k_funcinfo << "No vertices for object with " << obj->numfaces << " faces?!" << endl;
    return false;
  }

  // Construct a Mesh
  Mesh* boMesh = new Mesh();
  boMesh->setName(obj->name);
  lod()->addMesh(boMesh);

  // Set mesh's material
  int ac3dMatIndex = obj->faces[0].material;
  QString ac3dMatLine;
  if(ac3dMatIndex >= 0 && ac3dMatIndex < mMaterialLines.count())
  {
    ac3dMatLine = mMaterialLines[ac3dMatIndex];
  }
  bool hadDifferentMaterials = false;
  Material* material = requestMaterial(ac3dMatLine, obj->texture);
  if(!material)
  {
    BO_NULL_ERROR(material);
    return false;
  }
  boMesh->setMaterial(material);
#warning FIXME: twosided
#if 0
  material->setTwoSided(obj->faces[0].twosided);  // sucks
#endif
  if(!obj->texture.isEmpty())
  {
    if(material->texture() && (material->texture()->filename() != obj->texture))
    {
      // This material has different textures
      boWarning(100) << k_funcinfo << "Multiple textures use with material " << material->name() << endl;
    }
    else
    {
      material->setTexture(model()->getTexture(obj->texture));
    }
  }
  // Check if it's a teamcolored-mesh
  boMesh->setIsTeamColor(obj->name.find("teamcolor", 0, false) == 0);


  // Load faces
  // FIXME: properly handle points with same texel and vertex coords
  int bosonVertexCount = 0;
  int bosonFacesCount = 0;
  for(int i = 0; i < obj->numfaces; i++)
  {
    int points = obj->faces[i].numpoints;
    if(points < 3)
    {
      // a point or a line
      boWarning(100) << k_funcinfo << "faces with numpoints < 3 not yet supported (have " << obj->faces[i].numpoints << ")" << endl;
      continue;
    }
    bosonVertexCount += points;
    bosonFacesCount += (points - 2);
  }
  boMesh->allocateVertices(bosonVertexCount);
  boMesh->allocateFaces(bosonFacesCount);

  for(int i = 0; i < obj->numfaces; i++)
  {
    if(obj->faces[i].material != ac3dMatIndex)
    {
      hadDifferentMaterials = true;
    }
  }

  int pointIndex = 0;
  bosonFacesCount = 0;
  for(int i = 0; i < obj->numfaces; i++)
  {
    Vertex* vertices[3];
    Vertex* vertex;

    if(obj->faces[i].numpoints < 3)
    {
      boError(100) << k_funcinfo << "need at least 3 points - have only " << obj->faces[i].numpoints << " in face " << i << endl;
      return false;
    }

    // TODO: this should be a generic bobmfconverter processor
    // -> we should simply load all vertices into the face and let a
    //    postprocessing phase do this work.
    Face* faces = new Face[obj->faces[i].numpoints - 2];
    if(!convertPolygonToFaces(obj, i, boMesh, faces, &pointIndex))
    {
      boError(100) << k_funcinfo << "converting polygon to faces failed" << endl;
      return false;
    }
    for(int j = 0; j < obj->faces[i].numpoints - 2; j++)
    {
      Face* face = boMesh->face(bosonFacesCount);
      bosonFacesCount++;
      if(!face)
      {
        BO_NULL_ERROR(face);
        delete[] faces;
        return false;
      }
      face->setVertexCount(3);
      face->setVertex(0, faces[j].vertex(0));
      face->setVertex(1, faces[j].vertex(1));
      face->setVertex(2, faces[j].vertex(2));
      face->smoothgroup = faces[j].smoothgroup;
    }
    delete[] faces;
  }

  if(hadDifferentMaterials)
  {
    boWarning(100) << k_funcinfo << "Mesh " << obj->name << " had different materials!" << endl;
  }

  // Add mesh to frame
  f->setMesh(*index, boMesh);
  if(!f->matrix(*index))
  {
    BO_NULL_ERROR(f->matrix(*index));
    return false;
  }
  f->matrix(*index)->translate(obj->loc);
  (*index)++;

  return true;
}

bool LoaderAC::convertPolygonToFaces(ACObject* obj, int face, Mesh* boMesh, Face* boFaces, int* pointIndex)
{
  if(obj->faces[face].numpoints < 3)
  {
    boError(100) << k_funcinfo << "need at least 3 points" << endl;
    return false;
  }
  for(int i = 0; i < obj->faces[face].numpoints - 2; i++)
  {
    boFaces[i].setVertexCount(3);
    boFaces[i].smoothgroup = (obj->faces[face].smooth ? 1 : 0);
  }
  Vertex** vertices = new Vertex*[obj->faces[face].numpoints];

  // create the required vertices and copy the pointer into the vertices array
  for(int i = 0; i < obj->faces[face].numpoints; i++)
  {
    Vertex* vertex = boMesh->vertex(*pointIndex);
    (*pointIndex)++;
    if(!vertex)
    {
      BO_NULL_ERROR(vertex);
      return false;
    }
    vertex->pos = BoVector3Float(obj->vertices[obj->faces[face].vertexindex[i]]);
    vertex->tex = BoVector2Float(obj->faces[face].texu[i], obj->faces[face].texv[i]);
    vertices[i] = vertex;
  }

  for(int i = 0; i < obj->faces[face].numpoints - 2; i++)
  {
    boFaces[i].setVertex(0, vertices[0]);
    boFaces[i].setVertex(1, vertices[i + 1]);
    boFaces[i].setVertex(2, vertices[i + 2]);
  }
  delete[] vertices;
  return true;
}

void LoaderAC::countObjects(ACObject* obj, int* count)
{
  // First count children
  for(int i = 0; i < obj->numkids; i++)
  {
    countObjects(&obj->kids[i], count);
  }

  if(obj->numfaces > 0)
  {
    (*count)++;
  }
}

void LoaderAC::translateObject(ACObject* obj, const BoVector3Float& trans)
{
  // Add trans to our loc
  obj->loc += trans;

  // Translate children
  for(int i = 0; i < obj->numkids; i++)
  {
    translateObject(&obj->kids[i], obj->loc);
  }
}

/*
 * vim: et sw=2
 */
