#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include <glut.h> 
#include <cutil.h>

#pragma comment(lib, "RTE.lib")

#ifndef MGML_GUARDIAN
	#include "../CSL/MGML.h"
#endif

#include "../RTE/IGraphicsEngine.h"
#include "../RTE/Common_Graphics_Engine.h"
#include "../RTE/Ray_Tracer.h"
#include "../RTE/GPU_Ray_Tracer.h"
#include "../RTE/OpenGL_Render.h"

#include "Input.h"
#include "Cube.h"
#include "Prism.h"
#include "Camera.h"

using namespace MGML;
//using namespace RAYTR;
using namespace MGML_MATH;
using namespace std;

const int WinWidth = 640;
const int WinHeight = 480;

IGraphicsEngine* pRender;
Input input;  

RAYTR::PointLight light,light2;

const int numMat = 7; 
RAYTR::Material materials[numMat];

// floor triangles
const int numVert = 4;
const int numIndices = 6;
Vertex4f  vert[numVert];
unsigned int indices[numIndices];

// spheres
const int numSpheres = 2;
Sphere4f spheres[numSpheres];

//Mesh model;
Image texture,texture2,texture3;

Camera cam;


/////////////////////////////////////////////////////////////////////////////
void MakeSpheresDemo()
{
  AABB3f box;

  box.vmin.set(-200,0,-200);
  box.vmax.set(200,20,200);

  int N = 200;
  int M = 8;

  for(int i=0;i<N;i++)
  {
    float tx = ((float)i)/((float)(N+1));
    float x = box.vmin[0] + tx*(box.vmax[0] - box.vmin[0]);

    for(int j=0;j<N;j++)
    {
      float tz = ((float)j)/((float)(N+1));
      float z = box.vmin[2] + tz*(box.vmax[2] - box.vmin[2]);

      for(int k=0;k<M;k++)
      {
        float ty = ((float)k)/((float)(M+1));
        float y = box.vmin[1] + ty*(box.vmax[1] - box.vmin[1]);

        float r = 0.48f;
        Sphere4f sphere;
        sphere.pos.set(x,y,z,1);
        sphere.setRadius(r);
        sphere.material_id = (int)rnd(0,5);
        pRender->AddSpheres(&sphere, 1);
      }
    }
  }
}


