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

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


/*#ifndef NDEBUG
	#pragma comment(lib,"../gpu_rt/Debug/CERF.lib")
#else 
	#ifdef EMU_DEBUG_MODE
		#pragma comment(lib,"../gpu_rt/EmuDebug/CERF.lib")
	#else
		#pragma comment(lib,"../gpu_rt/Release/CERF.lib")
	#endif
#endif*/

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


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

#include "../gpu_rt/IGraphicsEngine.h"
#include "../gpu_rt/Common_Graphics_Engine.h"
#include "../gpu_rt/Ray_Tracer.h"
#include "../gpu_rt/GPU_Ray_Tracer.h"
#include "../gpu_rt/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;  


PointLight light,light2;

const int numMat = 7; 
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;

/////////////////////////////////////////////////////////////////////////////
//// if you want to set rays directly throw Ray_Tracer interface 
Ray3f* g_AxelRays;
vec4f* g_AxelColors;
uint*  g_AxelScreenBuffer;
/////////////////////////////////////////////////////////////////////////////


/////////////////////////////////////////////////////////////////////////////
void InitAxelRays(Ray3f* a_rays)
{
  const float fov = 3.141592654f/(2.0f); // normal mode = PI/4, not PI/2

  for(int x=0;x<WinWidth;x++)
  {
    for(int y=0;y<WinHeight;y++)
    {
      Ray3f& ray = a_rays[x+WinWidth*y];

      ray.pos.set(0,0,0); 

      ray.dir.x() = x - (WinWidth/2);
	    ray.dir.y() = y - (WinHeight/2);
	    ray.dir.z() = -(WinWidth/2.0f)/tanf(fov/2.0f);

      ray.dir.Normalize();
    }
  }

}


/////////////////////////////////////////////////////////////////////////////
////
void AxelDrawResultColor(vec4f* a_color)
{
  uint* screenBuffer = g_AxelScreenBuffer;

  for(int i=0;i<WinWidth*WinHeight;i++)
  {
    vec4f realColor = a_color[i]; // i == x+WinWidth*y

    // just need to cut off big values
    for(int i=0;i<3;i++)
      if(realColor[i] > 1.0f)
        realColor[i] = 1.0f;
    
    // make all scene blue
    realColor.x() = 0;
    realColor.y() = 0;
    screenBuffer[i] = MGML_GRAPHICS::REAL_COLOR_TO_UNT32(realColor);
  }

  // just for test
  glDrawPixels(WinWidth, WinHeight, GL_RGBA, GL_UNSIGNED_BYTE, screenBuffer);
}

/////////////////////////////////////////////////////////////////////////////


void MakeSpheresDemo()
{
  AABB3f box;

  box.vmin.set(-100,1,-100);
  box.vmax.set(100,20,100);


  for(int i=0;i<3000;i++)
  {
    float x = rnd(box.vmin.x(), box.vmax.x());
    float y = rnd(box.vmin.y(), box.vmax.y());
    float z = rnd(box.vmin.z(), box.vmax.z());
    float r = rnd(1.0f, 4.0f);

    Sphere4f sphere;

    sphere.pos.set(x,y,z,1);
    sphere.setRadius(r);
    sphere.material_id = (int)rnd(1,5);

    pRender->AddSpheres(&sphere, 1);

  }
}


