#pragma once
#include "../../stdafx.h"
#include "glLibObject.h"

namespace glLib { namespace Object {

class glLibInternal_object_grid : public object {
	public:
		char _dimensions;
	protected:
		unsigned long _size[3];

	protected:
		void glLibInternal_make_permute(const double* start, const double* end, char permute_dim_index, unsigned long frequency) {
			//printf("Index %d, frequency %d\n",permute_dim_index,frequency);
			//printf("start: %f %f  end: %f %f\n",start[0],start[1], end[0],end[1]);
			//printf("start[index]: %f  end[index]: %f\n",start[permute_dim_index],end[permute_dim_index]);

			if (permute_dim_index!=0) {
				//printf("===PUSH===\n");
				glLibInternal_make_permute(start,end, permute_dim_index-1,frequency*_size[permute_dim_index]);
				//printf("===POP===\n");
			}

			unsigned long index = permute_dim_index;
			while (index<numof_vertices*3) {
				for (unsigned long i=0;i<_size[permute_dim_index];++i) {
					GLfloat value =
						(end[permute_dim_index]-start[permute_dim_index])*((double)i/(double)(_size[permute_dim_index]-1)) +
						start[permute_dim_index];
					//printf("Trying to write value %f to index(es) ",value);
					for (unsigned long j=0;j<frequency;++j) {
						//printf("%d ",index);
						array_vertices[index] = value;
						index += 3;
					}
					//printf("\n");
				}
			}
		}
		void glLibInternal_make_swap_axes(char index1, char index2, char axes_indices[]) {
			char temp = axes_indices[index1];
			axes_indices[index1] = axes_indices[index2];
			axes_indices[index2] = temp;

			for (unsigned long i=0;i<numof_vertices;++i) {
				GLfloat temp = array_vertices[3*i+index1];
				array_vertices[3*i+index1] = array_vertices[3*i+index2];
				array_vertices[3*i+index2] = temp;
			}
		}
		void glLibInternal_make_resolve_axes(const glLibFlag* axes) {
			if (axes!=NULL) {
				char axes_indices[3] = {-1,-1,-1};
				for (char i=0;i<_dimensions;++i) {
					if      (axes[i]==GLLIB_AXIS_X) {
						#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
						for (char j=0;j<_dimensions;++j) {
							if (axes_indices[j]==0) throw GLLIB_ERROR_TYPE_ERROR("The axis cannot be specified twice!");
						}
						#endif
						axes_indices[i] = 0;
					}
					else if (axes[i]==GLLIB_AXIS_Y) {
						#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
						for (char j=0;j<_dimensions;++j) {
							if (axes_indices[j]==1) throw GLLIB_ERROR_TYPE_ERROR("The axis cannot be specified twice!");
						}
						#endif
						axes_indices[i] = 1;
					}
					else if (axes[i]==GLLIB_AXIS_Z) {
						#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
						for (char j=0;j<_dimensions;++j) {
							if (axes_indices[j]==2) throw GLLIB_ERROR_TYPE_ERROR("The axis cannot be specified twice!");
						}
						#endif
						axes_indices[i] = 2;
					}
					#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
					else {
						throw GLLIB_ERROR_TYPE_ERROR("Unrecognized axis specifier in axes array, index "+to_string(i)+"!");
					}
					#endif
				}

				if ( axes_indices[0]!=0 && axes_indices[0]!=-1 ) glLibInternal_make_swap_axes(0,1,axes_indices);
				if ( axes_indices[0]!=0 && axes_indices[0]!=-1 ) glLibInternal_make_swap_axes(0,2,axes_indices);
				if (                       axes_indices[1]!= 1 ) glLibInternal_make_swap_axes(1,2,axes_indices);
			}
		}
		void glLibInternal_make_set_dimensions(int dimensions, const unsigned long size[3]) {
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			if (dimensions<1||dimensions>3) {
				throw GLLIB_ERROR_TYPE_ERROR(to_string(dimensions)+"-dimensional grids cannot be constructed!");
			}
			#endif
			_dimensions = dimensions;
			for (char i=0;i<_dimensions;++i) {
				_size[i] = size[i];
			}
		}
		void glLibInternal_make_material(void) {
			numof_materials = 1;
			mtllib["GridMaterial"] = new material();
			meshes[0]->requested_material_name = "GridMaterial";
		}

		void glLibInternal_make_get_vertex_at_index(GLuint* index, unsigned long x, unsigned long y, unsigned long z) {
			if (_dimensions==2) {
				*index = x*_size[1] + y;
			}
			else if (_dimensions==2) {
				*index = x*_size[2]*_size[1] + y*_size[1] + z;
			}
			else {
				throw GLLIB_ERROR_TYPE_ERROR("Not implemented!");
			}
		}

