#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#include "scene.hpp"

#include "func.hpp"

//------------------------------------------------------------------------------
CScene::CScene(string aFileName)
//------------------------------------------------------------------------------
{
  // --- Jelenet betöltése ---
  ifstream file(aFileName.c_str());
  string dirName = aFileName.substr(0, aFileName.find_last_of("/\\") + 1);
  string mdlFileName;
  string s;

  cout << "Jelenetleíró fájl ('" << aFileName << "') betöltése: ";

  try
  {
    string line, key, value;
    size_t pos;

    while (file)
    {
      getline(file, line);
      if ((pos = line.find('=')) == string::npos)
        continue;

      key = line.substr(0, pos);
      value = line.substr(pos + 1);

      TrimString(key);
      TrimString(value);

      if (key == "model")
        mdlFileName = value;
      else if (key == "focus")
        lFocus = StringToFloat(value);
      else if (key == "rotX")
      {
        istringstream ss(value);
        ss >> s;

        if (s == "const")
        {
          lRotXType = MOVE2_CONST;
          ss >> lRotXAngle;
        }
        else if (s == "wave")
        {
          lRotXType = MOVE2_WAVE;
          ss >> lRotXSpeed >> lRotXAngle >> lRotXAmplitude;
        }
      }
      else if (key == "rotZ")
      {
        istringstream ss(value);
        ss >> s;

        if (s == "const")
        {
          lRotZType = MOVE3_CONST;
          ss >> lRotZAngle;
        }
        else if (s == "wave")
        {
          lRotZType = MOVE3_WAVE;
          ss >> lRotZSpeed >> lRotZAngle >> lRotZAmplitude;
        }
        else if (s == "linear")
        {
          lRotZType = MOVE3_LINEAR;
          ss >> lRotZSpeed;
        }
      }
      else if (key == "distance")
        lDistance = StringToFloat(value);
      else if (key == "origin")
      {
        istringstream ss(value);
        ss >> lOrigin[0] >> lOrigin[1] >> lOrigin[2];
      }
      else if (key == "light")
      {
        istringstream ss(value);
        ss >> s;
        lLamp = (s == "lamp");

        ss >> s;

        if (s == "const")
          lLightType = MOVE2_CONST;
        else if (s == "wave")
          lLightType = MOVE2_WAVE;
      }
      else if (key == "lightPos")
      {
        istringstream ss(value);
        ss >> lLightPos[0] >> lLightPos[1] >> lLightPos[2];
      }
      else if (key == "lightSpeed")
      {
        istringstream ss(value);
        ss >> lLightSpeed[0] >> lLightSpeed[1] >> lLightSpeed[2];
      }
      else if (key == "lightAmplitude")
      {
        istringstream ss(value);
        ss >> lLightAmplitude[0] >> lLightAmplitude[1] >> lLightAmplitude[2];
      }
    }

    file.close();
  }
  catch (...)
  {
    cout << "sikertelen" << endl << flush;
    throw string("Jelenet hiba!");
  }
  cout << "sikeres" << endl << flush;

  // --- Modell betöltése ---
  vector<GLfloat> vertices;
  vector<GLfloat> normals;
  vector<GLuint> faces;
  vector<GLfloat> objBoxes;
  vector<GLint> objRanges;
  map<string, GLint> mtlMap;
  GLint lastMaterial;
  int materialCount = 0;
  int faceCount = 0;
  lObjCount = -1;

  mdlFileName = dirName + mdlFileName;
  cout << "Modellfájl ('" << mdlFileName << "') betöltése: ";

  string mtlFileName;
  char line[80];

  try
  {
    file.open(mdlFileName.c_str());
    float f;
    bool first = true;
    float objMin[3], objMax[3];
    int objStart;

    while (file)
    {
      s = string("");
      file >> s;

      if (s == "mtllib")
      {
        file >> mtlFileName;
      }
      else if (s == "usemtl")
      {
        file >> s;

        map<string, GLint>::iterator it = mtlMap.find(s);

        if (it != mtlMap.end())
          lastMaterial = it->second;
        else
          mtlMap[s] = lastMaterial = mtlMap.size();

        ++materialCount;
      }
      else if (s == "o")
      {
        // Új objektum kezdése.
        if (!first)
        {
          // Az előző lezárása, ha volt.
          objBoxes.push_back(objMin[0]);
          objBoxes.push_back(objMin[1]);
          objBoxes.push_back(objMin[2]);
          objBoxes.push_back(objMax[0]);
          objBoxes.push_back(objMax[1]);
          objBoxes.push_back(objMax[2]);
          objRanges.push_back(objStart);
          objRanges.push_back(faceCount - 1);
          objRanges.push_back(lastMaterial);
        }
        ++lObjCount;

        first = true;
        objStart = faceCount;
      }
      else if (s == "v")
      {
        for (int i = 0; i < 3; ++i)
        {
          file >> f;
          vertices.push_back(f);

          if (first)
            objMin[i] = objMax[i] = f;
          else
          {
            if (f < objMin[i])
              objMin[i] = f;
            if (f > objMax[i])
              objMax[i] = f;
          }
        }
        first = false;

        file.getline(line, 80);
      }
      else if (s == "vn")
      {
        for (int i = 0; i < 3; ++i)
        {
          file >> f;
          normals.push_back(f);
        }

        file.getline(line, 80);
      }
      else if (s == "f")
      {
        ++faceCount;

        GLuint v[3], n[3];
        for (int i = 0; i < 3; ++i)
        {
          char c;
          file >> v[i] >> c >> c >> n[i];
          --v[i];
          --n[i];
        }
        faces.push_back(v[0]);
        faces.push_back(v[1]);
        faces.push_back(v[2]);
        faces.push_back(n[0]);
        faces.push_back(n[1]);
        faces.push_back(n[2]);

        file.getline(line, 80);
        if (line[0])
          cout << "Csak háromszögekkel foglalkozunk!" << endl;
      }
      else
        file.getline(line, 80);
    }

    // A végén le kell zárni az utolsó objektumot.
    objBoxes.push_back(objMin[0]);
    objBoxes.push_back(objMin[1]);
    objBoxes.push_back(objMin[2]);
    objBoxes.push_back(objMax[0]);
    objBoxes.push_back(objMax[1]);
    objBoxes.push_back(objMax[2]);
    objRanges.push_back(objStart);
    objRanges.push_back(faceCount - 1);
    objRanges.push_back(lastMaterial);

    ++lObjCount;

    file.close();
  }
  catch (...)
  {
    cout << "sikertelen" << endl << flush;
    throw string("Modell hiba!");
  }
  cout << "sikeres" << endl << flush;

  // --- Anyagjellemzők betöltése ---
  vector<GLfloat> materials(materialCount * 9);
  mtlFileName = dirName + mtlFileName;

  cout << "Anyagjellemző fájl ('" << mtlFileName << "') betöltése: ";

  try
  {
    file.open(mtlFileName.c_str());

    while (file)
    {
      s = string("");
      file >> s;

      if (s == "newmtl")
      {
        file >> s;

        map<string, GLint>::iterator it = mtlMap.find(s);

        if (it != mtlMap.end())
          lastMaterial = it->second;
      }
      else if (s == "Kd")
      {
        file >> materials[lastMaterial * 9] >> materials[lastMaterial * 9 + 1] >> materials[lastMaterial * 9 + 2];
      }
      else if (s == "Ks")
      {
        file >> materials[lastMaterial * 9 + 3] >> materials[lastMaterial * 9 + 4] >> materials[lastMaterial * 9 + 5];
      }
      else if (s == "Ns")
      {
        float ns;
        file >> ns;
        materials[lastMaterial * 9 + 6] = ns / 1000.0 * 512.0;
      }
      else if (s == "d")
      {
        float ref;
        file >> ref;
        materials[lastMaterial * 9 + 7] = 1.0f - ref;
      }
      else
        file.getline(line, 80);
    }
    file.close();
  }
  catch (...)
  {
    cout << "sikertelen" << endl << flush;
    throw string("Anyagjellemőz hiba!");
  }
  cout << "sikeres" << endl << flush;

  // --- Csúcspontok ---
  glGenTextures(1, &lVertices);
  glBindTexture(GL_TEXTURE_1D, lVertices);

  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB16F, vertices.size() / 3, 0, GL_RGB, GL_FLOAT, &vertices[0]);

  // --- Normálok ---
  glGenTextures(1, &lNormals);
  glBindTexture(GL_TEXTURE_1D, lNormals);

  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB16F, normals.size() / 3, 0, GL_RGB, GL_FLOAT, &normals[0]);

  // --- Lapok ---
  glGenTextures(1, &lFaces);
  glBindTexture(GL_TEXTURE_1D, lFaces);

  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  faceCount = faces.size() / 6;
  glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB32UI, faceCount << 1, 0, GL_RGB_INTEGER, GL_UNSIGNED_INT, &faces[0]);

  // --- Objektum befoglalók ---
  glGenTextures(1, &lObjBoxes);
  glBindTexture(GL_TEXTURE_1D, lObjBoxes);

  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB16F, objBoxes.size() / 3, 0, GL_RGB, GL_FLOAT, &objBoxes[0]);

  // --- Objektum tartományok ---
  glGenTextures(1, &lObjRanges);
  glBindTexture(GL_TEXTURE_1D, lObjRanges);

  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB32I, lObjCount, 0, GL_RGB_INTEGER, GL_INT, &objRanges[0]);

  // --- Anyagok ---
  glGenTextures(1, &lMaterials);
  glBindTexture(GL_TEXTURE_1D, lMaterials);

  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB16F, materialCount * 3, 0, GL_RGB, GL_FLOAT, &materials[0]);
}