void InitData()
{
	light.pos = vec3f(-2,2,2);
	light.intensity = 1.5; 
	light.kc = 0.1f;
	light.kl = 0.1f;
	light.kq = 0.008;
	light.color = vec3f(1,1,1);

	light2.pos.set(-2,4,2);
	light2.intensity = 1.0; 
	light2.kc = 0.1f;
	light2.kl = 0.1f;
	light2.kq = 0.008;
	light2.color = vec3f(1,0,0);

	materials[0].ka.set(0.15,0.15,0.15);
	materials[0].kd.set(0.30,0.30,0.30);
	materials[0].ks.set(0.55,0.55,0.55);
	materials[0].k_reflect = 0.5;
	materials[0].k_refract = 0;
	materials[0].n  = 1.0f;
	materials[0].power = 60;

	materials[1] = materials[0];
  materials[1].ka.set(0.0,0.0,0.15);
	materials[1].kd.set(0.0,0.0,0.30);
	materials[1].ks.set(0.0,0.0,0.55);
	materials[1].k_reflect = 0.75;
  materials[1].k_refract = 0.0;
  materials[1].n = 1.0f;

	materials[2] = materials[0];
	materials[2].ka.set(0.15,0,0);
	materials[2].kd.set(0.3,0,0);
	materials[2].ks.set(0.5,0,0);
	materials[2].k_reflect = 0.50;
	//materials[2].k_reflect = 0.25;

	materials[3] = materials[0];
  materials[3].ka.set(0.15,0.0,0.15);
	materials[3].kd.set(0.30,0.0,0.30);
	materials[3].ks.set(0.55,0.0,0.55);
	materials[3].k_reflect = 0.5f;

	materials[4] = materials[0];
	materials[4].ka.set(0.0,0.15,0.0);
	materials[4].kd.set(0.0,0.30,0.0);
	materials[4].ks.set(0.0,0.55,0.0);
	materials[4].k_reflect = 0.35f;
	//materials[4].k_refract = 0.50;
	//materials[4].n = 1.01f;

	materials[5].ka.set(0.20,0.20,0.20);
	materials[5].kd.set(0.35,0.35,0.35);
	materials[5].ks.set(0.55,0.55,0.55);
	materials[5].k_reflect = 0.35;
	materials[5].k_refract = 0.0;
	materials[5].power = 60;


	materials[6].ka.set(0.15,0.15,0);
	materials[6].kd.set(0.30,0.30,0);
	materials[6].ks.set(0.55,0.55,0);
	materials[6].k_reflect = 0.5;
	materials[6].k_refract = 0.0;
  materials[6].n = 1.2f;
	materials[6].power = 60;

	// triangles
	float size = 50;
	vert[0].pos.set(-size,-3,-size,1); vert[0].norm.set(0,1,0,1);
	vert[1].pos.set(-size,-3,size,1);  vert[1].norm.set(0,1,0,1);
	vert[2].pos.set(size,-3,size,1);   vert[2].norm.set(0,1,0,1);
	vert[3].pos.set(size,-3,-size,1);  vert[3].norm.set(0,1,0,1);

	vert[0].material_id = 5;
	vert[1].material_id = 5;
	vert[2].material_id = 5;
	vert[3].material_id = 5;

	// just one texture allowing now - set always texture_id[0]
	// we think about multititexturing if future
	vert[0].texture_id[0] = 0;
	vert[1].texture_id[0] = 0;
	vert[2].texture_id[0] = 0;
	vert[3].texture_id[0] = 0;

	
	float wrap = 16.0f;
	//float wrap = 1.0f;
	vert[0].t.set(0,0); vert[1].t.set(0,wrap);
	vert[2].t.set(wrap,wrap); vert[3].t.set(wrap,0);

	indices[0]=0;indices[1]=1;indices[2]=2;
	indices[3]=2;indices[4]=3;indices[5]=0;

	// spheres
	spheres[0] = Sphere4f(vec4f(1.5,-2,-3,1),1);
	spheres[0].material_id = 4;
	
	spheres[1] = Sphere4f(vec4f(-1,-2,-3,1),1);
	spheres[1].material_id = 1;


	texture.LoadBMP("data/parquet.bmp");
  texture2.LoadBMP("data/texture1.bmp");
  texture3.LoadBMP("data/texture4.bmp");
}

