#include "opengl31engine.h"
#include "fixed_stack.h"
#include "mesh.h"
#include "texture.h"
#include "model.h"
#include "camera.h"
#include "structures.h"
#include "Interfaces/camerainterface.h"
#include "Interfaces/meshinterface.h"
#include "Interfaces/textureinterface.h"
#include "Interfaces/modelinterface.h"
#include "Interfaces/lightinterface.h"
#include "shaderprogramm.h"
#include "material.h"
#include "materiallayer.h"
#include "openglstate.h"
#include "light.h"
#include <GL/glew.h>
#include <iostream>

#include "m3derror.h"

using namespace M3D;

OpenGL31Engine::OpenGL31Engine()
		: EngineInterface(),
		_capabilities_(),
		_models_ ( 0 ),
		_textures_ ( 0 ),
		_meshes_ ( 0 ),
		_cameras_ ( 0 ),
		_materials_ ( 0 ),
		_model_ids_ ( 0 ),
		_texture_ids_ ( 0 ),
		_mesh_ids_ ( 0 ),
		_camera_ids_ ( 0 ),
		_material_ids_ ( 0 ),
		_materials_to_draw_(),
		_active_camera_ ( 0, 0 ),
		_override_shader_program_ ( 0, 0 ),
		_is_override_shader_program_ ( false ),
		_current_light_ ( 0, 0 )

{
	_capabilities_.gl_shading_language_version = ( char* ) glGetString ( GL_SHADING_LANGUAGE_VERSION );
	_capabilities_.gl_vendor = ( char* ) glGetString ( GL_VENDOR );
	_capabilities_.gl_version = ( char* ) glGetString ( GL_VERSION );
	_capabilities_.gl_renderer = ( char* ) glGetString ( GL_RENDERER );
	_capabilities_.gl_extensions = ( char* ) glGetString ( GL_EXTENSIONS );
	glGetIntegerv ( GL_MAX_TEXTURE_SIZE, &_capabilities_.max_texture_size );
	glGetIntegerv ( GL_MAX_ELEMENTS_VERTICES, &_capabilities_.max_vertices );
	glGetIntegerv ( GL_MAX_ELEMENTS_INDICES, &_capabilities_.max_indeces );
	glGetIntegerv ( GL_POINT_SIZE_MAX_ARB, &_capabilities_.max_point_size );
}

OpenGL31Engine::~OpenGL31Engine()
{}

CameraInterface OpenGL31Engine::create_camera()
{
	return CameraInterface ( _camera_ids_->pop(), this );
}

Camera* OpenGL31Engine::_get_camera ( int camera_id ) const
{
	return &_cameras_[camera_id];
}
Model* OpenGL31Engine::_get_model ( int model_id ) const
{
	return &_models_[model_id];
}
Mesh* OpenGL31Engine::_get_mesh ( int mesh_id ) const
{
	return &_meshes_[mesh_id];
}
Texture* OpenGL31Engine::_get_texture ( int texture_id ) const
{
	return &_textures_[texture_id];
}
Light* OpenGL31Engine::_get_light ( int light_id ) const
{
	return &_lighs_[light_id];
}
ModelInterface OpenGL31Engine::clone_model ( const ModelInterface &model_id )
{
	ModelInterface new_model_id ( _model_ids_->pop(), this );
	Model *new_model = _get_model ( new_model_id );
	Model *old_model = _get_model ( model_id );
	*new_model = *old_model;
	new_model->set_mesh ( old_model->get_mesh(), _get_mesh(old_model->get_mesh()) );
	new_model->set_material ( old_model->get_material_interface(), old_model->get_material() );

	return new_model_id;
}
ModelInterface OpenGL31Engine::create_model( )
{
	ModelInterface model_id ( _model_ids_->pop(), this );
	MeshInterface mesh_id ( _mesh_ids_->pop(), this );
	MaterialInterface material_id ( _material_ids_->pop(), this );

	Model *model = _get_model ( model_id );
	Mesh *mesh = _get_mesh ( mesh_id );
	Material *material = _get_material ( material_id );

	model->set_mesh ( mesh_id, mesh );
	model->set_material ( material_id, material );

	return model_id;
}

