#ifdef HAVE_CONFIG_H
#include <conf_config.h>
#endif
#ifdef WIN32
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#endif
#include <GL/glew.h>

#include "video_GL_glfw.h"
#include "storage.h"
#include "config.h"
#include "event.h"
#include <string>
#include <iostream>
#include <list>
#include <string.h>

#include <GL/glfw.h>

#define LOG_INFO std::cout

#ifdef HAVE_CONFIG_H
#include <conf_config.h>
#endif

#ifdef Revision
#undef Revision
#endif
#define Revision "video_GL_glfw.cpp $Revision: 1.12 $"
///@TODO: Нормальтный ивент хендлинг
bool windowclosed=false;
std::list<Event > events;
///@TODO: Убрать этот грязный хак!!!!!
OpenGL_Video *vid;

int convert_key(int key)
{
    switch (key)
    {
        case GLFW_KEY_SPACE: return KEY_SPACE;
        case GLFW_KEY_SPECIAL: return KEY_SPECIAL;
        case GLFW_KEY_ESC: return KEY_ESC;
        case GLFW_KEY_F1: return KEY_F1;
        case GLFW_KEY_F2: return KEY_F2;
        case GLFW_KEY_F3: return KEY_F3;
        case GLFW_KEY_F4: return KEY_F4;
        case GLFW_KEY_F5: return KEY_F5;
        case GLFW_KEY_F6: return KEY_F6;
        case GLFW_KEY_F7: return KEY_F7;
        case GLFW_KEY_F8: return KEY_F8;
        case GLFW_KEY_F9: return KEY_F9;
        case GLFW_KEY_F10: return KEY_F10;
        case GLFW_KEY_F11: return KEY_F11;
        case GLFW_KEY_F12: return KEY_F12;
        case GLFW_KEY_F13: return KEY_F13;
        case GLFW_KEY_F14: return KEY_F14;
        case GLFW_KEY_F15: return KEY_F15;
        case GLFW_KEY_F16: return KEY_F16;
        case GLFW_KEY_F17: return KEY_F17;
        case GLFW_KEY_F18: return KEY_F18;
        case GLFW_KEY_F19: return KEY_F19;
        case GLFW_KEY_F20: return KEY_F20;
        case GLFW_KEY_F21: return KEY_F21;
        case GLFW_KEY_F22: return KEY_F22;
        case GLFW_KEY_F23: return KEY_F23;
        case GLFW_KEY_F24: return KEY_F24;
        case GLFW_KEY_F25: return KEY_F25;
        case GLFW_KEY_UP: return KEY_UP;
        case GLFW_KEY_DOWN: return KEY_DOWN;
        case GLFW_KEY_LEFT: return KEY_LEFT;
        case GLFW_KEY_RIGHT: return KEY_RIGHT;
        case GLFW_KEY_LSHIFT: return KEY_LSHIFT;
        case GLFW_KEY_RSHIFT: return KEY_RSHIFT;
        case GLFW_KEY_LCTRL: return KEY_LCTRL;
        case GLFW_KEY_RCTRL: return KEY_RCTRL;
        case GLFW_KEY_LALT: return KEY_LALT;
        case GLFW_KEY_RALT: return KEY_RALT;
        case GLFW_KEY_TAB: return KEY_TAB;
        case GLFW_KEY_ENTER: return KEY_ENTER;
        case GLFW_KEY_BACKSPACE: return KEY_BACKSPACE;
        case GLFW_KEY_INSERT: return KEY_INSERT;
        case GLFW_KEY_DEL: return KEY_DEL;
        case GLFW_KEY_PAGEUP: return KEY_PAGEUP;
        case GLFW_KEY_PAGEDOWN: return KEY_PAGEDOWN;
        case GLFW_KEY_HOME: return KEY_HOME;
        case GLFW_KEY_END: return KEY_END;
        case GLFW_KEY_KP_0: return KEY_KP_0;
        case GLFW_KEY_KP_1: return KEY_KP_1;
        case GLFW_KEY_KP_2: return KEY_KP_2;
        case GLFW_KEY_KP_3: return KEY_KP_3;
        case GLFW_KEY_KP_4: return KEY_KP_4;
        case GLFW_KEY_KP_5: return KEY_KP_5;
        case GLFW_KEY_KP_6: return KEY_KP_6;
        case GLFW_KEY_KP_7: return KEY_KP_7;
        case GLFW_KEY_KP_8: return KEY_KP_8;
        case GLFW_KEY_KP_9: return KEY_KP_9;
        case GLFW_KEY_KP_DIVIDE: return KEY_KP_DIVIDE;
        case GLFW_KEY_KP_MULTIPLY: return KEY_KP_MULTIPLY;
        case GLFW_KEY_KP_SUBTRACT: return KEY_KP_SUBTRACT;
        case GLFW_KEY_KP_ADD: return KEY_KP_ADD;
        case GLFW_KEY_KP_DECIMAL: return KEY_KP_DECIMAL;
        case GLFW_KEY_KP_EQUAL: return KEY_KP_EQUAL;
        case GLFW_KEY_KP_ENTER: return KEY_KP_ENTER;
        default: return key;
    }
}

