/*  $Id: gl_state_checker.cpp,v 1.57 2004/05/06 13:05:58 sphair Exp $
**
**  ClanLib Game SDK
**  Copyright (C) 2003  The ClanLib Team
**  For a total list of contributers see the file CREDITS.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This library is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
**  Lesser General Public License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/

#include "Display/display_precomp.h"
#include "GL/gl_state.h"
#include "GL/gl_state_checker.h"
#include "API/Display/blend_func.h"
#include "API/Display/color.h"
#include "API/Display/graphic_context.h"
#include "API/Core/Math/rect.h"
#include "API/Core/Math/point.h"
#include "API/GL/opengl_state.h"
#include "graphic_context_opengl.h"
#include "API/GL/opengl_wrap.h"

// Using 0.38 for the texture matrix instead of 0.375 - reduces some slight smooth (rounding errors?)
#define cl_pixelcenter_constant 0.38

/////////////////////////////////////////////////////////////////////////////
// CL_GLStateChecker construction:

CL_GLStateChecker::CL_GLStateChecker(CL_GraphicContext_OpenGL *gc_opengl)
: opengl_state(0), gc_opengl(gc_opengl), first_call(true)
{
	internal_state.blendfunc[0] = blend_src_alpha;
	internal_state.blendfunc[1] = blend_one_minus_src_alpha;

	internal_state.color[0] = 255;
	internal_state.color[1] = 255;
	internal_state.color[2] = 255;
	internal_state.color[3] = 255;

	// -2 since we want to force the first render to set enable/disable_2d
	internal_state.texture_handle = -2;

	internal_state.fillmode = fillmode_solid;
	
	primitive = CL_DONT_CARE;
	primitive_count = 0;

	stencil_op_pass = CL_INCR;
	stencil_op_fail = CL_KEEP;	
	stencil_write_ref = 0;
	stencil_func_write = CL_ALWAYS;

	opengl_state = new CL_OpenGLState(gc_opengl);
	opengl_state->attach_data(this);
}

CL_GLStateChecker::~CL_GLStateChecker()
{
	delete opengl_state;
}

/////////////////////////////////////////////////////////////////////////////
// CL_GLStateChecker operations:

CL_GLStateChecker *CL_GLStateChecker::from_gc(CL_GraphicContext *gc)
{
	CL_GraphicContext_OpenGL *gl_impl = (CL_GraphicContext_OpenGL *) gc->impl;
	return gl_impl->state_checker;
}

void CL_GLStateChecker::add_clear(
	const CL_Color &color)
{
	flush();
	clClearColor(color.get_red() / 255.0f, color.get_green() / 255.0f, color.get_blue() / 255.0f, color.get_alpha() / 255.0f);
	clClear(CL_COLOR_BUFFER_BIT);
}

void CL_GLStateChecker::add_pixel(
	const CL_GLState &state,
	int x, int y)
{
	set_state(state, CL_POINTS);
	clVertex2i(x, y);
}

void CL_GLStateChecker::add_line(
	const CL_GLState &state,
	int x1, int y1, int x2, int y2)
{
	set_state(state, CL_LINES);
	clVertex2i(x1, y1);
	clVertex2i(x2, y2);
}

void CL_GLStateChecker::add_gradient(
	const CL_GLState &state,
	const CL_Rectf &position,
	const CL_Gradient &gradient)
{
	set_state(state, CL_GRADIENT);
	add_quad_vertices( state, position.left, position.top, position.right, position.bottom );

	int a = primitive_count*16;

	// Bottom left vertex
	color_array[a++] = gradient.bottom_left.get_red() / 255.0f;
	color_array[a++] = gradient.bottom_left.get_green() / 255.0f;
	color_array[a++] = gradient.bottom_left.get_blue() / 255.0f;
	color_array[a++] = gradient.bottom_left.get_alpha() / 255.0f;

	// Bottom right vertex
	color_array[a++]  = gradient.bottom_right.get_red() / 255.0f;
	color_array[a++]  = gradient.bottom_right.get_green() / 255.0f;
	color_array[a++] =	gradient.bottom_right.get_blue() / 255.0f;
	color_array[a++] =	gradient.bottom_right.get_alpha() / 255.0f;

	// Top right vertex
	color_array[a++] = gradient.top_right.get_red() / 255.0f;
	color_array[a++] = gradient.top_right.get_green() / 255.0f;
	color_array[a++] = gradient.top_right.get_blue() / 255.0f;
	color_array[a++] = gradient.top_right.get_alpha() / 255.0f;

	// Top left vertex
	color_array[a++] = gradient.top_left.get_red() / 255.0f;
	color_array[a++] = gradient.top_left.get_green() / 255.0f;
	color_array[a++] = gradient.top_left.get_blue() / 255.0f;
	color_array[a++] = gradient.top_left.get_alpha() / 255.0f;

	primitive_count++;

	if( primitive_count == MAX_QUAD_COUNT )
		flush();
}

void CL_GLStateChecker::add_gradient(
	const CL_GLState &state,
	const CL_Quad &quad,
	const CL_Gradient &gradient)
{
	set_state(state, CL_GRADIENT);
	add_quad_vertices( state, (float)quad.x1, (float)quad.y1, (float)quad.x2, (float)quad.y2, (float)quad.x3, (float)quad.y3, (float)quad.x4, (float)quad.y4 );

	int a = primitive_count*16;

	// Top left vertex
	color_array[a++] = gradient.top_left.get_red() / 255.0f;
	color_array[a++] = gradient.top_left.get_green() / 255.0f;
	color_array[a++] = gradient.top_left.get_blue() / 255.0f;
	color_array[a++] = gradient.top_left.get_alpha() / 255.0f;

	// Top right vertex
	color_array[a++]  = gradient.top_right.get_red() / 255.0f;
	color_array[a++]  = gradient.top_right.get_green() / 255.0f;
	color_array[a++] =	gradient.top_right.get_blue() / 255.0f;
	color_array[a++] =	gradient.top_right.get_alpha() / 255.0f;

	// Bottom right vertex
	color_array[a++] = gradient.bottom_right.get_red() / 255.0f;
	color_array[a++] = gradient.bottom_right.get_green() / 255.0f;
	color_array[a++] = gradient.bottom_right.get_blue() / 255.0f;
	color_array[a++] = gradient.bottom_right.get_alpha() / 255.0f;

	// Bottom left vertex
	color_array[a++] = gradient.bottom_left.get_red() / 255.0f;
	color_array[a++] = gradient.bottom_left.get_green() / 255.0f;
	color_array[a++] = gradient.bottom_left.get_blue() / 255.0f;
	color_array[a++] = gradient.bottom_left.get_alpha() / 255.0f;

	primitive_count++;

	if( primitive_count == MAX_QUAD_COUNT )
		flush();
}

void CL_GLStateChecker::add_quad_vertices( 
	const CL_GLState &state,
	float px1,
	float py1,
	float px2,
	float py2 )
{
	int b = primitive_count * 8; // 8 values per quad

	vertex_array[b+0] = px1;
	vertex_array[b+1] = py2;
	vertex_array[b+2] = px2;
	vertex_array[b+3] = py2;
	vertex_array[b+4] = px2;
	vertex_array[b+5] = py1;
	vertex_array[b+6] = px1;
	vertex_array[b+7] = py1;
}

// used only by add_gradient with CL_Quad
void CL_GLStateChecker::add_quad_vertices( 
	const CL_GLState &state,
	float px1,
	float py1,
	float px2,
	float py2,
	float px3,
	float py3,
	float px4,
	float py4 )
{
	int b = primitive_count * 8; // 8 values per quad

	vertex_array[b+0] = px1;
	vertex_array[b+1] = py1;
	vertex_array[b+2] = px2;
	vertex_array[b+3] = py2;
	vertex_array[b+4] = px3;
	vertex_array[b+5] = py3;
	vertex_array[b+6] = px4;
	vertex_array[b+7] = py4;
}

void CL_GLStateChecker::add_quad(
	const CL_GLState &state,
	float px1,
	float py1,
	float px2,
	float py2,
	int tx1,
	int ty1,
	int tx2,
	int ty2)
{
	set_state(state, CL_QUADS);

	if( internal_state.texture_handle != -1 )
	{
		int a = primitive_count * 8; // offset in array. 8 values per quad 
		
		texcoord_array[a+0] = tx1;
		texcoord_array[a+1] = ty2;
		texcoord_array[a+2] = tx2;
		texcoord_array[a+3] = ty2;
		texcoord_array[a+4] = tx2;
		texcoord_array[a+5] = ty1;
		texcoord_array[a+6] = tx1;
		texcoord_array[a+7] = ty1;
	}

	add_quad_vertices( state, px1, py1, px2, py2 );
	
	primitive_count++;
		
	if( primitive_count == MAX_QUAD_COUNT )
		flush();
}

void CL_GLStateChecker::add_quad(
	const CL_GLState &state,
	double destX[4],
	double destY[4],
	int tx[4],
	int ty[4])
{
	set_state(state, CL_QUADS);

	int a = primitive_count * 8; // offset in array. 8 values per quad 

	if( internal_state.texture_handle != -1 )
	{
		texcoord_array[a+0] = tx[0];
		texcoord_array[a+1] = ty[0];
		texcoord_array[a+2] = tx[1];
		texcoord_array[a+3] = ty[1];
		texcoord_array[a+4] = tx[2];
		texcoord_array[a+5] = ty[2];
		texcoord_array[a+6] = tx[3];
		texcoord_array[a+7] = ty[3];
	}

	vertex_array[a+0] = destX[0];
	vertex_array[a+1] = destY[0];
	vertex_array[a+2] = destX[1];
	vertex_array[a+3] = destY[1];
	vertex_array[a+4] = destX[2];
	vertex_array[a+5] = destY[2];
	vertex_array[a+6] = destX[3];
	vertex_array[a+7] = destY[3];

	primitive_count++;
		
	if( primitive_count == MAX_QUAD_COUNT )
		flush();
}

void CL_GLStateChecker::flush(bool rebind_texture) const
{
	opengl_state->set_active();

	if (primitive == CL_LINES || primitive == CL_POINTS )
	{
		clEnd();
		primitive = CL_DONT_CARE; // carefull... CL_POINTS is defined as 0x0000
	}

	if( primitive_count != 0 )
	{
		// pointer must be set every frame, otherwise kabooom!
		clVertexPointer( 2, CL_FLOAT, 0, vertex_array );
		clTexCoordPointer( 2, CL_INT, 0, texcoord_array );

		// enable color array if gradient
		if( primitive == CL_GRADIENT )
		{
			clEnableClientState(CL_COLOR_ARRAY);
			clColorPointer( 4, CL_FLOAT, 0, color_array );
		}

		if( primitive == CL_QUADS || primitive == CL_GRADIENT )
		{
			clDrawArrays(CL_QUADS, 0, 4*primitive_count);
		}
		
		if( primitive == CL_GRADIENT ) // not gradient any more. disable color array.
		{
			clDisableClientState(CL_COLOR_ARRAY);
			clColor4fv(internal_state.color);
		}

		primitive_count = 0;
	}

	if( rebind_texture )
		internal_state.texture_handle = -2;
}

CL_Color CL_GLStateChecker::get_pixel(int x, int y) const
{
	flush();

	CLdouble viewport[4];
	clGetDoublev(CL_VIEWPORT, viewport);
	CLdouble height = viewport[3];

	CL_Color pixel;
	clReadPixels(x, (int)height-y, 1, 1, CL_RGB, CL_UNSIGNED_BYTE, &pixel.color);
	pixel.set_alpha( 255 );
	return pixel;
}

/////////////////////////////////////////////////////////////////////////////
// CL_OpenGLStateData:

void CL_GLStateChecker::on_save()
{
	flush();
}

void CL_GLStateChecker::on_load()
{
	gc_opengl->sync_gc();
/*
	// restore opengl states
	clBlendFunc(internal_state.blendfunc[0], internal_state.blendfunc[1]);
	clColor4fv(internal_state.color);
	if (internal_state.texture_handle == -2 || internal_state.texture_handle == -1)
	{
		clDisable(CL_TEXTURE_2D);
	}
	else
	{
		clEnable(CL_TEXTURE_2D);
		clBindTexture(CL_TEXTURE_2D, internal_state.texture_handle);
		CLint texture_width = 1, texture_height = 1;
		clGetTexLevelParameteriv(CL_TEXTURE_2D, 0, CL_TEXTURE_WIDTH, &texture_width);
		clGetTexLevelParameteriv(CL_TEXTURE_2D, 0, CL_TEXTURE_HEIGHT, &texture_height);
		clMatrixMode(CL_TEXTURE);
		CLdouble scale_matrix[16];
		memset(scale_matrix, 0, sizeof(CLdouble)*16);
		scale_matrix[0] = 1.0/texture_width;
		scale_matrix[5] = 1.0/texture_height;
		scale_matrix[10] = 1.0;
		scale_matrix[15] = 1.0;
		clLoadMatrixd(scale_matrix);
		clMatrixMode(CL_MODELVIEW);
	}
*/
}