void OpenGL31Engine::add_model ( const ModelInterface& obj )
{
	Model *model = _get_model ( obj );
	model->set_drawable ( true );
	if ( _materials_to_draw_.find ( model->get_material() ) == _materials_to_draw_.end() ) _materials_to_draw_.insert ( model->get_material() );
}

void OpenGL31Engine::draw()
{
	glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	std::set<Material*>::iterator it = _materials_to_draw_.begin();

	Camera *camera = _get_camera ( _active_camera_ );

	int programID;
	if ( _is_override_shader_program_ )
	{
		programID = _get_shader ( _override_shader_program_ )->get_shader_id();
		_gl_state_.use_program ( programID );
	}
	//Применим текущий источник света
	Light *light = _get_light ( _current_light_ );
	light->set_light ( _renderer_info_._frame_no_, &_gl_state_ );
	//Пройдемся по списку материалов которые надо рисовать и для каждого из них нарисуем все объекты у которых установлен флаг рисования
	for ( ;it != _materials_to_draw_.end(); ++it )
	{
		( *it )->set_material ( &_gl_state_ );
		camera->set_camera ( &_gl_state_ );

		std::set<Model*> *models = ( *it )->get_associated_models();
		std::set<Model*>::iterator model_it;
		for ( model_it = models->begin(); model_it != models->end(); ++model_it )
		{
			( *model_it )->draw ( _renderer_info_._frame_no_, &_gl_state_ );
		}
	}
	_renderer_info_._frame_no_ ++;
	_gl_state_.bind_vertex_array ( 0 );
	_gl_state_.use_program ( 0 );
}

void OpenGL31Engine::remove_camera ( const CameraInterface& camera )
{
	///@TODO реализовать удаление камеры. Что делать если камера была последней?
}

void OpenGL31Engine::remove_model ( const ModelInterface& obj )
{
	_get_model ( obj )->set_drawable ( false );
	///@TODO реализовать проверку того что его материал более не требует отображения ни одного объекта
}

void OpenGL31Engine::set_limits ( const EngineLimits &limits )
{
	_limits_ = limits;
	_limits_.texture_size = ( _limits_.texture_size > _capabilities_.max_texture_size ) ? _capabilities_.max_texture_size : _limits_.texture_size ;
	_limits_.vertices_count = ( _limits_.vertices_count < _capabilities_.max_vertices ) ? _limits_.vertices_count : _capabilities_.max_vertices;
	_limits_.indeces_count = ( _limits_.indeces_count < _capabilities_.max_indeces ) ? _limits_.indeces_count : _capabilities_.max_indeces;
	///@TODO Ликвидировать это ограничение когда будет более одного источника света.
	_limits_.light_count = 1;
}

void OpenGL31Engine::switch_camera ( const CameraInterface& camera )
{
	_active_camera_ = camera;
}