void CharEvent ( int character, int action )
{
	if ( action==GLFW_PRESS )
	{
                Event evt(CHARACTER);
                evt._character = character;
		events.push_back ( evt );
	}
}
void KeyEvent ( int key, int action )
{
        Event evt( action == GLFW_PRESS ? KEYBOARD_PRESS : KEYBOARD_RELEASE );
        evt._key = convert_key( key );
	events.push_back ( evt );
}
void MouseMoveEvent( int xn, int yn )
{
    Event evt (MOUSE_MOVE);
    static double x_old = 0.0, y_old = 0.0;
    double x, y;
    vid->_get_mouse_position ( &x, &y );
    evt._x = x;
    evt._y = y;
    evt._dx = x - x_old;
    evt._dy = y - y_old;
    x_old = x;
    y_old = y;
    events.push_back ( evt );
}
void MouseButtonEvent ( int button, int action )
{
	MOUSE_BUTTON mb;
	switch ( button )
	{
		case GLFW_MOUSE_BUTTON_LEFT:
			mb=LBUTTON;
			break;
		case GLFW_MOUSE_BUTTON_RIGHT:
			mb=RBUTTON;
			break;
		case GLFW_MOUSE_BUTTON_MIDDLE:
			mb=MBUTTON;
			break;
		default:
			mb=LBUTTON;
	}
        Event evt( action == GLFW_PRESS ? MOUSE_PRESS : MOUSE_RELEASE );
        static double x_old = 0.0, y_old = 0.0;
        double x, y;
	vid->_get_mouse_position ( &x, &y );
        evt._x = x;
        evt._y = y;
        evt._dx = x - x_old;
        evt._dy = y - y_old;
        x_old = x;
        y_old = y;
        evt._button = mb;
	events.push_back ( evt );
}
int WindowCloseEvent()
{
	events.push_back ( Event ( EXIT ) );
	return GL_FALSE;
}

std::string storage_name;
std::string storage_prefix;
typedef Storage<std::string, Storage<std::string, Video_Surface*>* > _Surface_Group_Map_;
typedef Storage<std::string, Storage<std::string, Video_Sprite*>* > _Sprite_Group_Map_;
typedef Storage<std::string, Storage<std::string, Video_Particles*>* > _Particles_Group_Map_;
typedef Storage<std::string, Video_Surface*> _Surface_Map_;
typedef Storage<std::string, Video_Sprite*> _Sprite_Map_;
typedef Storage<std::string, Video_Particles*> _Particles_Map_;
typedef std::vector<Video_Sprite*> _Sprites_Draw_List_;
typedef std::vector<Video_Particles*> _Particles_Draw_List_;

Video_Interface* _get_video_driver ( std::string * params, int params_num )
{
	if ( params_num < 2 )
		return 0;
	storage_name = params[ 0 ];
	storage_prefix = params[ 1 ];
	Config_Interface *intf = config_get_interface ( storage_name );
	if ( !intf ) return 0;
	return new OpenGL_Video;
}
template <typename A>
struct clean1
{
	void operator() ( A object )
	{
		std::cerr<<"Deiniting object:"<<object.first<<std::endl;
		delete object.second;
	}
};
template <typename A, typename B>
struct clean
{
	clean1<B> clean_1;
	void operator() ( A object )
	{
		std::cerr<<"Deiniting object:"<<object.first<<std::endl;
		object.second->for_each ( clean_1 );
		delete object.second;
	}
};

bool OpenGL_Video::_deinit()
{
	clean<std::pair<std::string, Storage<std::string, Video_Surface*>* >, std::pair<std::string, Video_Surface*> > cleaner_surf;
	_surfaces_.for_each ( cleaner_surf );
	clean<std::pair<std::string, Storage<std::string, Video_Sprite*>* >, std::pair<std::string, Video_Sprite*> > cleaner_spr;
	_sprites_.for_each ( cleaner_spr );
	clean<std::pair<std::string, Storage<std::string, Video_Particles*>* >, std::pair<std::string, Video_Particles*> > cleaner_par;
	_particles_.for_each ( cleaner_par );
	glfwCloseWindow();
	glfwTerminate();
	return true;
}

