#pragma once
#include "../../stdafx.h"
#include "../glLibLocals.h"
#include "../glLibMath/glLibMathMatrix.h"
#include "../glLibMath/glLibMathMisc.h"
#include "../glLibMath/glLibMathRandom.h"
#include "../glLibExtAlgorithm/glLibExtAlgOctree.h"

class glLibExtCamera {
	friend class glLibExtRayTracer;
	private:
		glLibInternal_glLibView* _view;
		bool _complete;
		float _position[3];
		//float _center[3];
		//float _up[3];
		bool _set_position;
		//bool _set_center;
		//bool _set_up;
	public:
		glLibExtCamera() {
			_view = NULL;
			_complete = false;
			_set_position = false;
			//_set_center = false;
			//_set_up = false;
		}
		void set_view( glLibView2D* view) { _view = view; glLibInternal_check_complete(); }
		void set_view( glLibView3D* view) { _view = view; glLibInternal_check_complete(); }
		void set_view(glLibViewISO* view) { _view = view; glLibInternal_check_complete(); }
		void set_vec_position(float x, float y, float z) { float vec[3]; vec[0]=x; vec[1]=y; vec[2]=z; set_vec_position(vec); }
		void set_vec_position(float new_position[3]) { glLibMathVec::copy3(new_position,_position); _set_position = true; glLibInternal_check_complete(); }
		//void   set_vec_center(float x, float y, float z) { float vec[3]; vec[0]=x; vec[1]=y; vec[2]=z;   set_vec_center(vec); }
		//void   set_vec_center(float   new_center[3]) { glLibMathVec::copy3(  new_center,  _center);   _set_center = true; glLibInternal_check_complete(); }
		//void       set_vec_up(float x, float y, float z) { float vec[3]; vec[0]=x; vec[1]=y; vec[2]=z;       set_vec_up(vec); }
		//void       set_vec_up(float       new_up[3]) { glLibMathVec::copy3(      new_up,      _up);       _set_up = true; glLibInternal_check_complete(); }
	private:
		void glLibInternal_check_complete(void) {
			if (_view==NULL) return;
			if (!_set_position) return;
			//if (!_set_center) return;
			//if (!_set_up) return;
			_complete = true;
		}
};

/*template <typename type_float> class glLibExtTransformation {
	friend class glLibExtRayTracerScene;
	private:
		glLibMathMatrix<type_float>* _transform_matrix;
		glLibMathMatrix<type_float>* _result;
	public:
		glLibExtTransformation() {
			_transform_matrix = new glLibMathMatrix<type_float>(4,4,GLLIB_MATRIX_IDENTITY);
			_result = new glLibMathMatrix<type_float>(4,4);
		}
		~glLibExtTransformation() {
			delete _transform_matrix;
			delete _result;
		}
		void transform_position(type_float position[3]) {
			type_float data[4][4] = {{1,0,0,position[0]},{0,1,0,position[1]},{0,0,1,position[2]},{0,0,0,1}};
			glLibMathMatrix<type_float> new_transform_matrix(4,4,data);
			glLibInternal_concatenate_transform(new_transform_matrix);
		}
		void transform_rotation(type_float degrees, type_float rotation[3]) {
			//http://www.opengl.org/sdk/docs/man/xhtml/glRotate.xml
			degrees = radians(degrees);
			type_float s = sin(degrees);
			type_float c = cos(degrees);

			type_float new_rotation[3]; glLibMathVec::normalize(rotation,new_rotation,3);
			type_float x = new_rotation[0];
			type_float y = new_rotation[1];
			type_float z = new_rotation[2];

			type_float data[4][4] = {
				{x*x*(1-c)+  c, x*y*(1-c)-z*s, x*z*(1-c)+y*s, 0},
				{y*x*(1-c)+z*s, y*y*(1-c)+  c, y*z*(1-c)-x*s, 0},
				{z*x*(1-c)-y*s, z*y*(1-c)+x*s, z*z*(1-c)+  c, 0},
				{            0,             0,             0, 1}};
			glLibMathMatrix<type_float> new_transform_matrix(4,4,data);
			glLibInternal_concatenate_transform(new_transform_matrix);
		}
		void transform_scale(type_float scale[3]) {
			type_float data[4][4] = {{scale[0],0,0,0},{0,scale[1],0,0},{0,0,scale[2],0},{0,0,0,1}};
			glLibMathMatrix<type_float> new_transform_matrix(4,4,data);
			glLibInternal_concatenate_transform(new_transform_matrix);
		}
		void transform_transform(glLibExtTransformation<type_float>* transform) {
			glLibInternal_concatenate_transform(transform->_transform_matrix);
		}
	private:
		void glLibInternal_concatenate_transform(glLibMathMatrix<type_float>* new_transform_matrix) {
			glLibMathMatrix<type_float>::multiply(_transform_matrix,new_transform_matrix,_result);
			glLibMathMatrix<type_float>::copy(_result,_transform_matrix);
		}
};*/
class glLibInternal_object_params {
	friend class glLibExtRayTracer;
	private:
		GLfloat _transform[16];
		glLibObject* _object;
public: //TODO remove this line
		glLibMathMatrix<float,char> *_matrix_mv, *_matrix_mvp_inverse;
	public:
		glLibInternal_object_params(glLibObject* object, GLfloat transform[16]) {
			_object = object;
			for (char i=0;i<16;++i) transform[i]=_transform[i]; //glLibMathVec::copy(transform,_transform,16);, but GLLIBMATHVEC_DISALLOW_GENERICS might be #define-d
			_matrix_mv = new glLibMathMatrix<float,char>(4,4, transform, GLLIB_MATRIX_COLUMN);
			_matrix_mvp_inverse = new glLibMathMatrix<float,char>(4,4, GLLIB_MATRIX_EMPTY);
		}
		~glLibInternal_object_params() {
			delete _matrix_mv;
			delete _matrix_mvp_inverse;
		}
};
class glLibExtRayTracerScene {
	friend class glLibExtRayTracer;
	public:
		float bounds[6];
	//private: //TODO
		list<glLibInternal_object_params*> _objects;
	public:
		glLibExtRayTracerScene() {
			for (char i=0;i<6;++i) bounds[i] = 0.0f;
		}
		~glLibExtRayTracerScene() {
			glLibInternal_delete();
		}
		void empty(void) {
			glLibInternal_delete();
		}
		void add_object(glLibObject* object) {
			GLfloat mat[16];
			glGetFloatv(GL_MODELVIEW_MATRIX,mat);
			_objects.push_back(new glLibInternal_object_params(object,mat));
		}
		/*void add_object(glLibObject* object, list<glLibExtTransformation<float>*>* transforms) {
			glLibExtTransformation<float> transform;
			list<glLibExtTransformation<float>*>::const_iterator iter;
			for (iter=transforms->begin(); iter!=transforms->end(); ++iter) {
				transform.transform_transform( (*iter) );
			}
			_objects.push_back(new glLibInternal_object_params(object,transform._transform_matrix));
		}*/
	private:
		void glLibInternal_delete(void) {
			list<glLibInternal_object_params*>::iterator iter;
			for (iter=_objects.begin(); iter!=_objects.end(); ++iter) {
				delete (*iter);
			}
			_objects.clear();
		}
};