		void glLibInternal_make_grid(const unsigned long* size, const double* start, const double* end, int dimensions, const glLibFlag* axes) {
			glLibInternal_init_start();

			glLibInternal_make_set_dimensions(dimensions,size);

			numof_vertices = 1;
			for (unsigned char i=0;i<_dimensions;++i) {
				numof_vertices *= size[i];
			}

			array_vertices = new GLfloat[3*numof_vertices]; //deleted by superclass
			for (unsigned long i=0;i<3*numof_vertices;++i) {
				array_vertices[i] = 0.0f;
			}
			glLibInternal_make_permute(start,end, _dimensions-1,1);

			numof_meshes = 1;
			glLibInternal_mesh* mesh = new glLibInternal_mesh();
			meshes = new glLibInternal_mesh*[numof_meshes];
			meshes[0] = mesh;
			meshes[0]->verts_per_face = 1;
			meshes[0]->numof_indices = numof_vertices;
			meshes[0]->init();
			for (unsigned long i=0;i<numof_vertices;++i) {
				meshes[0]->vindices[i] = i;
			}

			glLibInternal_make_resolve_axes(axes);

			glLibInternal_make_material();

			glLibInternal_init_end();
		}
		void glLibInternal_make_grid_mesh(const unsigned long* size, const double* start, const double* end, int dimensions, const glLibFlag* axes, const glLibFlagType polygon_type) {
			glLibInternal_init_start();

			glLibInternal_make_set_dimensions(dimensions,size);
			if (_dimensions!=2) throw GLLIB_ERROR_TYPE_ERROR("Meshes for dimensionality "+to_string(_dimensions)+" not implemented!");

			if (_dimensions==2) {
				numof_vertices = _size[0] * _size[1];

				array_vertices = new GLfloat[3*numof_vertices]; //deleted by superclass
				for (unsigned long i=0;i<3*numof_vertices;++i) {
					array_vertices[i] = 0.0f;
				}
				glLibInternal_make_permute(start,end, _dimensions-1,1);

				numof_meshes = 1;
				glLibInternal_mesh* mesh = new glLibInternal_mesh();
				meshes = new glLibInternal_mesh*[numof_meshes];
				meshes[0] = mesh;

				if (polygon_type&GLLIB_TRIANGLE) {
					meshes[0]->verts_per_face = -3;
					meshes[0]->numof_indices = (size[0]-1) * (2*(size[1]-1)+2) + 2*(size[0]-2);
					meshes[0]->init();
					unsigned long index = 0;
					for (unsigned long i=0;i<_size[0]-1;++i) {
						for (unsigned long j=0;j<_size[1];++j) {
							glLibInternal_make_get_vertex_at_index( &(meshes[0]->vindices[index++]),   i, j, 0 );
							glLibInternal_make_get_vertex_at_index( &(meshes[0]->vindices[index++]), i+1, j, 0 );
						}
						if (i!=_size[0]-2) {
							glLibInternal_make_get_vertex_at_index( &(meshes[0]->vindices[index++]), i+1, _size[1]-1, 0 );
							glLibInternal_make_get_vertex_at_index( &(meshes[0]->vindices[index++]), i+1,          0, 0 );
						}
					}
				}
				else if (polygon_type&GLLIB_QUADRILATERAL) {
					meshes[0]->verts_per_face = -4;
					meshes[0]->numof_indices = (size[0]-1) * (2*(size[1]-1)+2) + 4*(size[0]-2);
					meshes[0]->init();
					unsigned long index = 0;
					for (unsigned long i=0;i<_size[0]-1;++i) {
						for (unsigned long j=0;j<_size[1];++j) {
							glLibInternal_make_get_vertex_at_index( &(meshes[0]->vindices[index++]),   i, j, 0 );
							glLibInternal_make_get_vertex_at_index( &(meshes[0]->vindices[index++]), i+1, j, 0 );
						}
						if (i!=_size[0]-2) {
							glLibInternal_make_get_vertex_at_index( &(meshes[0]->vindices[index++]), i+1, _size[1]-1, 0 );
							glLibInternal_make_get_vertex_at_index( &(meshes[0]->vindices[index++]), i+1, _size[1]-1, 0 );
							glLibInternal_make_get_vertex_at_index( &(meshes[0]->vindices[index++]), i+1,          0, 0 );
							glLibInternal_make_get_vertex_at_index( &(meshes[0]->vindices[index++]), i+1,          0, 0 );
						}
					}
				}
				#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
				else throw GLLIB_ERROR_TYPE_ERROR("Unrecognized polygon type!");
				#endif
			}

			glLibInternal_make_resolve_axes(axes);

			glLibInternal_make_material();

			glLibInternal_init_end();
		}
	public:
		glLibInternal_object_grid(void) {}
		~glLibInternal_object_grid(void) {}

		/*for (unsigned long i=0;i<numof_vertices;++i) {
			printf("%f %f %f\n",vertices[3*i],vertices[3*i+1],vertices[3*i+2]);
		}*/
};

class grid : public glLibInternal_object_grid {
	public:
		grid(const unsigned long* size, const double* start, const double* end, int dimensions, const glLibFlag* axes=NULL) {
			glLibInternal_make_grid(size,start,end,dimensions,axes);
		}
};
class grid_mesh : public glLibInternal_object_grid {
	public:
		//TODO: Make a version that supports quads/triangles instead of quad/triangle strips?
		grid_mesh(const unsigned long* size, const double* start, const double* end, int dimensions, const glLibFlag* axes=NULL, const glLibFlagType polygon_type=GLLIB_TRIANGLE) {
			glLibInternal_make_grid_mesh(size,start,end,dimensions,axes,polygon_type);
		}
};

};};