bool OpenGL_Video::_init()
{
	Config_Interface *intf = config_get_interface ( storage_name );
	if ( !intf )
	{
		_error = "Can`t get config interface";
		return false;
	}

	_w_ = atoi ( intf->_get_value ( storage_prefix + "Width", "800" ).c_str() );
	_h_ = atoi ( intf->_get_value ( storage_prefix + "Height", "600" ).c_str() );
	_DoubleBuffer_ = ( intf->_get_value ( storage_prefix + "DoubleBuffer", "yes" ) == "yes" ) ? true : false;
	_FullScreen_ = ( intf->_get_value ( storage_prefix + "FullScreen", "yes" ) == "yes" ) ? true : false;
	_mouse_ = ( intf->_get_value ( storage_prefix + "HideMouse", "yes" ) == "yes" ) ? false : true;
	_caption_=intf->_get_value ( storage_prefix + "Caption", std::string ( "OpenGL video driver Version: " ) +VERSION );
	_base_h_=atoi ( intf->_get_value ( storage_prefix + "BaseHeight", "600" ).c_str() );
	_base_scale_= ( float ) _h_/ ( float ) _base_h_;
	_use_vbo_= ( intf->_get_value ( storage_prefix + "VBO", "yes" ) == "yes" ) ? true : false;
	_use_point_sprite_ = ( intf->_get_value ( storage_prefix + "PointSprite", "yes" ) == "yes" ) ? true : false;
	_force_run_without_non_pow2_textures_= ( intf->_get_value ( storage_prefix + "ForceNonPow2", "no" ) == "yes" ) ? true : false;

	LOG_INFO << "_w_\t=" << _w_ << std::endl;
	LOG_INFO << "_h_\t=" << _h_ << std::endl;
	LOG_INFO << "_DoubleBuffer_\t=" << _DoubleBuffer_ << std::endl;
	LOG_INFO << "_FullScreen_\t=" << _FullScreen_ << std::endl;

	if ( glfwInit() !=GL_TRUE )
	{
		_error="Unable to init GLFW";
		return false;
	}
	int ret;
	
	ret=glfwOpenWindow ( _w_, _h_, 8, 8, 8, 8, 16, 0, ( _FullScreen_ ) ?GLFW_FULLSCREEN:GLFW_WINDOW );
	if ( ret!=GL_TRUE ) 
	{
		_error="Unable to create window";
		return false;
	}
	glfwSetWindowTitle ( _caption_.c_str() );
	if ( !_mouse_ ) glfwDisable ( GLFW_MOUSE_CURSOR );
	glfwEnable ( GLFW_KEY_REPEAT );

//	mogulBegin ( flags, _caption_.c_str(), -_w_, -_h_ );
	GLenum err = glewInit();
	if ( err != GLEW_OK )
	{
		_error = (char*)glewGetErrorString ( err );
		return false;
	}
	GLint texts;
	glGetIntegerv ( GL_MAX_TEXTURE_SIZE, &texts );
	LOG_INFO << "OpenGL info:" << std::endl <<
	"OpenGL version: " << glGetString ( GL_VERSION ) << std::endl <<
	"Vendor:: " << glGetString ( GL_VENDOR ) << std::endl <<
	"Supported Extesions: " << glGetString ( GL_EXTENSIONS ) << std::endl <<
	"Shading Language Version: " << glGetString( GL_SHADING_LANGUAGE_VERSION ) << std::endl <<
	"Renderer info: " << glGetString ( GL_RENDERER ) << std::endl;
	LOG_INFO << "MaxTextureSize " << texts << std::endl;
	glGetIntegerv ( GL_MAX_ELEMENTS_VERTICES, ( GLint* ) &_max_element_vertices_ );
	glGetIntegerv ( GL_MAX_ELEMENTS_INDICES, ( GLint* ) &_max_element_indeces_ );
	glGetIntegerv ( GL_POINT_SIZE_MAX_ARB, ( GLint* ) &_max_point_size_ );
	LOG_INFO << "MaxPointSize "<<_max_point_size_<<std::endl;
	LOG_INFO << "Max element vertices "<<_max_element_vertices_<<std::endl;
	LOG_INFO << "Max element indeces "<<_max_element_indeces_<<std::endl;
	LOG_INFO<<"\n\nChecking required extensions:"<<std::endl;
	bool state=true;
	if ( _use_vbo_ )
	{
		LOG_INFO<<"GL_ARB_vertex_buffer_object:";
		if ( !strstr ( ( char* ) glGetString ( GL_EXTENSIONS ), "GL_ARB_vertex_buffer_object" ) )
		{
			LOG_INFO<<"no"<<std::endl;
			state=true;
			_use_vbo_=false;
		}
		else LOG_INFO<<" yes"<<std::endl;
	}
	else
	{
		LOG_INFO<<"VBO Disabled. General perfomance will be lost."<<std::endl;
	}
	if ( _use_point_sprite_ )
	{
		LOG_INFO<<"GL_ARB_point_sprite:";
		if ( !strstr ( ( char* ) glGetString ( GL_EXTENSIONS ), "GL_ARB_point_sprite" ) )
		{
			LOG_INFO<<"no"<<std::endl;
			state=true;
			_use_point_sprite_=false;
		}
		else LOG_INFO<<" yes"<<std::endl;
	}
	else
	{
		LOG_INFO<<"PointSprites Disabled. General effects perfomance will be lost."<<std::endl;
	}
	LOG_INFO<<"GL_ARB_texture_non_power_of_two:";
	if ( !strstr ( ( char* ) glGetString ( GL_EXTENSIONS ), "GL_ARB_texture_non_power_of_two" ) )
	{
		if ( !_force_run_without_non_pow2_textures_ )
		{
			LOG_INFO<<"no"<<std::endl;
			_error = "GL_ARM_texture_nun_power_of_two extension is missed. You may disable this test by setting \"ForceNonPow2\" to state \"yes\" in config file";  
			state=false;
			
		}
		else
		{
			LOG_INFO<<"NO! But running was forced by config! You may see white boxes and other bad things!"<<std::endl;
		}

	}
	else LOG_INFO<<" yes"<<std::endl;
	if ( !state )
	{
		glfwCloseWindow();
		return false;
	}

	glfwSetWindowCloseCallback ( &WindowCloseEvent );

	glEnable ( GL_TEXTURE_2D );
	if ( _use_point_sprite_ )
		glEnable ( GL_POINT_SPRITE_ARB );

	_GL_State.gl_blend=false;
	glDisable ( GL_BLEND );
	glShadeModel ( GL_SMOOTH );
	glHint ( GL_LINE_SMOOTH_HINT, GL_NICEST );
	//		glViewport ( 0, 0, _w_, _h_ );

	glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	glClearColor ( 0, 0, 0, 0 );
	glClear ( GL_COLOR_BUFFER_BIT );

	return true;
}
OpenGL_Video::~ OpenGL_Video( )
{
	glfwCloseWindow();
	glfwTerminate();
//	mogulEnd();
}

