#include <cmath>
#include <fstream>
#include <iostream>
#include <SDL/SDL.h>

#include "renderer.hpp"
#include "func.hpp"

//------------------------------------------------------------------------------
CRenderer::CRenderer(const CSettings &aSettings)
//------------------------------------------------------------------------------
    : lSettings(aSettings)
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  glViewport(0, 0, lSettings.Width(), lSettings.Height());
  lRatio = static_cast<float>(lSettings.Width()) / lSettings.Height();
  gluOrtho2D(-lRatio, lRatio, -1.0, 1.0);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  lScene = new CScene(aSettings.SceneFileName());

  lShVertex = LoadShader(GL_VERTEX_SHADER, "shader/main.vs");
  lShFragment = LoadShader(GL_FRAGMENT_SHADER, "shader/main.fs");
  bool result = true;
  result &= DebugShader(lShVertex, "shader/main.vs");
  result &= DebugShader(lShFragment, "shader/main.fs");
  if (!result)
    throw string("Shader hiba!");

  lShProgram = glCreateProgram();
  glAttachShader(lShProgram, lShVertex);
  glAttachShader(lShProgram, lShFragment);
  glLinkProgram(lShProgram);

  lShFocus = glGetUniformLocation(lShProgram, "focus");
  lShRotX = glGetUniformLocation(lShProgram, "rotX");
  lShRotZ = glGetUniformLocation(lShProgram, "rotZ");
  lShDistance = glGetUniformLocation(lShProgram, "distance");
  lShOrigin = glGetUniformLocation(lShProgram, "origin");

  lShVertices = glGetUniformLocation(lShProgram, "vertices");
  lShNormals = glGetUniformLocation(lShProgram, "normals");
  lShFaces = glGetUniformLocation(lShProgram, "faces");
  lShObjBoxes = glGetUniformLocation(lShProgram, "objBoxes");
  lShObjRanges = glGetUniformLocation(lShProgram, "objRanges");
  lShMaterials = glGetUniformLocation(lShProgram, "materials");
  lShObjCount = glGetUniformLocation(lShProgram, "objCount");
  lShLightPos = glGetUniformLocation(lShProgram, "lightPos");

  GLenum error;
  if ((error = glGetError()) != GL_NO_ERROR)
    cout << gluErrorString(error) << endl;
}

//------------------------------------------------------------------------------
CRenderer::~CRenderer()
//------------------------------------------------------------------------------
{
  delete lScene;
}

//------------------------------------------------------------------------------
GLuint CRenderer::LoadShader(GLenum aShaderType, const string &aFileName) const
//------------------------------------------------------------------------------
{
  GLuint shader = glCreateShader(aShaderType);

  ifstream file(aFileName.c_str(), ios::binary);

  // --- A fájl méretének kiderítése ---
  file.seekg(0, ios::end);
  int length = file.tellg();
  if (length < 0)
    return 0;
  file.seekg(0, ios::beg);

  // --- Tartalom beolvasása ---
  char *data = new char[length + 1];
  file.read(data, length);
  data[length] = '\0';
  file.close();
  const char **source = const_cast<const char **>(&data);

  StrReplace(data, "%DEFINE_REFLECTION%", lSettings.MaxRayDepth() > 1 ? "#define REFLECTION " : "                   ");
  StrReplace(data, "%DEFINE_SHADOW%", lSettings.Shadow() ? "#define SHADOW " : "               ");
  StrReplace(data, "%DEFINE_LAMP%", lScene->Lamp() ? "#define LAMP " : "             ");
  StrReplace(data, "%MAXRAYDEPTH%", IntToStr(lSettings.MaxRayDepth(), 13).c_str());
  StrReplace(data, "%REFL_THRESHOLD%", FloatToStr(lSettings.ReflThreshold(), 16).c_str());
  StrReplace(data, "%FAR_Z%", FloatToStr(lSettings.FarZ(), 7).c_str());

  // --- Árnyaló fordítása ---
  glShaderSource(shader, 1, source, NULL);
  glCompileShader(shader);
  delete[] data;

  return shader;
}

