#include <string>
#include <iostream>
#include "engine_structs.h"
#include "engineinterface.h"
#include "structures.h"
#include "magic3d.h"
#include "me_time.h"
#include "m3derror.h"
#include <stdlib.h>
#include <GL/glew.h>
#include "mmap.h"
#include <vector>
#include <string.h>

#pragma pack(push, 1)
struct CameraPath
{
	M3D::Coords position;
	M3D::Rotation rotation;
	bool is_loop_start;
	float length;
};
#pragma pack(pop)
std::vector<CameraPath> cam_path_points;

#define LOG_INFO std::cerr
M3D::EngineInterface *engine;
M3D::EngineLimits limits;
M3D::Coords camera_position;
M3D::Coords light_position;
M3D::Rotation camera_rotation;
M3D::Coords water_position;
M3D::Scale base_scale;
Time timer;
Time animation_time;

M3D::CameraInterface camera ( 0, 0 );

#define read_mmap(dest, size) {memcpy(dest, file_pointer, size); file_pointer+=size;}

void scene_3d_init ( float w, float h, float fov )
{
	try
	{
		limits.camera_count = 1;
		limits.indeces_count = 10000;
		limits.mesh_count = 5;
		limits.model_count = 100000;
		limits.shader_count = 5;
		limits.texture_count = 20;
		limits.texture_size = 4096;
		limits.vertices_count = 1024 * 1024;
		limits.material_count = limits.model_count;
		engine = M3D::create_engine ( &limits, ( int ) w, ( int ) h, fov );

		M3D::MMAP mapped_file("res/nyp2010scene.nyps");
		char *file_pointer = mapped_file.get_pointer();
		read_mmap(&base_scale, sizeof(base_scale));
		
		M3D::ModelInterface cube = engine->create_model();
		cube.load("res/models/cube.m3dm");
		M3D::Scale cs(base_scale);
		cs.x_scale*=2.0;
		cs.y_scale*=2.0;
		cs.z_scale*=2.0;
		cube.set_scale(cs);
		M3D::Coords cl;
		cube.set_location(cl);
		engine->add_model(cube);
		
		std::cerr << base_scale.y_scale << std::endl;
		read_mmap ( &camera_position, sizeof ( camera_position ) );
		read_mmap( &camera_rotation, sizeof ( camera_rotation ) );
		read_mmap( &light_position, sizeof ( light_position ) );
		read_mmap( &water_position, sizeof ( water_position ) );

		camera_position.x_pos *= base_scale.x_scale;
		camera_position.y_pos *= base_scale.y_scale;
		camera_position.z_pos *= base_scale.z_scale;
		light_position.x_pos *= base_scale.x_scale;
		light_position.y_pos *= base_scale.y_scale;
		light_position.z_pos *= base_scale.z_scale;
		water_position.x_pos *= base_scale.x_scale;
		water_position.y_pos *= base_scale.y_scale;
		water_position.z_pos *= base_scale.z_scale;

		M3D::ModelInterface ice_plane = engine->create_model();
		ice_plane.load ( "res/models/iceplane.m3dm" );
		ice_plane.set_location ( water_position );
		ice_plane.set_scale ( base_scale );
		engine->add_model ( ice_plane );


		M3D::ModelInterface snow_plane = engine->create_model();
		snow_plane.load ( "res/models/snowplane.m3dm" );
		snow_plane.set_scale ( base_scale );
		engine->add_model ( snow_plane );


		short int base_object_count;
		read_mmap ( &base_object_count, sizeof ( short int ) );
		for ( int a = 0; a < base_object_count;++a )
		{
			short int length;
			read_mmap ( &length, sizeof ( short int ) );
			char path[length];
			read_mmap ( path, length );

			read_mmap ( &length, sizeof ( short int ) );
			char name[length];
			read_mmap ( name, length );

			short int count;
			read_mmap ( &count, sizeof ( short int ) );

			M3D::ModelInterface obj = engine->create_model();
			obj.load ( path );
			for ( int b = 0; b < count; ++b )
			{
				M3D::Coords obj_c;
				M3D::Scale obj_s;
				M3D::Rotation obj_r;
				read_mmap ( &obj_c, sizeof ( M3D::Coords ) );
				read_mmap ( &obj_s, sizeof ( M3D::Scale ) );
				read_mmap ( &obj_r, sizeof ( M3D::Rotation ) );
				obj_c.x_pos *= base_scale.x_scale;
				obj_c.y_pos *= base_scale.y_scale;
				obj_c.z_pos *= base_scale.z_scale;
				obj.set_location ( obj_c );
				obj.set_scale ( obj_s );
				obj.set_rotation ( obj_r );
				engine->add_model ( obj );
				obj = obj.clone();
			}
		}
		short int cam_path_len;
		read_mmap ( &cam_path_len, sizeof ( cam_path_len ) );
		cam_path_points.clear();
		cam_path_points.reserve ( cam_path_len );
		for ( int a = 0; a < cam_path_len; ++a )
		{
			CameraPath p;
			read_mmap ( &p, sizeof ( CameraPath ) );
			p.position.x_pos *= base_scale.x_scale;
			p.position.y_pos *= base_scale.y_scale;
			p.position.z_pos *= base_scale.z_scale;
			cam_path_points.push_back ( p );
		}
		camera_position = cam_path_points[0].position;
		camera_rotation = cam_path_points[0].rotation;

		camera = engine->create_camera();
		M3D::CameraParams params ( fov/* ( ( float ) h / ( float ) w )*/, 0.01, 100.0, ( float ) ( ( float ) w / ( float ) h ) );
		engine->switch_camera ( camera );
		camera.set_rotation ( camera_rotation );
		camera.set_camera_params ( params );
		camera.set_location ( camera_position );

		M3D::LightInterface light = engine->create_light();
		light.set_location ( light_position );
		engine->set_light ( light );

		timer.reset();
		animation_time.reset();
	}
	catch ( M3DError error )
	{
		std::cerr << "====ERROR====" << std::endl;
		std::cerr << error.get_file() << ":" << error.get_line() << " in function \"" << error.get_function() << "\"" << std::endl;
		std::cerr << error.get_reason() << std::endl;
		std::cerr << "=============" << std::endl;
		exit ( 0 );
	}
}
float scene_3d_animate ( void )
{
	float xs, ys, zs, xas, yas, len;
	static unsigned int current_point = 0;
	static unsigned int loop_point = 0;
	static CameraPath p0 = cam_path_points[current_point], p1 = cam_path_points[current_point +1];

	float delta_time = animation_time.get_elapsed_time_noreset();
	if ( delta_time >= p0.length )
	{
		animation_time.reset();
		delta_time = 0;
		current_point++;
		if ( current_point >= cam_path_points.size() ) current_point = loop_point;
		p0 = cam_path_points[current_point];
		p1 = cam_path_points[current_point+1];
		if ( current_point + 1 >= cam_path_points.size() )
			p1 = cam_path_points[loop_point];
		else
			p1 = cam_path_points[current_point+1];
	}

	camera_position = p0.position;
	camera_rotation = p0.rotation;
	xs = ( p1.position.x_pos - p0.position.x_pos ) / p0.length;
	ys = ( p1.position.y_pos - p0.position.y_pos ) / p0.length;
	zs = ( p1.position.z_pos - p0.position.z_pos ) / p0.length;
	xas = ( p1.rotation.x_angle - p0.rotation.x_angle ) / p0.length;
	yas = ( p1.rotation.y_angle - p0.rotation.y_angle ) / p0.length;
	len = p0.length;
	camera_position.x_pos += xs * delta_time;
	camera_position.y_pos += ys * delta_time;
	camera_position.z_pos += zs * delta_time;
	camera_rotation.x_angle += xas * delta_time;
	camera_rotation.y_angle += yas * delta_time;
	if ( p0.is_loop_start ) loop_point = current_point;
	return yas;
}
void scene_3d ( void )
{
	try
	{
		glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
		glEnable ( GL_DEPTH_TEST );
		camera.set_location ( camera_position );
		camera.set_rotation ( camera_rotation );
		engine->draw();

		glActiveTexture ( GL_TEXTURE0 );
	}
	catch ( M3DError error )
	{
		std::cerr << "====ERROR====" << std::endl;
		std::cerr << error.get_file() << ":" << error.get_line() << " in function \"" << error.get_function() << "\"" << std::endl;
		std::cerr << error.get_reason() << std::endl;
		std::cerr << "=============" << std::endl;
		exit ( 0 );
	}

}