OpenGL_Video::OpenGL_Video( ) :_mouse_pointer_name_ ( "" ), _mouse_pointer_group_ ( "" )
{
	_error = "";
	vid=this;
}
void OpenGL_Video::_draw_sprites()
{
	_Sprites_Draw_List_::iterator it;
	Video_Sprite *spr, *cur_spr=0;
	OpenGL_Surface *surf;
	std::vector<std::pair<std::string, std::string> >::iterator sps_it;
	std::vector<Video_Sprite*>::iterator sps_p_it;
	int num=0;
	int blend_switch=0;
	int textures_count=0;
	int begins=0;
	for ( it=_sprites_draw_list_.begin();it!=_sprites_draw_list_.end();++it )
	{
		spr=*it;
		if ( spr->_drawed_ || !spr->_need_draw_ ) continue;
		surf=dynamic_cast<OpenGL_Surface*> ( _get_surface ( spr->_surface_name_, spr->_surface_group_ ) );
		if ( !surf ) continue;
		if ( surf->_need_update_ ) surf->_update_();
		glBindTexture ( GL_TEXTURE_2D, surf->_tex_id_ );
		textures_count++;
		if ( surf->_get_alpha_status() ==Video_Surface::ALPHA_CHANNEL )
		{
			if ( !_GL_State.gl_blend ) {glEnable ( GL_BLEND ); _GL_State.gl_blend=true;blend_switch++;}
		}
		else
		{
			if ( _GL_State.gl_blend ) {glDisable ( GL_BLEND ); _GL_State.gl_blend=false;blend_switch++;}
		}
		glBegin ( GL_QUADS );
		begins++;
//		for(sps_it=surf->_sprites_.begin();sps_it!=surf->_sprites_.end();++sps_it)
		for ( sps_p_it=surf->_sprites_p_.begin();sps_p_it!=surf->_sprites_p_.end();++sps_p_it )
		{
//			cur_spr=_get_sprite(sps_it->first, sps_it->second );
			cur_spr=*sps_p_it;
			if ( !cur_spr ) continue;
			if ( cur_spr->_drawed_ || !cur_spr->_need_draw_ ) continue;
			double w, h, x,y,x1,y1;
			w=2.* ( double ) cur_spr->_w_/ ( double ) _w_;
			h=2.* ( double ) cur_spr->_h_/ ( double ) _h_;
			x=(cur_spr->_x_-cur_spr->_base_x_)*_base_scale_;
			y=(cur_spr->_y_-cur_spr->_base_y_)*_base_scale_;
			x1=x+w* ( cur_spr->_scale_x_*_base_scale_ );
			y1=y+h* ( cur_spr->_scale_y_*_base_scale_ );


			glColor4f ( cur_spr->_Rblend_, cur_spr->_Gblend_, cur_spr->_Bblend_,cur_spr->_Ablend_ );
//			glRotatef ( cur_spr->_rotation_[0],1,0,0 );
//			glRotatef ( cur_spr->_rotation_[1],0,1,0 );
//			glRotatef ( cur_spr->_rotation_[2],0,0,1 );

			glTexCoord2d ( 0, 0 );
			glVertex2f ( x, y );

			glTexCoord2d ( 0, 1 );
			glVertex2f ( x, y1 );

			glTexCoord2d ( 1, 1 );
			glVertex2f ( x1, y1 );

			glTexCoord2d ( 1, 0 );
			glVertex2f ( x1, y );

			cur_spr->_drawed_=true;
			cur_spr->_need_draw_=false;
			num++;
		}
		glEnd();
	}

//	glPopMatrix();
//	std::cerr<<"Textures: "<<textures_count<<" Blend switch:"<<blend_switch<<" Sprites:"<<num<<" glBegin:"<<begins<<std::endl;

}
void OpenGL_Video::_draw_particles(const std::vector<Video_Particles*> &list)
{
	glPushMatrix();
	glLoadIdentity();

	_Particles_Draw_List_::const_iterator it;
	OpenGL_Particles *par;
	if ( _use_vbo_ )
	{
		glEnableClientState ( GL_VERTEX_ARRAY );
		glEnableClientState ( GL_COLOR_ARRAY );
		if ( !_use_point_sprite_ )glEnableClientState ( GL_TEXTURE_COORD_ARRAY );

	if ( !_GL_State.gl_blend ) {glEnable ( GL_BLEND ); _GL_State.gl_blend=true;}
		for ( it=list.begin();it!=list.end();++it )
		{
			par=dynamic_cast<OpenGL_Particles*> ( *it );
			if ( _use_point_sprite_ ) glPointSize ( par->_size_ );
			if ( par->_have_texture_ )
			{
				OpenGL_Surface *surf;
				surf=dynamic_cast<OpenGL_Surface*> ( _get_surface ( par->_surface_name_, par->_surface_group_ ) );
				if ( surf->_need_update_ ) surf->_update_();
				glBindTexture ( GL_TEXTURE_2D, surf->_tex_id_ );
				if ( _use_point_sprite_ )
				{
					glTexEnvf ( GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE );
					glPointParameterfARB ( GL_POINT_FADE_THRESHOLD_SIZE_ARB, 4.0f );
					glPointParameterfARB ( GL_POINT_SIZE_MIN_ARB, 1.0f );
					glPointParameterfARB ( GL_POINT_SIZE_MAX_ARB, _max_point_size_ );
				}

			}

			if ( !_use_point_sprite_ )
			{
				glBindBuffer ( GL_ARRAY_BUFFER, par->_c_buf_id_ );
				glBufferSubData ( GL_ARRAY_BUFFER, 0, par->_count_*4*4*sizeof ( GLfloat ), par->_colors_ );
				glColorPointer ( 4, GL_FLOAT, 0, 0 );

				glBindBuffer ( GL_ARRAY_BUFFER, par->_t_buf_id_ );
				glBufferSubData ( GL_ARRAY_BUFFER, 0, par->_count_*8*sizeof ( GLfloat ), par->_texture_ );
				glTexCoordPointer ( 2, GL_FLOAT, 0, 0 );

				glBindBuffer ( GL_ARRAY_BUFFER, par->_p_buf_id_ );
				glBufferSubData ( GL_ARRAY_BUFFER, 0, par->_count_*8*sizeof ( GLfloat ), par->_particles_ );
				glVertexPointer ( 2, GL_FLOAT, 0, 0 );

				glDrawArrays ( GL_QUADS, 0, par->_count_*4 );
			}
			else
			{
				glBindBuffer ( GL_ARRAY_BUFFER, par->_c_buf_id_ );
				glBufferSubData ( GL_ARRAY_BUFFER, 0, par->_count_*4*sizeof ( GLfloat ), par->_colors_ );
				glColorPointer ( 4, GL_FLOAT, 0, 0 );

				glBindBuffer ( GL_ARRAY_BUFFER, par->_p_buf_id_ );
				glBufferSubData ( GL_ARRAY_BUFFER, 0, par->_count_*2*sizeof ( GLfloat ), par->_particles_ );
				glVertexPointer ( 2, GL_FLOAT, 0, 0 );

				glDrawArrays ( GL_POINTS, 0, par->_count_ );
			}

		}
		glTexEnvf ( GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_FALSE );
	}
	else
	{
		if ( !_GL_State.gl_blend ) {glEnable ( GL_BLEND ); _GL_State.gl_blend=true;}
		for ( it=list.begin();it!=list.end();++it )
		{
			par=dynamic_cast<OpenGL_Particles*> ( *it );
			if ( _use_point_sprite_ ) glPointSize ( par->_size_ );
			if ( par->_have_texture_ )
			{
				OpenGL_Surface *surf;
				surf=dynamic_cast<OpenGL_Surface*> ( _get_surface ( par->_surface_name_, par->_surface_group_ ) );
				if ( surf->_need_update_ ) surf->_update_();
				glBindTexture ( GL_TEXTURE_2D, surf->_tex_id_ );
				if ( _use_point_sprite_ )
				{
					glTexEnvf ( GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE );
					glPointParameterfARB ( GL_POINT_FADE_THRESHOLD_SIZE_ARB, 4.0f );
					glPointParameterfARB ( GL_POINT_SIZE_MIN_ARB, 1.0f );
					glPointParameterfARB ( GL_POINT_SIZE_MAX_ARB, _max_point_size_ );
				}

			}
			if ( _use_point_sprite_ )
			{
				glBegin ( GL_POINTS );
				for ( int a=0;a<par->_count_;a++ )
				{
					glColor4f ( par->_colors_[a*4], par->_colors_[a*4+1], par->_colors_[a*4+2], par->_colors_[a*4+3] );
					glVertex2f ( par->_particles_[a*2], par->_particles_[a*2+1] );
				}
				glEnd();
			}
			else
			{
				glBegin ( GL_QUADS );
				for ( int a=0;a<par->_count_;a++ )
				{
					glColor4f ( par->_colors_[a*4], par->_colors_[a*4+1], par->_colors_[a*4+2], par->_colors_[a*4+3] );
					float x ( par->_particles_[a*8] ), y ( par->_particles_[a*8+1] );
					glTexCoord2d ( 0, 0 );
					glVertex2f ( x, y );

					glTexCoord2d ( 0, 1 );
					glVertex2f ( x, y+par->_fsize_ );

					glTexCoord2d ( 1, 1 );
					glVertex2f ( x+par->_fsize_, y+par->_fsize_ );

					glTexCoord2d ( 1, 0 );
					glVertex2f ( x+par->_fsize_, y );

				}
				glEnd();
			}
		}
	}
	glPopMatrix();
//	glPopClientState();
}
void OpenGL_Video::_draw( )
{
	glMatrixMode ( GL_MODELVIEW );//сменим матрицу на всякий случай
	glPushMatrix();
	glLoadIdentity();

	if ( _mouse_pointer_name_!="" && _mouse_pointer_group_!="" )
	{
		Video_Sprite *spr;
		spr=_get_sprite ( _mouse_pointer_name_, _mouse_pointer_group_ );
		if ( spr!=0 )
		{
			double x,y;
			_get_mouse_position ( &x, &y );
			spr->_set_coord ( x, y );
			_add_sprite ( spr );
		}
		else
		{
			std::cerr<<"Can't get sprite:"<<_get_error() <<std::endl;
		}
	}

	_draw_particles(_bg_particles_draw_list_);
	_draw_sprites();
	_draw_particles(_particles_draw_list_);

	_sprites_draw_list_.clear();
	_particles_draw_list_.clear();
	_bg_particles_draw_list_.clear();
	glPopMatrix();
	glfwSwapBuffers();
	glClear ( GL_COLOR_BUFFER_BIT );
}

