/////////////////////////////////////////////////////////////////
// main.cpp Author: Vladimir Frolov, 2011, Graphics & Media Lab.
/////////////////////////////////////////////////////////////////

#include <GL/glew.h>

#include "GL/glus.h"
#include "../vsgl3/glHelper.h"
#include "../vsgl3/Timer.h"

#include "radiosity.h"

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <windows.h>


struct MyInput
{
  MyInput()
  {
    cam_rot[0] = cam_rot[1] = cam_rot[2] = cam_rot[3] = 0.f;
    mx = my = 0;
    rdown = ldown = false;
    cam_dist = 20.0f;

    turretAngle = 0.0f;
  }

  int mx;
  int my;
  bool rdown;
  bool ldown;
  float cam_rot[4];
  float cam_pos[4];

  float cam_dist;

  float turretAngle;

}input;

FullScreenQuad* g_pFullScreenQuad = nullptr;
int g_width = 0;
int g_height = 0;

RenderableTexture2D* g_pPatchIdTex = nullptr;

float4x4 g_projectionMatrix;

float3 g_camPos(0, 0, 1.75f);
float3 g_lookAt(0, 0, 0);
float g_camDist = 1.0f;

int g_camMode = 0;

Timer g_timer;

RadiosityScene* g_pScene = nullptr;

void RequreExtentions()
{
  CHECK_GL_ERRORS;

  std::cout << "GPU Vendor: " << glGetString(GL_VENDOR) << std::endl;
  std::cout << "GPU Name  : " << glGetString(GL_RENDERER) << std::endl;
  std::cout << "GL_VER    : " << glGetString(GL_VERSION) << std::endl;
  std::cout << "GLSL_VER  : " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;

  //GlusHelperRequireExt h;
  //h.require("GL_EXT_texture_filter_anisotropic");

}

void initRadiosityScene()
{
  g_pScene = new RadiosityScene;

  const float r = 0.5f;
  const int bigQuadSubdiv = 16;

  // cornell walls
  //
  g_pScene->m_quads.push_back(QuadAA(PlaneType::PLUS_X, float3(-0.5f, 0, 0), float2(0.5f, 0.5f), float3(r, 0, 0), bigQuadSubdiv));
  g_pScene->m_quads.push_back(QuadAA(PlaneType::MINUS_X, float3(0.5f, 0, 0), float2(0.5f, 0.5f), float3(0, r, 0), bigQuadSubdiv));

  g_pScene->m_quads.push_back(QuadAA(PlaneType::PLUS_Y, float3(0.0f, -0.5f, 0), float2(0.5f, 0.5f), float3(r, r, r), bigQuadSubdiv));
  g_pScene->m_quads.push_back(QuadAA(PlaneType::MINUS_Y, float3(0.0f, 0.5f, 0), float2(0.5f, 0.5f), float3(r, r, r), bigQuadSubdiv));

  g_pScene->m_quads.push_back(QuadAA(PlaneType::PLUS_Z, float3(0.0f, 0.0f, -0.5f), float2(0.5f, 0.5f), float3(r, r, r), bigQuadSubdiv));
  g_pScene->m_quads.push_back(QuadAA(PlaneType::MINUS_Z, float3(0.0f, 0.0f, 0.5f), float2(0.5f, 0.5f), float3(0, 0, 0), bigQuadSubdiv));

  // small box inside
  //

  float scale = 0.25f;
  float3 offs(0, -0.25, 0); // (0.20f, -0.30f, -0.15f);

  float3 boxColor = float3(0.25f, 0.25f, 0.01f);

  g_pScene->m_quads.push_back(QuadAA(PlaneType::MINUS_X, offs + scale*float3(-0.5f, 0, 0), scale*float2(0.5f, 0.5f), boxColor, 8));
  g_pScene->m_quads.push_back(QuadAA(PlaneType::PLUS_X, offs + scale*float3(0.5f, 0, 0), scale*float2(0.5f, 0.5f), boxColor, 8));

  g_pScene->m_quads.push_back(QuadAA(PlaneType::MINUS_Y, offs + scale*float3(0.0f, -0.5f, 0), scale*float2(0.5f, 0.5f), boxColor, 8));
  g_pScene->m_quads.push_back(QuadAA(PlaneType::PLUS_Y, offs + scale*float3(0.0f, 0.5f, 0), scale*float2(0.5f, 0.5f), boxColor, 8));

  g_pScene->m_quads.push_back(QuadAA(PlaneType::MINUS_Z, offs + scale*float3(0.0f, 0.0f, -0.5f), scale*float2(0.5f, 0.5f), boxColor, 8));
  g_pScene->m_quads.push_back(QuadAA(PlaneType::PLUS_Z, offs + scale*float3(0.0f, 0.0f, 0.5f), scale*float2(0.5f, 0.5f), boxColor, 8));


  // add light sources
  //
  const float3 lightColor = 20.0f*float3(1, 1, 1);

  for (int x = 6; x <= 9; x++)
    for (int y = 6; y <= 9;y++)
      g_pScene->m_initialEmissivePatches.push_back(EmissivePatch(lightColor, bigQuadSubdiv*bigQuadSubdiv * 3 + bigQuadSubdiv * y + x));

  // go
  //
  std::cout << "subdivide scene ... " << std::endl;
  g_pScene->m_patches = subdivideQuads(g_pScene->m_quads);

  std::cout << "total patches = " << g_pScene->m_patches.size() << std::endl;

  std::ifstream fin("../data/formfactors.image1f", std::ios::binary);

  if (fin.is_open())
  {
    int wh[2];
    fin.read((char*)wh, sizeof(int) * 2);

    if (wh[0] != g_pScene->m_patches.size() || wh[1] != g_pScene->m_patches.size())
    {
      std::cout << "broken form factor file" << std::endl;
      exit(0);
    }

    g_pScene->m_formFactors.resize(wh[0] * wh[1]);
    fin.read((char*)&g_pScene->m_formFactors[0], sizeof(float) * wh[0] * wh[1]);
  }
  else
  {
    g_timer.start();
    std::cout << "compute form factors ... " << std::endl;
    g_pScene->m_formFactors = computeFormFactors(g_pScene->m_patches, g_pScene->m_quads);
    std::cout << "form factors time = " << g_timer.getElapsed()*1000.0f << " ms" << std::endl;

    int w = int(g_pScene->m_patches.size());
    std::ofstream fout("../data/formfactors.image1f", std::ios::binary);
    fout.write((char*)&w, sizeof(int));
    fout.write((char*)&w, sizeof(int));
    fout.write((char*)&g_pScene->m_formFactors[0], sizeof(float)*w*w);
    fout.close();
  }

}


