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

#include <GL/glew.h>

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

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

#include "Maze.h"
#include "Player.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 = NULL;
int g_width = 0;
int g_height = 0;


float4x4 g_projectionMatrix;

float3 g_camPos(-100, 60, 100);
//float3 g_lookAt(-40, 0, 40);
float3 g_lookAt(0, 0, 0);

Timer g_timer;


Maze2D* g_pMaze;

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");
}

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

    g_pFullScreenQuad = new FullScreenQuad();
    g_pMaze           = new Maze2D(10, 10);

    g_timer.start();

    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

}

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;
  }
}

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 & 2)		// left button
  {
    int x1 = x;
    int y1 = y;

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

    input.mx = x;
    input.my = 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;

  Player* pPlayer = dynamic_cast<Player*>(g_pMaze->getPlayer());

  if (pPlayer == NULL)
    RUN_TIME_ERROR("bad cast for 'Player' ");

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

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

  case 'a':
  case 'A':

    break;

  case 'd':
  case 'D':

    break;

  case 'i':
  case 'I':

    break;

  case 'q':
  case 'Q':
    input.turretAngle += 2.5f;
    pPlayer->rotateTurret(input.turretAngle);
    break;

  case 'e':
  case 'E':
    input.turretAngle -= 2.5f;
    pPlayer->rotateTurret(input.turretAngle);
    break;


  case VK_RIGHT:
    if (!pPlayer->inputIsBlocked())
     pPlayer->move(TURN_RIGHT, 1);
    break;

  case VK_LEFT: 
    if (!pPlayer->inputIsBlocked())
      pPlayer->move(TURN_LEFT, 1);
    break;

  case VK_UP:
    if (!pPlayer->inputIsBlocked())
      pPlayer->move(MOVE_FORWARD, 1);
    break;

  case VK_DOWN:
    if (!pPlayer->inputIsBlocked())
      pPlayer->move(MOVE_BACK, 1);
    break;

  }

}

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


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

    glusMultMatrixf(worldView.L(), worldView.L(), model.L()); 	            // ... to get the final model view matrix

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

    std::vector<IGraphicsObject*> drawList = g_pMaze->getDrawList();

    for (size_t i = 0; i < drawList.size(); i++)
      drawList[i]->draw(worldView, g_projectionMatrix);

    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_pFullScreenQuad; g_pFullScreenQuad = NULL;
  delete g_pMaze;    g_pMaze = NULL;
}

/**
* Main entry point.
*/
int main(int argc, char* argv[])
{
  glusInitFunc(init);
  glusReshapeFunc(reshape);
  glusUpdateFunc(update);
  glusTerminateFunc(shutdown);
  glusMouseFunc(mouse);
  glusMouseMoveFunc(mouseMove);
  glusKeyFunc(keyboard);

  glusPrepareContext(3, 0, GLUS_FORWARD_COMPATIBLE_BIT);

  if (!glusCreateWindow("Tank In Hell", 800, 600, 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.3 is supported.
  if (!glewIsSupported("GL_VERSION_3_0"))
  {
    printf("OpenGL 3.0 not supported.");

    glusDestroyWindow();
    return -1;
  }

  glusRun();

  return 0;
}