bool OpenGL_Video::_create_surface ( Video_Surface * surf, std::string name, std::string group_name, bool rewrite )
{
	/*		if ( group_name == "" ) group_name = _cur_group_;
			if ( !_surfaces_.exist( group_name ) )
			{
				_surfaces_.insert( group_name, new _Surface_Map_ );
			}
			else
			{
				_error = "Group already exist: " + group_name;
				return false;
			}

			_Surface_Map_ *smap = _surfaces_.get( group_name );
			if ( !smap )
			{
				_error = "Group not exist: " + group_name + " and creation was unsuccesfull";
				return false;
			}
			if ( smap->exist( name ) && !rewrite )
			{
				_error = "Surface already exist: " + name;
				return false;
			}

			smap->insert( name, surf );*/
	return false;
}

Video_Surface * OpenGL_Video::_get_surface ( std::string name , std::string group_name )
{
	if ( group_name == "" ) group_name = _cur_group_;
	if ( !_surfaces_.exist ( group_name ) )
	{
		_error = "Group not exist: " + group_name;
		return 0;
	}

	_Surface_Map_ *smap = _surfaces_.get ( group_name );
	if ( !smap )
	{
		_error = "Group not exist: " + group_name;
		return 0;
	}
	if ( !smap->exist ( name ) )
	{
		_error = "Surface not exist: " + name;
		return 0;
	}
	return smap->get ( name );
}
Video_Sprite * OpenGL_Video::_get_sprite ( std::string name , std::string group_name )
{
	if ( group_name == "" ) group_name = _cur_group_;
	if ( !_sprites_.exist ( group_name ) )
	{
		_error = "Group not exist: " + group_name;
		return 0;
	}

	_Sprite_Map_ *smap = _sprites_.get ( group_name );
	if ( !smap )
	{
		_error = "Group not exist: " + group_name;
		return 0;
	}
	if ( !smap->exist ( name ) )
	{
		_error = "Sprite not exist: " + name;
		return 0;
	}
	return smap->get ( name );
}