class glLibExtRayTracer {
	private:
		glLibExtRayTracerScene* _scene;
		glLibExtCamera* _camera;
		SDL_Surface* _result;
		glLibMathMatrix<float,char>* matrix_pixel;
		glLibMathMatrix<float,char>* matrix_point;

		unsigned long _num_samples;

		glLibExtAlgOctree<unsigned long,float>* octree;
	public:
		glLibExtRayTracer() {
			_scene = NULL;
			_camera = NULL;
			_result = NULL;
			_num_samples = 0;

			matrix_pixel = new glLibMathMatrix<float,char>(4,1,GLLIB_MATRIX_EMPTY);
			matrix_point = new glLibMathMatrix<float,char>(4,1,GLLIB_MATRIX_EMPTY);
		}
		~glLibExtRayTracer() {
			glLibInternal_free_result();

			delete matrix_pixel;
			delete matrix_point;
		}

		void set_scene(glLibExtRayTracerScene* scene) {
			_scene = scene;
		}

		void set_camera(glLibExtCamera* camera) { _camera = camera; }

		void set_sample_parameters(unsigned long num_samples) {
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			if (num_samples==0) throw glLibError("Must have a non-zero number of samples per pixel!");
			#endif
			_num_samples = num_samples;
		}

		const SDL_Surface* render(void) {
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			if (_scene==NULL) throw glLibError("Must have a scene to render!");
			if (_camera==NULL) throw glLibError("Must have a camera to render from!");
			if (!_camera->_complete) throw glLibError("Camera is not completely specified!");
			if (_num_samples==0) throw glLibError("Must set the number of samples to take per pixel!");
			#endif

			/*octree = new glLibExtAlgOctree<float*,float>(
				_scene->bounds[0],_scene->bounds[1],
				_scene->bounds[2],_scene->bounds[3],
				_scene->bounds[4],_scene->bounds[5],
				5); //bounds not set, so this doesn't work*/
			octree = new glLibExtAlgOctree<unsigned long,float>(-1.01,1.01, -1.01,1.01, -1.01,1.01, 3);

			list<glLibInternal_object_params*>::const_iterator iter1;
			for (iter1=_scene->_objects.begin(); iter1!=_scene->_objects.end(); ++iter1) {
				glLibObject* object = (*iter1)->_object;

				for (unsigned long mesh_index=0;mesh_index<object->numof_meshes;++mesh_index) {
					glLibInternal_mesh* mesh = object->meshes[mesh_index];

					unsigned long vpf = mesh->verts_per_face;
					#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
					if (vpf!=3) throw glLibError("An object in the scene contains faces other than triangles!");
					#endif

					GLuint face[3];

					unsigned long num_vertices = mesh->numof_indices;
					unsigned long face_counter = 0;
					for (unsigned long i=0; i<num_vertices; ++i) {
						face[face_counter++] = 3*mesh->vindices[i];

						if (face_counter==3) {
							octree->add_value(object->array_vertices+face[0],to_string(face[0]),face[0]);
							octree->add_value(object->array_vertices+face[1],to_string(face[1]),face[1]);
							octree->add_value(object->array_vertices+face[2],to_string(face[2]),face[2]);
							face_counter = 0;
						}
					}
				}
			}

			glLibInternal_free_result();
			_result = SDL_CreateRGBSurface(0,_camera->_view->width,_camera->_view->height,32,0,0,0,0);

			glLibMathMatrix<float,char> matrix_id(4,4,GLLIB_MATRIX_IDENTITY);
			glLibMathMatrix<float,char> matrix_p(4,4,GLLIB_MATRIX_EMPTY);
			glLibMathMatrix<float,char> matrix_pmv(4,4,GLLIB_MATRIX_EMPTY);
			glLibMathMatrix<float,char> matrix_augmented(4,8,GLLIB_MATRIX_EMPTY);

			_camera->_view->get_matrix_projection(&matrix_p);

			list<glLibInternal_object_params*>::const_iterator scene_object;
			for (scene_object=_scene->_objects.begin(); scene_object!=_scene->_objects.end(); ++scene_object) {
				glLibMathMatrix<float,char>::multiply( &matrix_p, (*scene_object)->_matrix_mv, &matrix_pmv );

				glLibMathMatrix<float,char>::copy( &matrix_pmv, &matrix_augmented, 0,3,0,3, 0,3,0,3 );
				glLibMathMatrix<float,char>::copy(  &matrix_id, &matrix_augmented, 0,3,0,3, 4,7,0,3 );
				
				glLibMathMatrix<float,char>::rref(&matrix_augmented);
				
				glLibMathMatrix<float,char>::copy(&matrix_augmented, (*scene_object)->_matrix_mvp_inverse, 4,7,0,3, 0,3,0,3 );
			}

			map<string,unsigned long*> hit_triangle_indices;

			int width = _camera->_view->width;
			int height = _camera->_view->height;
			for (int y=0;y<height;++y) {
				for (int x=0;x<width;++x) {
					float rf,gf,bf;
					rf = gf = bf = 0.0f;
					for (unsigned long i=0;i<_num_samples;++i) {
						glLibInternal_shoot_ray(x,y, &rf,&gf,&bf, &hit_triangle_indices);
					}
					rf /= _num_samples;
					gf /= _num_samples;
					bf /= _num_samples;
					Uint8 ri = glLibMathMisc::clamp( (Uint8)(glLibMathMisc::rndint(rf)), (Uint8)0, (Uint8)255 );
					Uint8 gi = glLibMathMisc::clamp( (Uint8)(glLibMathMisc::rndint(gf)), (Uint8)0, (Uint8)255 );
					Uint8 bi = glLibMathMisc::clamp( (Uint8)(glLibMathMisc::rndint(bf)), (Uint8)0, (Uint8)255 );
					surface_pixel_set(_result,x,height-y-1,SDL_MapRGB(_result->format,ri,gi,bi));
					
					if ((x==0)&&(y%8==0)) {
						printf("%f done\n",(float)(y*_camera->_view->width+x)/(float)(_camera->_view->width*_camera->_view->height));
					}
				}
			}

			delete octree;

			return _result;
		}
	private:
		void glLibInternal_free_result(void) {
			if (_result!=NULL) {
				SDL_FreeSurface(_result);
				_result = NULL;
			}
		}