void Init()
{
	try
	{
		InitData();

		pRender = new GPU_Ray_Tracer(WinWidth, WinHeight);
		//pRender = new CPU_Ray_Tracer(WinWidth, WinHeight);
		//pRender = new OpenGL_Render(WinWidth,WinHeight);

		pRender->AddPointLight(light);

		pRender->AddTexture(texture.getConstBuffer(),
							          texture.width(),
							          texture.height(),
							          IGraphicsEngine::RGBA32BIT_TEXTURE_FORMAT);

    for(int i=0;i<numMat;i++)
      pRender->AddMaterial(materials[i]);

		// floor
		pRender->AddTriangles(vert,numVert,indices,numIndices);
		
    //MakeSpheresDemo();

////////////////////////////////////////////////////////////////////////
//////////////////// ###### CHANGE_SCENE ###### ////////////////////////
////////////////////////////////////////////////////////////////////////

    pRender->AddTexture(texture2.getConstBuffer(),
                        texture2.width(),
                        texture2.height(),
                        IGraphicsEngine::RGBA32BIT_TEXTURE_FORMAT);

    pRender->AddTexture(texture3.getConstBuffer(),
                        texture3.width(),
                        texture3.height(),
                        IGraphicsEngine::RGBA32BIT_TEXTURE_FORMAT);
    

		// you can continue in this manner, just don't forget that
		// if you apply matrix, the transform will save forever
    // white and blue sphere
    pRender->AddSpheres(spheres,numSpheres);
		
    // add cubes, yellow sphere
		// insert cubes
		Matrix4x4f t1,r1,r2;
		//t1.Transpose();
		Cube cube;

		//parquet cube
		t1.setTranslate(vec3f(0,-2.5,2));
		cube = Cube(t1,1,0,5); 
		pRender->AddTriangles(cube.GetVertices(),24,cube.GetIndices(),36);
		// level 1,left common textured cube
		t1.Identity(); r1.Identity();
		t1.setTranslate(vec3f(-1.5,-2.5,2));
		r1.setRotationY(45);
		cube = Cube(t1*r1,1,1,6);
		pRender->AddTriangles(cube.GetVertices(),24,cube.GetIndices(),36);

		// red metal cube
		t1.Identity(); r1.Identity();
		t1.setTranslate(vec3f(4,-2.5,0));
		r1.setRotationY(-45);
		cube = Cube(t1*r1,1,2,5); 
		pRender->AddTriangles(cube.GetVertices(),24,cube.GetIndices(),36);

		//level 2, left common cube
		t1.Identity(); r1.Identity();
		t1.setTranslate(vec3f(-0.8,-1.50,2));
		r1.setRotationY(30);
		cube = Cube(t1*r1,1,1,6); 
		pRender->AddTriangles(cube.GetVertices(),24,cube.GetIndices(),36);

		//level 1, right common cube
		t1.Identity(); r1.Identity();
		t1.setTranslate(vec3f(1.5,-2.5,2));
		r1.setRotationY(20);
		cube = Cube(t1*r1,1,1,6); 
		pRender->AddTriangles(cube.GetVertices(),24,cube.GetIndices(),36);

		//level 2, right blue
		t1.Identity(); r1.Identity();
		t1.setTranslate(vec3f(0.6,-1.51,2));
		r1.setRotationY(0);
		cube = Cube(t1*r1,1,255,6); // 255 means no texture
		pRender->AddTriangles(cube.GetVertices(),24,cube.GetIndices(),36);

		// blue mirror cube
		t1.Identity();
		t1.setTranslate(vec3f(0,-0.499,2));
		cube = Cube(t1,1,255,1); 
    //Prism prism(t1*r1,1,1);
    pRender->AddTriangles(cube.GetVertices(), 24, cube.GetIndices(), 36);

		// yellow sphere on the top
		Sphere4f sph;
		sph = Sphere4f(vec4f(0,0.51,2,1),0.5);
		sph.material_id = 6;
		pRender->AddSpheres(&sph,1);

//	sph = Sphere4f(vec4f(1,-0.5,-2,1),0.5);
//	sph.material_id = 5;
//	pRender->AddSpheres(&sph,1);

		cout << "constructing acceleration structures (may take a very long time), please wait...\n";
		pRender->BuildAccelerationStructures();

	}
	catch(std::bad_alloc e)
	{
		string err_msg = "You must construct additional pylons";//NotEnoughMemory();
		ALERT(err_msg);
		cout << err_msg << endl; 
		exit(-1);
	}
	catch(const Error& e)
	{
		cout << "Error: " << typeid(e).name() << endl;
		cout << e.GetMsg() << endl;
		exit(-1);
	}
	catch(...)
	{
		cout << "Unexpected Exeption!" << endl;
		exit(-1);
	}
}

//////////////////////////////////////////////////////////////////////////
////
void ShutDown()
{
  
  try
	{
		err_stream.flush();
		delete pRender;
    pRender = 0;
	}
	catch(std::bad_alloc e)
	{
		string err_msg = "You must construct additional pylons!";
		ALERT(err_msg);
		cout << err_msg << endl; 
		exit(-1);
	}
	catch(const Error& e)
	{
		cout << "Error: " << typeid(e).name() << endl;
		cout << e.GetMsg() << endl;
		exit(-1);
	}
	catch(...)
	{
		cout << "Unexpected Exeption at Exit!" << endl;
		exit(-1);
	}
	exit(0);
}