Video_Particles * OpenGL_Video::_get_particles ( std::string name , std::string group_name )
{
	if ( group_name == "" ) group_name = _cur_group_;
	if ( !_particles_.exist ( group_name ) )
	{
		_error = "Group not exist: " + group_name;
		return 0;
	}

	_Particles_Map_ *pmap = _particles_.get ( group_name );
	if ( !pmap )
	{
		_error = "Group not exist: " + group_name;
		return 0;
	}
	if ( !pmap->exist ( name ) )
	{
		_error = "Particles not exist: " + name;
		return 0;
	}
	return pmap->get ( name );
}

std::string OpenGL_Video::_get_current_group( )
{
	return _cur_group_;
}

bool OpenGL_Video::_set_current_group ( std::string name )
{
	if ( _surfaces_.exist ( name ) ) {_cur_group_ = name; return true;}
	_error = "Group " + name + " not exist";
	return false;
}

bool OpenGL_Video::_create_surface ( double xs, double ys, std::string name, std::string group_name, bool rewrite )
{
	return _create_surface ( ( int ) ( xs * ( double ) _w_ ), ( int ) ( ys * ( double ) _h_ ), name, group_name, rewrite );
}

bool OpenGL_Video::_create_surface ( int xs, int ys, std::string name, std::string group_name, bool rewrite )
{
	if ( group_name == "" ) group_name = _cur_group_;
	OpenGL_Surface *s;
	s = new OpenGL_Surface ( );

	if ( !s->_create_ ( xs, ys ) )
	{
		_error = "Can`t crfeate surface";
		delete s;
		return false;
	}

	if ( !_surfaces_.exist ( group_name ) )
	{
		_surfaces_.insert ( group_name, new _Surface_Map_ );
	}

	_Surface_Map_ *smap = _surfaces_.get ( group_name );
	if ( !smap )
	{
		_error = "Group not exist: " + group_name + " and creation was unsuccesfull";
		delete s;
		return false;
	}
	if ( smap->exist ( name ) && !rewrite )
	{
		_error = "Surface already exist: " + name;
		return false;
	}
	if ( smap->exist ( name ) && rewrite )
	{
		_free_surface ( name, group_name );
	}
	smap->insert ( name, s );
	return true;
}