		void glLibInternal_shoot_ray(
			int x, int y,
			float* r, float* g, float* b,
			map<string,unsigned long*>* hit_triangle_indices)
		{
			//Classic OpenGL:
			//V' = P*MV*V

			//Ray Tracer:
			//Want V
			
			//So:
			//V' = P*MV*V
			//(P*MV)^-1 * V' = V

			float intersection[3];

			list<glLibInternal_object_params*>::const_iterator iter1;
			for (iter1=_scene->_objects.begin(); iter1!=_scene->_objects.end(); ++iter1) {
				float triangle[3][3];

				glLibObject* object = (*iter1)->_object;
				glLibMathMatrix<float,char>* matrix_mvp_inverse = (*iter1)->_matrix_mvp_inverse;

				GLdouble s[3],e[3],direction[3],w;
				
				matrix_pixel->values[0][0] = 2.0f*(float)(x-_camera->_view->x)/(float)(_camera->_view-> width) - 1.0f;
				matrix_pixel->values[1][0] = 2.0f*(float)(y-_camera->_view->y)/(float)(_camera->_view->height) - 1.0f;
				matrix_pixel->values[3][0] = 1.0f;

				matrix_pixel->values[2][0] = 2.0f*(0.0f) - 1.0f;
				glLibMathMatrix<float,char>::multiply(matrix_mvp_inverse,matrix_pixel,matrix_point);
				w = matrix_point->values[3][0];
				for (char i=0;i<3;++i) s[i]=matrix_point->values[i][0] / w;

				matrix_pixel->values[2][0] = 2.0f*(1.0f) - 1.0f;
				glLibMathMatrix<float,char>::multiply(matrix_mvp_inverse,matrix_pixel,matrix_point);
				w = matrix_point->values[3][0];
				for (char i=0;i<3;++i) e[i]=matrix_point->values[i][0] / w;

				/*printf("Proposed s: "); glLibMathVec::print(s,3);
				printf("Proposed e: "); glLibMathVec::print(e,3);

				GLdouble modelview[16], projection[16];
				glLibMathVec::copy((*iter1)->_transform,modelview,16);
				_camera->_view->get_matrix_projection(projection);
				printf("Modelview (transpose):\n "); glLibMathVec::print(modelview,4);
				printf(" "); glLibMathVec::print(modelview+4,4);
				printf(" "); glLibMathVec::print(modelview+8,4);
				printf(" "); glLibMathVec::print(modelview+12,4);
				printf("Projection (transpose):\n "); glLibMathVec::print(projection,4);
				printf(" "); glLibMathVec::print(projection+4,4);
				printf(" "); glLibMathVec::print(projection+8,4);
				printf(" "); glLibMathVec::print(projection+12,4);
				printf("Inverse MVP:\n");
				matrix_mvp_inverse->print();
				gluUnProject(
					x,y,0.0,
					modelview,projection,_camera->_view->rect,
					&(s[0]),&(s[1]),&(s[2]));
				gluUnProject(
					x,y,1.0,
					modelview,projection,_camera->_view->rect,
					&(e[0]),&(e[1]),&(e[2]));
				printf("Correct s: "); glLibMathVec::print(s,3);
				printf("Correct e: "); glLibMathVec::print(e,3);*/

				glLibMathVec::subtract3(e,s,direction);
				glLibMathVec::normalize3(direction);

				GLfloat direction_float[3];
				glLibMathVec::copy3(direction,direction_float);

				/*for (unsigned long mesh_index=0;mesh_index<object->numof_meshes;++mesh_index) {
					glLibInternal_mesh* mesh = object->meshes[mesh_index];

					unsigned long vpf = mesh->verts_per_face; //already checked above

					GLuint face[3];

					unsigned long num_vertices = mesh->numof_indices;
					unsigned long face_counter = 0;
					for (unsigned long i=0; i<num_vertices; ++i) {
						face[face_counter++] = 3*mesh->vindices[i];
						if (face_counter==3) {
							glLibMathVec::copy<float>(object->array_vertices+face[0],triangle[0],3);
							glLibMathVec::copy<float>(object->array_vertices+face[1],triangle[1],3);
							glLibMathVec::copy<float>(object->array_vertices+face[2],triangle[2],3);
							if ( glLibMathIntersect::ray_triangle(_camera->_position, direction_float, triangle, intersection) ) {
								*r = 255.0;
								return;
							}
							face_counter = 0;
						}
					}
				}*/

				if (false) {//octree->trace_ray(_camera->_position,direction_float,hit_triangle_indices)) {

					for (unsigned long mesh_index=0;mesh_index<object->numof_meshes;++mesh_index) {
						glLibInternal_mesh* mesh = object->meshes[mesh_index];

						GLuint face[3];

						unsigned long num_vertices = mesh->numof_indices;
						unsigned long face_counter = 0;
						for (unsigned long i=0; i<num_vertices; ++i) {
							face[face_counter++] = 3*mesh->vindices[i];
							if (face_counter==3) {
								glLibMathVec::copy3<float>(object->array_vertices+face[0],triangle[0]);
								glLibMathVec::copy3<float>(object->array_vertices+face[1],triangle[1]);
								glLibMathVec::copy3<float>(object->array_vertices+face[2],triangle[2]);
								if ( glLibMathIntersect::ray_triangle(_camera->_position, direction_float, triangle, intersection) ) {
									*r = 255.0;
									return;
								}
								face_counter = 0;
							}
						}
					}

					*r = 255.0;
					hit_triangle_indices->clear();
					return;
				}
			}
		}
};