void OpenGL31Engine::_init_ ( int w, int h, float fov )
{
	_w_ = w;
	_h_ = h;
	_fov_ = fov;
	GLenum err = glewInit();
	if ( err != GLEW_OK ) THROW ( ( char* ) glewGetErrorString ( err ) );
	_gl_state_.fill();

	_models_ = new Model [ _limits_.model_count ];

	Texture::static_init ( _limits_.texture_size );
	_textures_ = new Texture [ _limits_.texture_count ];

	//Фейсов всегда в 3 раза меньше чем индексов, потомучто на 1 фейс нужны 3 вершины
	Mesh::set_limits ( _limits_.vertices_count, _limits_.indeces_count );
	_meshes_ = new Mesh [ _limits_.mesh_count ];

	_cameras_ = new Camera [ _limits_.camera_count ];
	_shaders_ = new ShaderProgram [ _limits_.shader_count ];
	_materials_ = new Material [ _limits_.material_count ];
	_lighs_ = new Light [ _limits_.light_count ];

	_model_ids_ = new fixed_stack<int32_t> ( _limits_.model_count );
	for ( int a = 0;a < _limits_.model_count;++a ) _model_ids_->push ( a );

	_texture_ids_ = new fixed_stack<int32_t> ( _limits_.texture_count );
	for ( int a = 0;a < _limits_.texture_count;++a ) _texture_ids_->push ( a );

	_mesh_ids_ = new fixed_stack<int32_t> ( _limits_.mesh_count );
	for ( int a = 0;a < _limits_.mesh_count;++a ) _mesh_ids_->push ( a );

	_camera_ids_ = new fixed_stack<int32_t> ( _limits_.camera_count );
	for ( int a = 0;a < _limits_.camera_count;++a ) _camera_ids_->push ( a );

	_shader_ids_ = new fixed_stack<int32_t> ( _limits_.shader_count );
	for ( int a = 0;a < _limits_.shader_count;++a ) _shader_ids_->push ( a );

	_material_ids_ = new fixed_stack<int32_t> ( _limits_.material_count );
	for ( int a = 0;a < _limits_.material_count;++a ) _material_ids_->push ( a );

	_light_ids_ = new fixed_stack<int32_t> ( _limits_.light_count );
	for ( int a = 0;a < _limits_.light_count;++a ) _light_ids_->push ( a );


	std::cout << "Renderer info:" << std::endl;
	std::cout << "Screen dimensions:\t" << _w_ << "x" << _h_ << std::endl;
	std::cout << "OpenGL version:\t" << _capabilities_.gl_version << std::endl <<
	          "OpenGL vendor:\t" << _capabilities_.gl_vendor << std::endl <<
	          "Shading Language Version: " << _capabilities_.gl_shading_language_version << std::endl <<
	          "Supported Extesions:\t" << _capabilities_.gl_extensions << std::endl <<
	          "Renderer info:\t" << _capabilities_.gl_renderer << std::endl <<
	          "MaxTextureSize:\t" << _capabilities_.max_texture_size << "x" << _capabilities_.max_texture_size << std::endl <<
	          "MaxPointSize:\t" << _capabilities_.max_point_size << std::endl <<
	          "Max element indices:\t" << _capabilities_.max_indeces << std::endl <<
	          "Max element vertices:\t" << _capabilities_.max_vertices << std::endl;

	glClearColor ( 0, 0, 0, 0 );
	glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	_gl_state_.enable_depth_test();
	_gl_state_.enable_texture_2d();
	_gl_state_.enable_blend();
	glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	glDepthFunc ( GL_LESS );

	glViewport ( 0, 0, ( GLsizei ) ( _w_ ), ( GLsizei ) ( _h_ ) );


}

ShaderProgramInterface OpenGL31Engine::create_shader_program( )
{
	return ShaderProgramInterface ( _shader_ids_->pop(), this );
}

void OpenGL31Engine::remove_shader_override( )
{
	_is_override_shader_program_ = false;
}

void OpenGL31Engine::override_shader_program ( const ShaderProgramInterface & shader_id )
{
	_is_override_shader_program_ = true;
	_override_shader_program_ = shader_id;
}

void OpenGL31Engine::remove_shader_program ( const ShaderProgramInterface & shader_id )
{
	///@TODO реализовать удаление шейдера
}

ShaderProgram * OpenGL31Engine::_get_shader ( int shader_id ) const
{
	return &_shaders_[shader_id];
}

void OpenGL31Engine::set_draw_mode ( DRAW_MODE draw_mode )
{
	switch ( draw_mode )
	{
		case DRAW_TEXTURE:
			_gl_state_.enable_texture_2d();
			glPolygonMode ( GL_FRONT_AND_BACK, GL_FILL );
			break;
		case DRAW_WIREFRAME:
			_gl_state_.disable_texture_2d();
			glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE );
			break;
	}
}

Material * OpenGL31Engine::_get_material ( int material_id ) const
{
	return &_materials_[material_id];
}

MaterialInterface OpenGL31Engine::create_material()
{
	return MaterialInterface ( _material_ids_->pop(), this );
}

TextureInterface OpenGL31Engine::create_texture()
{
	return TextureInterface ( _texture_ids_->pop(), this );
}
LightInterface OpenGL31Engine::create_light()
{
	return LightInterface ( _light_ids_->pop(), this );
}

void OpenGL31Engine::set_light ( const LightInterface &light_id )
{
	_current_light_ = light_id;
}