bool OpenGL_Video::_create_sprite ( std::string name, std::string group_name, std::string surf_name, std::string surf_group, bool rewrite )
{
	Video_Sprite *s;
	s = new Video_Sprite ( name, group_name, this );


	if ( !_sprites_.exist ( group_name ) )
	{
		_sprites_.insert ( group_name, new _Sprite_Map_ );
	}
	_Sprite_Map_ *smap = _sprites_.get ( group_name );
	if ( !smap )
	{
		_error = "Group not exist: " + group_name + " and creation was unsuccesfull";
		delete s;
		return false;
	}
	if ( smap->exist ( name ) && !rewrite )
	{
		_error = "Sprite already exist: " + name;
		return false;
	}
	if ( smap->exist ( name ) && rewrite )
	{
		delete smap->get ( name );
		smap->remove ( name );
//		_free_surface ( name, group_name );
	}
	s->_set_surface ( this, surf_name, surf_group );
	smap->insert ( name, s );
	return true;
}


bool OpenGL_Video::_add_sprite ( Video_Sprite *spr )
{
	if ( !spr )
	{
		_error="Illegal sprite handler! 0x00000000";
		return false;
	}
	spr->_drawed_=false;
	spr->_need_draw_=true;
	_sprites_draw_list_.push_back ( spr );
	return true;
}
bool OpenGL_Video::_add_sprite ( std::string sprite_name, std::string sprite_group )
{
	if ( sprite_group == "" ) sprite_group = _cur_group_;

	_Sprite_Map_ *mp;
	if ( !_sprites_.exist ( sprite_group ) )
	{
		_error = "No such group: " + sprite_group;
		return false;
	}
	mp = _sprites_.get ( sprite_group );
	if ( !mp->exist ( sprite_name ) )
	{
		_error = "No such surface: " + sprite_name;
		return false;
	}
	Video_Sprite *spr;
	spr=mp->get ( sprite_name );
	spr->_drawed_=false;
	spr->_need_draw_=true;
	_sprites_draw_list_.push_back ( spr );
	return true;
}

bool OpenGL_Video::_free_surface ( std::string name, std::string grp )
{
	_Surface_Map_ *mp;
	if ( !_surfaces_.exist ( grp ) )
	{
		_error = "No such group: " + grp;
		return false;
	}
	mp = _surfaces_.get ( grp );
	if ( !mp->exist ( name ) )
	{
		_error = "No such surface: " + name;
		return false;
	}
	delete mp->get ( name );
	mp->remove ( name );
	return true;
}

std::string OpenGL_Video::_get_driver_name( )
{
	return "libGL_glfw";
}

const Video_Features & OpenGL_Video::_get_features( )
{
	static OpenGL_Video_Features _features;
	return _features;
}
void OpenGL_Video::show_mouse ( bool state )
{
	/// @todo Make show_mouse work
}
void OpenGL_Video::_get_mouse_position ( int *x, int *y )
{
	glfwGetMousePos ( x, y );
	*x-=_w_/2;
	*y-=_h_/2;
	return;
}
void OpenGL_Video::_get_mouse_position ( double *x, double *y )
{
	int xi, yi;
	glfwGetMousePos ( &xi, &yi );
	xi-=_w_/2;
	yi-=_h_/2;
	yi=-yi;
	*x= ( double ) xi/ ( ( double ) _w_/2. );
	*y= ( double ) yi/ ( ( double ) _h_/2. );

	return;
}
Event OpenGL_Video::_get_event()
{
	Event tmp;
	if ( !events.empty() )
	{
		tmp=events.front();
		events.pop_front();
	}
	return tmp;
}
bool OpenGL_Video::check_surface_point ( double x_pos, double y_pos, double x_point, double y_point, std::string sprite_name, std::string group_name )
{
	double w,h;
	int wi, hi;
	Video_Sprite *spr;
	spr=_get_sprite ( sprite_name , group_name );
	if ( !spr ) return false;
	wi=spr->_w_;
	hi=spr->_h_;
	w= ( double ) wi/ ( ( double ) _w_/2. );
	h= ( double ) hi/ ( ( double ) _h_/2. );

	float xs,xe,ys,ye;
	xs=x_pos-spr->_base_x_;
	xe=xs+w;
	ys=y_pos-spr->_base_y_;
	ye=ys+h;

	if ( x_point>=xs && x_point<=xe && y_point>=ys && y_point<=ye )
	{
		std::cerr<<"Hit OK"<<std::endl;
		return true;
	}

	return false;
}

bool OpenGL_Video::_add_particles ( std::string name, std::string group, bool is_background )
{
	if ( group == "" ) group = _cur_group_;

	_Particles_Map_ *mp;
	if ( !_particles_.exist ( group ) )
	{
		_error = "No such particles group: " + group;
		return false;
	}
	mp = _particles_.get ( group );
	if ( !mp->exist ( name ) )
	{
		_error = "No such particles: " + name;
		return false;
	}
	Video_Particles *particle;
	particle=mp->get ( name );
	if(!is_background)_particles_draw_list_.push_back ( particle );
	else _bg_particles_draw_list_.push_back ( particle );
	return true;
}