//------------------------------------------------------------------------------
CScene::~CScene()
//------------------------------------------------------------------------------
{
  glDeleteTextures(1, &lVertices);
  glDeleteTextures(1, &lNormals);
  glDeleteTextures(1, &lFaces);
  glDeleteTextures(1, &lObjBoxes);
  glDeleteTextures(1, &lObjRanges);
  glDeleteTextures(1, &lMaterials);
}

//------------------------------------------------------------------------------
GLfloat CScene::Focus() const
//------------------------------------------------------------------------------
{
  return lFocus;
}

//------------------------------------------------------------------------------
CScene::EMoveType2 CScene::RotXType() const
//------------------------------------------------------------------------------
{
  return lRotXType;
}

//------------------------------------------------------------------------------
GLfloat CScene::RotXAngle() const
//------------------------------------------------------------------------------
{
  return lRotXAngle;
}

//------------------------------------------------------------------------------
GLfloat CScene::RotXAmplitude() const
//------------------------------------------------------------------------------
{
  return lRotXAmplitude;
}

//------------------------------------------------------------------------------
GLfloat CScene::RotXSpeed() const
//------------------------------------------------------------------------------
{
  return lRotXSpeed;
}

//------------------------------------------------------------------------------
CScene::EMoveType3 CScene::RotZType() const
//------------------------------------------------------------------------------
{
  return lRotZType;
}