extern DLL_API Ray3f g_debugRay; // for debug only
extern DLL_API bool g_debugDrawGrid;

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

	light2.pos().set(-2,4,2,1);
	light2.intensity() = 1.0; 
	light2.kc() = 0.1f;
	light2.kl() = 0.1f;
	light2.kq() = 0.008;
	light2.color() = vec4f(1,0,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;
	materials[0].power = 60;

	materials[1] = materials[0];
  materials[1].ka.set(0.15,0.15,0.15);
	materials[1].kd.set(0.30,0.30,0.30);
	materials[1].ks.set(0.55,0.55,0.55);
	materials[1].k_reflect = 0.00;
  materials[1].k_refract = 0.85;
  materials[1].n = 1.1f;

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

	materials[3] = materials[0];
	materials[3].kd.set(0.30,0.30,0.30);
	materials[3].ks.set(0.55,0.55,0.55);
	materials[3].k_reflect = 0.00f;

	materials[4] = materials[0];
	materials[4].ka.set(0.0,0.15,0.15);
	materials[4].kd.set(0.0,0.30,0.30);
	materials[4].ks.set(0.0,0.55,0.55);
	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.50;
	materials[6].k_refract = 0;
	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;


	//string mesh_file = "data/chair23.3ds";
	//string mesh_file = "data/sphere/sphere_1984.3ds";
	//string mesh_file = "data/diamond.3ds";
	//string mesh_file = "data/cars/4x4.3ds";
	//string mesh_file = "data/cars/lamborgini.3ds";
	//string mesh_file = "data/buildings/ionic_temple.3ds";
	//cout << "loading mesh " + mesh_file + "\n";
	
	//model.LoadFrom3DS(mesh_file); 
	

	// for "data/cars/4x4.3ds"
	Matrix4x4f ms,mr,mr2,mt;
//	ms.setScale(vec3f(0.125,0.125,0.125));
	//mr.setRotationX(DEG_TO_RAD(-90.0f));
	//mr2.setRotationY(DEG_TO_RAD(180.0f));
	//mt.setTranslate(vec4f(0,0,0,1.0f));
  //model.ApplyMatrix(mt*ms*mr*mr2);

  // for ionic_temple
  //mr.setRotationX(DEG_TO_RAD(180.0f));
  //mt.setTranslate(vec4f(0,-3,0,1.0f));
  //model.ApplyMatrix(mt*mr);

  // for sphere
  //ms.setScale(vec4f(0.2,0.2,0.2,1.0f));
  //model.ApplyMatrix(ms);


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

void Init()
{
	try
	{
    /////////////////////////////////////////////////////////////////////////////
    g_AxelRays = new Ray3f[WinWidth*WinHeight];
    g_AxelColors = new vec4f [WinWidth*WinHeight];
    g_AxelScreenBuffer = new uint [WinWidth*WinHeight];
    /////////////////////////////////////////////////////////////////////////////

    InitAxelRays(g_AxelRays);

		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->AddPointLight(light2);

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

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

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

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

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

		// white and blue sphere
		pRender->AddSpheres(spheres,numSpheres);

		// add mesh
		//pRender->AddTriangles(model.pVertices(),model.numVertices(),
			//				            model.pIndices(), model.numIndices());

////////////////////////////////////////////////////////////////////////
//////////////////// ###### CHANGE_SCENE ###### ////////////////////////
////////////////////////////////////////////////////////////////////////
/*
		Matrix4x4f mt;

    int N = 8;
    int M = 8;

    for(float i=(-N/2.0f);i<N/2;i++)
    {
      for(int j =(-M/2.0f);j<M/2;j++)
      {
          float x = 4*(i+0.5f);
          float z = 4*(j+0.5f) - M;
          Mesh model2 = model;

        	mt.setTranslate(vec3f(x,0,z));
		      model2.ApplyMatrix(mt);
		      pRender->AddTriangles(model2.pVertices(),model2.numVertices(),
							                  model2.pIndices(), model2.numIndices());
      }
    }
*/    

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

		// you can continue in this manner, just don't forget that
		// if you apply matrix, the transform will save forever

		// 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,3);
		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,3); 
		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,3); 
		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,4); // 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(prism.GetVertices(), Prism::VERT_NUM, prism.GetIndices(), Prism::INDEX_NUM);

		// 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()
{
  static bool first = true;
  if(!first)
    return;
  
  try
	{
    /////////////////////////////////////////////////////////////////////////
    delete [] g_AxelRays;
    delete [] g_AxelColors;
    delete [] g_AxelScreenBuffer;
    /////////////////////////////////////////////////////////////////////////

		err_stream.flush();
		delete pRender;
    pRender = 0;
    first = false;
	}
	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()));

    g_debugRay.dir = (my*mx)*g_debugRay.dir;
    g_debugRay.dir.Normalize();
    g_debugDrawGrid = input.drawAllGrid;

    t_process += (omega/FPS);

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

    //Matrix4x4f camMatrix = CalcCamMatrix(input.cam_pos,input.cam_rot);
    //Matrix4x4f camMatrix(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);
    //pRender->SetCamMatrix(camMatrix);
    //pRender->SetCamPosition(input.cam_pos);
      
    cam.Move(input.cam_mov);
    cam.SetRotation(input.cam_rot);
    pRender->SetCamMatrix(cam.CalcMatrix());

    
    //////////////////////////////////////////////////////////////////////// for Axel
    /*Ray_Tracer* pRayTracer = dynamic_cast<Ray_Tracer*>(pRender);
    if(pRayTracer != 0)
    {
     pRayTracer->BeginDrawScene(g_AxelRays, WinWidth*WinHeight, 
                                input.trace_depth, input.shadows);


     pRayTracer->GetColorsToBuffer(g_AxelColors, WinWidth*WinHeight);
     AxelDrawResultColor(g_AxelColors);
    }*/
     //////////////////////////////////////////////////////////////////////// for Axel

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

    //glDisable(GL_TEXTURE_2D);
	  //glDisable(GL_LIGHTING);
    //if(pCGE!=0)
    //  pCGE->DebugDrawAccelStruct();
    //glEnable(GL_LIGHTING);
	  //glEnable(GL_TEXTURE_2D);

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