/**
* Function for initialization.
*/
GLUSboolean init(GLUSvoid)
{
  try
  {
    RequreExtentions();

    g_timer.start();
    std::cout << "do radiosity ... " << std::endl;
    std::vector<float3> colors = radiosityBruteForce(g_pScene->m_patches, g_pScene->m_formFactors, g_pScene->m_initialEmissivePatches, 4);

    std::cout << "radiosity time = " << g_timer.getElapsed()*1000.0f << " ms" << std::endl;

    checkFormFactors(g_pScene->m_patches, g_pScene->m_formFactors, int(g_pScene->m_patches.size()));

    for (auto i = 0; i < colors.size(); i++)
      g_pScene->m_patches[i].color = colors[i];

    g_pScene->initGL();
    g_pScene->CopyToGL();

    g_timer.start();

    std::cout << std::endl;
    std::cout << "USER HELP: press 'c' for clear " << std::endl;
    std::cout << "USER HELP: mouse right button for paint " << std::endl;
    std::cout << "USER HELP: mouse left button for paint " << std::endl;
    std::cout << std::endl;

    return GLUS_TRUE;
  }
  catch (std::runtime_error e)
  {
    std::cerr << e.what() << std::endl;
    exit(-1);
  }
  catch (...)
  {
    std::cerr << "Unexpected Exception (init)!" << std::endl;
    exit(-1);
  }
}

GLUSvoid reshape(GLUSuint width, GLUSuint height)
{
  glViewport(0, 0, width, height);
  g_width  = width;
  g_height = height;

  glusPerspectivef(g_projectionMatrix.L(), 45.0f, (GLfloat)width / (GLfloat)height, 1.0f, 100000.0f);  // Calculate the projection matrix

  delete g_pPatchIdTex;
  g_pPatchIdTex = new RenderableTexture2D(GL_R32I, g_width / 2, g_height / 2);
}


void addEmissivePatch(int x, int y)
{
  int patchId = 0;

  if (g_pPatchIdTex != nullptr)
  {
    int w = g_width / 2;
    int h = g_height / 2;

    std::vector<int> tempData(w*h);

    GLuint texId = g_pPatchIdTex->GetColorTexId();
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RED_INTEGER, GL_INT, &tempData[0]); CHECK_GL_ERRORS;

    int x1 = x / 2;
    int y1 = h - y / 2;

    if (x1 >= 0 && x1 < w && y1 >= 0 && y1 < h)
      patchId = tempData[y1*w + x1];
  }

  g_pScene->m_initialEmissivePatches.push_back(EmissivePatch(20 * float3(1, 1, 1), patchId));
}

GLUSvoid mouse(GLUSboolean pressed, GLUSuint button, GLUSuint x, GLUSuint y)
{
  if (!pressed)
    return;

  if (button & 1)// left button
  {
    input.ldown = true;
    input.mx = x;
    input.my = y;
  }

  if (button & 4)	// right button
  {
    input.rdown = true;
    input.mx = x;
    input.my = y;
  }

  if (button & 4)		// left button
    addEmissivePatch(x, y);
}