//------------------------------------------------------------------------------
GLfloat CScene::RotZAngle() const
//------------------------------------------------------------------------------
{
  return lRotZAngle;
}

//------------------------------------------------------------------------------
GLfloat CScene::RotZAmplitude() const
//------------------------------------------------------------------------------
{
  return lRotZAmplitude;
}

//------------------------------------------------------------------------------
GLfloat CScene::RotZSpeed() const
//------------------------------------------------------------------------------
{
  return lRotZSpeed;
}

//------------------------------------------------------------------------------
GLfloat CScene::Distance() const
//------------------------------------------------------------------------------
{
  return lDistance;
}

//------------------------------------------------------------------------------
const GLfloat *CScene::Origin() const
//------------------------------------------------------------------------------
{
  return lOrigin;
}

//------------------------------------------------------------------------------
bool CScene::Lamp() const
//------------------------------------------------------------------------------
{
  return lLamp;
}

//------------------------------------------------------------------------------
CScene::EMoveType2 CScene::LightType() const
//------------------------------------------------------------------------------
{
  return lLightType;
}

//------------------------------------------------------------------------------
const GLfloat *CScene::LightPos() const
//------------------------------------------------------------------------------
{
  return lLightPos;
}

//------------------------------------------------------------------------------
const GLfloat *CScene::LightSpeed() const
//------------------------------------------------------------------------------
{
  return lLightSpeed;
}

//------------------------------------------------------------------------------
const GLfloat *CScene::LightAmplitude() const
//------------------------------------------------------------------------------
{
  return lLightAmplitude;
}

//------------------------------------------------------------------------------
GLuint CScene::Vertices() const
//------------------------------------------------------------------------------
{
  return lVertices;
}

//------------------------------------------------------------------------------
GLuint CScene::Normals() const
//------------------------------------------------------------------------------
{
  return lNormals;
}

//------------------------------------------------------------------------------
GLuint CScene::Faces() const
//------------------------------------------------------------------------------
{
  return lFaces;
}

//------------------------------------------------------------------------------
GLuint CScene::ObjBoxes() const
//------------------------------------------------------------------------------
{
  return lObjBoxes;
}

//------------------------------------------------------------------------------
GLuint CScene::ObjRanges() const
//------------------------------------------------------------------------------
{
  return lObjRanges;
}

//------------------------------------------------------------------------------
GLuint CScene::Materials() const
//------------------------------------------------------------------------------
{
  return lMaterials;
}

//------------------------------------------------------------------------------
int CScene::ObjCount() const
//------------------------------------------------------------------------------
{
  return lObjCount;
}