void CL_GLStateChecker::on_flush()
{
	flush();
}

/////////////////////////////////////////////////////////////////////////////
// CL_GLStateTree implementation:

void CL_GLStateChecker::set_state(const CL_GLState &state, CLenum new_primitive)
{
	opengl_state->set_active();
	if (first_call)
	{
		opengl_state->setup_2d(gc_opengl->is_canvas_target());
		clEnableClientState(CL_VERTEX_ARRAY);
		clEnableClientState(CL_TEXTURE_COORD_ARRAY);
		set_blendfunc(internal_state);
		clColor4fv(internal_state.color);
		clEnable(CL_TEXTURE_2D);
		clEnable(CL_BLEND);
		clTexEnvf(CL_TEXTURE_ENV, CL_TEXTURE_ENV_MODE, CL_MODULATE);
		first_call = false;
	}

	if (internal_state.texture_handle != state.texture_handle)
	{
		internal_state.texture_handle = state.texture_handle;
		
		flush();

		if(state.texture_handle == -1)
		{
			clDisable(CL_TEXTURE_2D);
		}
		else
		{
			// Bind texture and convert internal texture coordinates to be
			// in pixels.
			clEnable(CL_TEXTURE_2D);
			clBindTexture(CL_TEXTURE_2D, (CLuint)state.texture_handle);
			clTexEnvf(CL_TEXTURE_ENV, CL_TEXTURE_ENV_MODE, CL_MODULATE);
			CLint texture_width = 1, texture_height = 1;
			clGetTexLevelParameteriv(CL_TEXTURE_2D, 0, CL_TEXTURE_WIDTH, &texture_width);
			clGetTexLevelParameteriv(CL_TEXTURE_2D, 0, CL_TEXTURE_HEIGHT, &texture_height);
			clMatrixMode(CL_TEXTURE);
			CLdouble scale_matrix[16];
			memset(scale_matrix, 0, sizeof(CLdouble)*16);
			scale_matrix[0] = 1.0/texture_width;
			scale_matrix[5] = 1.0/texture_height;
			scale_matrix[10] = 1.0;
			scale_matrix[15] = 1.0;
			clLoadMatrixd(scale_matrix);

			// Sync texture coordinates with screen coordinates for GL_LINEAR sampling:
			clTranslated(cl_pixelcenter_constant, cl_pixelcenter_constant, 0.0);

			clMatrixMode(CL_MODELVIEW);
		}
	}
	
	if (internal_state.blendfunc[0] != state.blendfunc[0] || 
		internal_state.blendfunc[1] != state.blendfunc[1])
	{
		flush();

		internal_state.blendfunc[0] = state.blendfunc[0];
		internal_state.blendfunc[1] = state.blendfunc[1];

		set_blendfunc(state);
	}

	if (internal_state.fillmode != state.fillmode )
	{
		flush();
		internal_state.fillmode = state.fillmode;
		set_fillmode(state);
	}

	if (internal_state.color[0] != state.color[0] ||
		internal_state.color[1] != state.color[1] ||
		internal_state.color[2] != state.color[2] ||
		internal_state.color[3] != state.color[3])
	{
		flush();

		internal_state.color[0] = state.color[0];
		internal_state.color[1] = state.color[1];
		internal_state.color[2] = state.color[2];
		internal_state.color[3] = state.color[3];

		clColor4fv(state.color);
	}

	if ( primitive != new_primitive  )
	{
		flush();

		if ( new_primitive == CL_LINES || new_primitive == CL_POINTS )
			clBegin(new_primitive);

		primitive = new_primitive;
	}
}