GLUSvoid mouseMove(GLUSuint button, GLUSint x, GLUSint y)
{
  if (button & 1)		// left button
  {
    int x1 = x;
    int y1 = y;

    input.cam_rot[0] += 0.25f*(y1 - input.my);	// change rotation
    input.cam_rot[1] += 0.25f*(x1 - input.mx);

    input.mx = x;
    input.my = y;
  }

  if ((button & 4) && (button & 1))
    g_pScene->m_initialEmissivePatches.clear();

  if (button & 4)	// left button
    addEmissivePatch(x, y);
}

GLUSvoid keyboard(GLUSboolean pressed, GLUSuint key)
{
  float3 camDir = normalize(g_lookAt - g_camPos);

  static bool secondCall = false;
  static GLUSuint lastKey = 0;

  if (!secondCall)
  {
    secondCall = true;
    return;
  }
  else
    secondCall = false;

  switch (int(key))
  {
    
  case 'w':
  case 'W':
    g_camPos += camDir*1.0f;
    g_camDist -= 0.025f;
    break;

  case 's':
  case 'S':
    g_camPos -= camDir*1.0f;
    g_camDist += 0.025f;
    break;

  case 'c':
  case 'C':
    g_pScene->m_initialEmissivePatches.clear();
    break;

  }


}

GLUSboolean update(GLUSfloat time)
{
  try
  {
    static float elaspedTimeFromStart = 0;
    elaspedTimeFromStart += 10 * time;


    float4x4 model;
    float4x4 modelView;
    glusLoadIdentityf(model.L()); 
    glusRotateRzRyRxf(model.L(), input.cam_rot[0], input.cam_rot[1], 0.0f);
    glusLookAtf(modelView.L(), g_camPos.x, g_camPos.y, g_camPos.z, 
                               0.0f, 0.0f, 0.0f, 
                               0.0f, 1.0f, 0.0f);                           // ... and the view matrix ...

    modelView = modelView*model; 	                                         // ... to get the final model view matrix


    std::vector<float3> colors = radiosityBruteForce(g_pScene->m_patches, g_pScene->m_formFactors, g_pScene->m_initialEmissivePatches, 4);
    for (auto i = 0; i < colors.size(); i++)
      g_pScene->m_patches[i].color = colors[i];

    g_pScene->CopyToGL();

    // make our program current
    //
    glViewport(0, 0, g_width, g_height);
    glClearColor(0.0f, 0.0f, 0.25f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    g_pScene->Draw(modelView, g_projectionMatrix);

    // render patches id image
    //
    glViewport(0, 0, g_width/2, g_height/2);

    g_pPatchIdTex->BeginRenderingToThisTexture();        
    g_pScene->DrawIndices(modelView, g_projectionMatrix);
    g_pPatchIdTex->EndRenderingToThisTexture();           

    return GLUS_TRUE;
  }
  catch (std::runtime_error e)
  {
    std::cerr << e.what() << std::endl;
    exit(-1);
  }
  catch (...)
  {
    std::cerr << "Unexpected Exception(render)!" << std::endl;
    exit(-1);
  }
}

/**
* Function to clean up things.
*/
GLUSvoid shutdown(GLUSvoid)
{
  delete g_pPatchIdTex; g_pPatchIdTex = nullptr;
  delete g_pScene;      g_pScene   = nullptr;

}

/**
* Main entry point.
*/
int main(int argc, char* argv[])
{
  initRadiosityScene();

  glusInitFunc(init);
  glusReshapeFunc(reshape);
  glusUpdateFunc(update);
  glusTerminateFunc(shutdown);
  glusMouseFunc(mouse);
  glusMouseMoveFunc(mouseMove);
  glusKeyFunc(keyboard);

  glusPrepareContext(3, 2, GLUS_FORWARD_COMPATIBLE_BIT);

  if (!glusCreateWindow("Radiosity Demo", 1024, 1024, GLUS_FALSE))
  {
    printf("Could not create window!");
    return -1;
  }

  // Init GLEW
  glewExperimental = GL_TRUE;
  GLenum err = glewInit();
  if (err != GLEW_OK)
  {
    sprintf("glewInitError", "Error: %s\n", glewGetErrorString(err));
    return -1;
  }
  glGetError(); // flush error state variable, caused by glew errors

  // Only continue, if OpenGL 3.2 is supported.
  //
  if (!glewIsSupported("GL_VERSION_3_2"))
  {
    printf("OpenGL 3.2 not supported.");

    glusDestroyWindow();
    return -1;
  }

  glusRun();

  return 0;
}