//------------------------------------------------------------------------------
bool CRenderer::DebugShader(GLuint aShader, const string &aFileName) const
//------------------------------------------------------------------------------
{
  int value;

  glGetShaderiv(aShader, GL_COMPILE_STATUS, &value);
  cout << "Árnyaló ('" << aFileName << "') fordítása: " << (value == GL_TRUE ? "sikeres" : "sikertelen") << endl;
  if (value)
    return true;

  glGetShaderiv(aShader, GL_INFO_LOG_LENGTH, &value);
  char log[value];
  int length;

  glGetShaderInfoLog(aShader, value, &length, log);
  cout << "Árnyaló naplója: \"" << endl << log << "\"" << endl;

  cout << flush;

  return false;
}

//------------------------------------------------------------------------------
void CRenderer::Draw(float aTime) const
//------------------------------------------------------------------------------
{
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glUseProgram(lShProgram);

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_1D, lScene->Vertices());
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_1D, lScene->Normals());
  glActiveTexture(GL_TEXTURE2);
  glBindTexture(GL_TEXTURE_1D, lScene->Faces());
  glActiveTexture(GL_TEXTURE3);
  glBindTexture(GL_TEXTURE_1D, lScene->ObjBoxes());
  glActiveTexture(GL_TEXTURE4);
  glBindTexture(GL_TEXTURE_1D, lScene->ObjRanges());
  glActiveTexture(GL_TEXTURE5);
  glBindTexture(GL_TEXTURE_1D, lScene->Materials());
  glActiveTexture(GL_TEXTURE0);
  glUniform1f(lShFocus, lScene->Focus() / 12.0);
  switch (lScene->RotXType())
  {
    case CScene::MOVE2_CONST:
      glUniform1f(lShRotX, lScene->RotXAngle() * -M_PI / 180.0f);
      break;

    case CScene::MOVE2_WAVE:
      glUniform1f(lShRotX, (cos(aTime * lScene->RotXSpeed()) * lScene->RotXAmplitude() + lScene->RotXAngle()) * -M_PI / 180.0);
      break;
  }
  switch (lScene->RotZType())
  {
    case CScene::MOVE3_CONST:
      glUniform1f(lShRotZ, lScene->RotZAngle() * -M_PI / 180.0f);
      break;

    case CScene::MOVE3_WAVE:
      glUniform1f(lShRotZ, (sin(aTime * lScene->RotZSpeed()) * lScene->RotZAmplitude() + lScene->RotZAngle()) * -M_PI / 180.0);
      break;

    case CScene::MOVE3_LINEAR:
      glUniform1f(lShRotZ, lScene->RotZSpeed() * aTime * 2.0 * M_PI);
      break;
  }
  glUniform1f(lShDistance, lScene->Distance());
  glUniform3fv(lShOrigin, 1, lScene->Origin());
  glUniform1i(lShVertices, 0);
  glUniform1i(lShNormals, 1);
  glUniform1i(lShFaces, 2);
  glUniform1i(lShObjBoxes, 3);
  glUniform1i(lShObjRanges, 4);
  glUniform1i(lShMaterials, 5);
  glUniform1i(lShObjCount, lScene->ObjCount());
  switch (lScene->LightType())
  {
    case CScene::MOVE2_CONST:
      glUniform3fv(lShLightPos, 1, lScene->LightPos());
      break;

    case CScene::MOVE2_WAVE:
      GLfloat lightPos[3];
      lightPos[0] = cos(aTime * lScene->LightSpeed()[0]) * lScene->LightAmplitude()[0] + lScene->LightPos()[0];
      lightPos[1] = sin(aTime * lScene->LightSpeed()[1]) * lScene->LightAmplitude()[1] + lScene->LightPos()[1];
      lightPos[2] = cos(aTime * lScene->LightSpeed()[2]) * lScene->LightAmplitude()[2] + lScene->LightPos()[2];
      glUniform3fv(lShLightPos, 1, lightPos);
      break;
  }

  glBegin(GL_QUADS);
  glVertex2f(-lRatio, -1.0);
  glVertex2f(lRatio, -1.0);
  glVertex2f(lRatio,  1.0);
  glVertex2f(-lRatio,  1.0);
  glEnd();

  glUseProgram(0);
  glFlush();

  SDL_GL_SwapBuffers();

  GLenum error;
  if ((error = glGetError()) != GL_NO_ERROR)
    cout << gluErrorString(error) << endl;
}