void CL_GLStateChecker::set_fillmode(const CL_GLState &state)
{
	switch( state.fillmode )
	{
	case fillmode_line:
		clPolygonMode( CL_FRONT_AND_BACK, CL_LINE );
		break;
	case fillmode_point:
		clPolygonMode( CL_FRONT_AND_BACK, CL_POINT );
		break;
	case fillmode_solid:
		clPolygonMode( CL_FRONT_AND_BACK, CL_FILL );
		break;
	}
}

void CL_GLStateChecker::set_blendfunc(const CL_GLState &state)
{
	CLenum blendfunc[2];
	for(int i = 0; i < 2; ++i)
	{			
		switch (state.blendfunc[i])
		{
		case blend_zero:
			blendfunc[i] = CL_ZERO;
			break;
		case blend_one:
			blendfunc[i] = CL_ONE;
			break;
		case blend_dest_color:
			blendfunc[i] = CL_DST_COLOR;
			break;
		case blend_src_color:
			blendfunc[i] = CL_SRC_COLOR;
			break;
		case blend_one_minus_dest_color:
			blendfunc[i] = CL_ONE_MINUS_DST_COLOR;
			break;
		case blend_one_minus_src_color:
			blendfunc[i] = CL_ONE_MINUS_SRC_COLOR;
			break;
		case blend_src_alpha:
			blendfunc[i] = CL_SRC_ALPHA;
			break;
		case blend_one_minus_src_alpha:
			blendfunc[i] = CL_ONE_MINUS_SRC_ALPHA;
			break;
		case blend_dst_alpha:
			blendfunc[i] = CL_DST_ALPHA;
			break;
		case blend_one_minus_dest_alpha:
			blendfunc[i] = CL_ONE_MINUS_DST_ALPHA;
			break;
		case blend_src_alpha_saturate:
			blendfunc[i] = CL_SRC_ALPHA_SATURATE;
			break;
		case blend_constant_color:
			blendfunc[i] = CL_CONSTANT_COLOR;
			break;
		case blend_constant_alpha:
			blendfunc[i] = CL_CONSTANT_ALPHA;
			break;
		case blend_one_minus_constant_color:
			blendfunc[i] = CL_ONE_MINUS_CONSTANT_COLOR;
			break;
		case blend_one_minus_constant_alpha:
			blendfunc[i] = CL_ONE_MINUS_CONSTANT_ALPHA;
			break;
		}
	}
	clBlendFunc(blendfunc[0], blendfunc[1]);
}