void Display()
{
	static GLuint OldTick;			// Старое время

  static GLuint FramesCount;		// Счётчик кадров
  static GLuint StartTick;		// Начало отсчёта
	static float FPS = 60;          // Частота кадров в секунду

	static unsigned int counter = 0;

	static float t_process = 1;
	static float omega = 0.5;

	try
	{

		if(input.exit_status) 
			ShutDown();

    OpenGL_Render* pCGE = dynamic_cast<OpenGL_Render*>(pRender);

    if(input.printPerfInfo)
    {
      Ray_Tracer* rt =  dynamic_cast<Ray_Tracer*>(pRender);
      if(rt!=0)
        rt->PrintPerfInfo(std::cout);
    }
		//pRender->SetActiveScene(input.sceneIndex);

    g_debugRay.pos = input.rayPos;
    g_debugRay.dir.set(0,0,1);
    Matrix4x4f mx,my;
    mx.setRotationX(MGML_MATH::DEG_TO_RAD(input.rayRot.x));
    my.setRotationY(MGML_MATH::DEG_TO_RAD(input.rayRot.y));

    t_process += (omega/FPS);

    RAYTR::PointLight light = pRender->GetPointLight(0);
		light.pos.x = 4*sin(t_process);
		light.pos.y = 4;
		light.pos.z = 4*cos(t_process);
		pRender->SetPointLight(light,0);

      
    cam.Move(input.cam_mov);
    cam.SetRotation(input.cam_rot);
    pRender->SetCamMatrix(cam.CalcMatrix());

		pRender->BeginDrawScene(input.trace_depth, input.shadows);

	 	pRender->EndDrawScene();

		glutSwapBuffers();

		input.reset();

	}
	catch(std::bad_alloc e)
	{
		string err_msg = "You must construct additional pylons";
		ALERT(err_msg);
		cout << err_msg << endl; 
		exit(-1);
	}
	catch(const Error& e)
	{
		cout << "Error: " <<typeid(e).name() << endl;
		cout << e.GetMsg() << endl;
		exit(-1);
	}
	catch(...)
	{
		cout << "Unexpected Exeption!" << endl;
		exit(-1);
	}
	
	// -----------------------  алгоритм подсчета FPS. -----------------------
	FramesCount++;
	if(FramesCount > FPS)
	{
		FramesCount=0;
        StartTick = glutGet(GLUT_ELAPSED_TIME);
	}
    char Title[32];
	GLuint st_tick_get = glutGet(GLUT_ELAPSED_TIME);
	if (st_tick_get!=StartTick)
	{
		  FPS = float(FramesCount)*1000/(st_tick_get-StartTick);
			sprintf(Title, "FPS=%.2f", FPS);
			glutSetWindowTitle(Title);
	}
	//\\ -----------------------  алгоритм подсчета FPS. -----------------------
}


void Mouse(int button, int state, int x, int y) { input.Mouse(button,state,x,y); }
void MouseMotion(int x, int y) { input.MouseMotion(x,y); }
void Reshape(int Width,int Height) {  }               //Обработка изменения размеров окна
void Keyboard(unsigned char key,int x,int y){ input.Keyboard(key,x,y); }   //клава
void KeyboardSpecial(int key, int x, int y) { input.KeyboardSpecial(key,x,y);  }   // тоже клава
void Mouse(int button, int state, int x, int y); //Обработка щелчков мыши
void Idle() { glutPostRedisplay(); } 

int main(int argc, char** argv)
{

  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);
	glutInitWindowSize(WinWidth,WinHeight);
	glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-WinWidth)/2, 
		                   (glutGet(GLUT_SCREEN_HEIGHT)-WinHeight)/2);
	glutCreateWindow("NO FATE");
	
	glutDisplayFunc(Display);
	glutKeyboardFunc(Keyboard);
	glutSpecialFunc(KeyboardSpecial);
	glutReshapeFunc(Reshape);
	glutMouseFunc(Mouse);
	glutMotionFunc(MouseMotion);
	glutIdleFunc(Idle);
	
	Init();
  atexit(ShutDown);
			
	glutMainLoop();
}