bool OpenGL_Video::_create_particles ( std::string name, std::string group_name, std::string surf_name, std::string surf_group, int count, bool rewrite )
{
	OpenGL_Particles *p;
	p = new OpenGL_Particles ( this );


	if ( !_particles_.exist ( group_name ) )
	{
		_particles_.insert ( group_name, new _Particles_Map_ );
	}
	_Particles_Map_ *pmap = _particles_.get ( group_name );
	if ( !pmap )
	{
		_error = "Group not exist: " + group_name + " and creation was unsuccesfull";
		delete p;
		return false;
	}
	if ( pmap->exist ( name ) && !rewrite )
	{
		_error = "Particles already exist: " + name;
		delete p;
		return false;
	}
	if ( pmap->exist ( name ) && rewrite )
	{
		delete pmap->get ( name );
		pmap->remove ( name );
	}
	p->_have_texture_= ( surf_name=="" ) ?false:true;
	if ( !_use_point_sprite_ )
	{
		p->_particles_=new GLfloat[8*count];//Используем квады - 4 вершиины по 2 координаты
		p->_texture_=new GLfloat[8*count];//4 вершины квада по 2 координаты
		p->_colors_=new GLfloat[4*4*count];//4 значения на вершину, 4 вершины на 1 объект
	}
	else
	{
		p->_particles_=new GLfloat[2*count];//Используем поинтспрайты - 2 координаты
		p->_colors_=new GLfloat[4*count];//4 значения на вершину
	}
	if ( _use_vbo_ )
	{
		//Создадим буфер для частиц
		glGenBuffers ( 1, ( GLuint* ) &p->_p_buf_id_ );
		glGenBuffers ( 1, ( GLuint* ) &p->_c_buf_id_ );

		if ( _use_point_sprite_ )
		{
			glBindBuffer ( GL_ARRAY_BUFFER, p->_c_buf_id_ );//Буфер цветов
			glBufferData ( GL_ARRAY_BUFFER, count*sizeof ( GLfloat ) *4, 0, GL_STREAM_DRAW );//Создаем объект

			glBindBuffer ( GL_ARRAY_BUFFER, p->_p_buf_id_ );//Буфер вершин
			glBufferData ( GL_ARRAY_BUFFER, count*sizeof ( GLfloat ) *2, 0, GL_STREAM_DRAW );//Создаем объект
		}
		else
		{
			glBindBuffer ( GL_ARRAY_BUFFER, p->_c_buf_id_ );//Буфер цветов
			glBufferData ( GL_ARRAY_BUFFER, count*sizeof ( GLfloat ) *4*4, 0, GL_STREAM_DRAW );//Создаем объект

			glBindBuffer ( GL_ARRAY_BUFFER, p->_p_buf_id_ );//Буфер вершин
			glBufferData ( GL_ARRAY_BUFFER, count*sizeof ( GLfloat ) *8, 0, GL_STREAM_DRAW );//Создаем объект

			glGenBuffers ( 1, ( GLuint* ) &p->_t_buf_id_ );
			glBindBuffer ( GL_ARRAY_BUFFER, p->_t_buf_id_ );//Буфер текстур
			glBufferData ( GL_ARRAY_BUFFER, count*sizeof ( GLfloat ) *8, 0, GL_STREAM_DRAW );//Создаем объект
		}
	}
	if ( p->_have_texture_ )
	{
		p->_surface_name_=surf_name;
		p->_surface_group_=surf_group;
	}
	p->_count_=count;
	p->_use_point_sprite_=_use_point_sprite_;
	pmap->insert ( name, p );
	return true;
}

bool OpenGL_Video::_set_mouse_pointer ( std::string sprite_name, std::string sprite_group )
{
	_mouse_pointer_name_=sprite_name;
	_mouse_pointer_group_=sprite_group;
	return true;
}

void OpenGL_Video::_enable_mouse_button_event ( bool state )
{
	glfwSetMouseButtonCallback ( state?&MouseButtonEvent:0 );
}

void OpenGL_Video::_enable_char_event ( bool state )
{
	glfwSetCharCallback ( state?&CharEvent:0 );
}

void OpenGL_Video::_enable_keyboard_event ( bool state )
{
	glfwSetKeyCallback ( state?&KeyEvent:0 );
}
void OpenGL_Video::_enable_mouse_move_event ( bool state )
{
        glfwSetMousePosCallback( state?&MouseMoveEvent:0 );
}
void OpenGL_Video::_enable_mouse_cursor (bool state)
{
	if(!state) glfwDisable ( GLFW_MOUSE_CURSOR );
	else glfwEnable ( GLFW_MOUSE_CURSOR );
}
void OpenGL_Video::_get_mouse_state( const MOUSE_BUTTON & button, EVENT_TYPE * state )
{
	int glfw_button;
	switch (button)
	{
		case LBUTTON:
			glfw_button=GLFW_MOUSE_BUTTON_LEFT;
			break;
		case RBUTTON:
			glfw_button=GLFW_MOUSE_BUTTON_RIGHT;
			break;
		case MBUTTON:
			glfw_button=GLFW_MOUSE_BUTTON_MIDDLE;
			break;
		default:
			glfw_button=GLFW_MOUSE_BUTTON_LEFT;
	}
	int g_state = glfwGetMouseButton( glfw_button);
	*state = (g_state==GLFW_PRESS)?MOUSE_PRESS:MOUSE_RELEASE;
}