void CL_GLStateChecker::clear_stencil(int value)
{
	opengl_state->set_active();
	
	flush();
	
	clClearStencil(value);
	clClear(CL_STENCIL_BUFFER_BIT);
}

void CL_GLStateChecker::enable_stencil_write(bool enabled, bool visible, float alpha_limit)
{
	opengl_state->set_active();

	flush();

	if( enabled )
	{
		if( !visible )
			clColorMask(0,0,0,0);

		clEnable(CL_STENCIL_TEST);
		clStencilFunc(stencil_func_write, stencil_write_ref, 255);
		clStencilOp(stencil_op_fail, CL_KEEP, stencil_op_pass); // CL_KEEP: action for stencil-pass & z-fail
		clEnable(CL_ALPHA_TEST);
		clAlphaFunc(CL_GEQUAL, alpha_limit);
	}
	else
	{
		clColorMask(1,1,1,1);
		clDisable(CL_STENCIL_TEST);
		clDisable(CL_ALPHA_TEST);  
	}
}

void CL_GLStateChecker::enable_stencil_test(bool enabled, CL_StencilFunc func, int ref)
{
	opengl_state->set_active();

	flush();

	int stencil_func = 0;

	switch( func )
	{
		case stencil_never: stencil_func = CL_NEVER; break;
		case stencil_less: stencil_func = CL_LESS; break;
		case stencil_lequal: stencil_func = CL_LEQUAL; break;
		case stencil_greater: stencil_func = CL_GREATER; break;
		case stencil_gequal: stencil_func = CL_GEQUAL; break;
		case stencil_equal: stencil_func = CL_EQUAL; break;
		case stencil_notequal: stencil_func = CL_NOTEQUAL; break;
		case stencil_always: stencil_func = CL_ALWAYS; break;
		default: stencil_func = CL_LEQUAL;
	}

	if( enabled )
	{
		clEnable(CL_STENCIL_TEST);	
		clStencilFunc(stencil_func, ref, 255); 
		clStencilOp(CL_KEEP, CL_KEEP, CL_KEEP);
	}
	else
	{
		clDisable(CL_STENCIL_TEST);	
	}
}
