/*  $Id: surface_target_opengl.cpp,v 1.41 2004/04/23 22:38:13 mbn 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 "surface_target_opengl.h"
#include "gl_state_checker.h"
#include "graphic_context_opengl.h"
#include "API/Display/pixel_buffer.h"
#include "API/Display/graphic_context.h"
#include "API/Display/pixel_format.h"
#include "API/Display/display.h"
#include "API/Display/display_window.h"
#include "API/Core/System/error.h"
#include "API/GL/opengl.h"
#include "API/GL/opengl_state.h"
#include "API/GL/opengl_wrap.h"
#include "canvas_opengl.h"

#include <GL/glu.h>

/////////////////////////////////////////////////////////////////////////////
// CL_Surface_Target_OpenGL construction:

CL_Surface_Target_OpenGL::CL_Surface_Target_OpenGL(
	CL_PixelBuffer *provider,
	bool delete_provider,
	int flag)
{
	selected_canvas = 0;

	if(flag & CL_Surface::flag_keep_pixelbuffer)
	{
		if(delete_provider)
		{
			original_pixelbuffer = provider;
			delete_provider = false;
		}
		else
		{
			original_pixelbuffer = new CL_PixelBuffer(provider->get_width(), provider->get_height(), provider->get_pitch(), provider->get_format());
			original_pixelbuffer->lock();
			memcpy(original_pixelbuffer->get_data(), provider->get_data(), provider->get_pitch() * provider->get_height());
			original_pixelbuffer->unlock();
		}
	}
	else
		original_pixelbuffer = 0;

	CL_OpenGLState state(CL_Display::get_current_window()->get_gc());
	state.set_active();
	state.setup_2d();
	clEnable(CL_TEXTURE_2D);

	surface_size.width = provider->get_width();
	surface_size.height = provider->get_height();
	
	texture_size.width = 1;
	texture_size.height = 1;
	while (texture_size.width < surface_size.width) texture_size.width *= 2;
	while (texture_size.height < surface_size.height) texture_size.height *= 2;

	// check out if the original texture needs or doesn't need an alpha channel
	bool needs_alpha = provider->get_format().get_alpha_mask() || provider->get_format().has_colorkey();

	CLint internal_format = needs_alpha ? CL_RGBA : CL_RGB;
	CLenum format = needs_alpha ? CL_RGBA : CL_RGB;

	// Upload to OpenGL:
	clGenTextures(1, &handle);
	clBindTexture(CL_TEXTURE_2D, handle);

	// create
	clTexImage2D(
		CL_TEXTURE_2D,            // target
		0,                        // level
		internal_format,          // internalformat
		texture_size.width,       // width
		texture_size.height,      // height
		0,                        // border
		format,                   // format (it really doesn't matter since nothing is uploaded)
		CL_UNSIGNED_BYTE,         // type (it really doesn't matter since nothing is uploaded)
		0);                       // texels (0 to avoid uploading)

	set_pixeldata(CL_Point(0, 0), CL_Rect(0, 0, provider->get_width(), provider->get_height()), *provider);

	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_MIN_FILTER, CL_LINEAR);
	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_MAG_FILTER, CL_LINEAR);
	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_WRAP_S, CL_CLAMP_TO_EDGE);
	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_WRAP_T, CL_CLAMP_TO_EDGE);

	if (delete_provider) delete provider;
}

CL_Surface_Target_OpenGL::CL_Surface_Target_OpenGL(
	const CL_PixelFormat &pf,
	const CL_Size &size)
{
	selected_canvas = 0;

	CL_OpenGLState state(CL_Display::get_current_window()->get_gc());
	state.set_active();
	state.setup_2d();
	clEnable(CL_TEXTURE_2D);

	surface_size.width = size.width;
	surface_size.height = size.height;
	
	texture_size.width = 1;
	texture_size.height = 1;
	while (texture_size.width < surface_size.width) texture_size.width *= 2;
	while (texture_size.height < surface_size.height) texture_size.height *= 2;

	// check out if the original texture needs or doesn't need an alpha channel
	bool needs_alpha = pf.get_alpha_mask() || pf.has_colorkey();

	CLint internal_format = needs_alpha ? CL_RGBA : CL_RGB;
	CLenum format = needs_alpha ? CL_RGBA : CL_RGB;

	// Upload to OpenGL:
	clGenTextures(1, &handle);
	clBindTexture(CL_TEXTURE_2D, handle);

	// create
	clTexImage2D(
		CL_TEXTURE_2D,            // target
		0,                        // level
		internal_format,          // internalformat
		texture_size.width,       // width
		texture_size.height,      // height
		0,                        // border
		format,                   // format (it really doesn't matter since nothing is uploaded)
		CL_UNSIGNED_BYTE,         // type (it really doesn't matter since nothing is uploaded)
		0);                       // texels (0 to avoid uploading)

	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_MIN_FILTER, CL_LINEAR);
	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_MAG_FILTER, CL_LINEAR);
	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_WRAP_S, CL_CLAMP_TO_EDGE);
	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_WRAP_T, CL_CLAMP_TO_EDGE);
}

CL_Surface_Target_OpenGL::~CL_Surface_Target_OpenGL()
{
	CL_OpenGLState state(CL_Display::get_current_window()->get_gc());
	state.set_active();

	clDeleteTextures(1, &handle);
}

/////////////////////////////////////////////////////////////////////////////
// CL_Surface_Target_OpenGL attributes:

int CL_Surface_Target_OpenGL::get_width() const
{
	return surface_size.width;
}
	
int CL_Surface_Target_OpenGL::get_height() const
{
	return surface_size.height;
}

CL_PixelBuffer CL_Surface_Target_OpenGL::get_pixeldata()
{
	if (original_pixelbuffer && !selected_canvas)
		return *original_pixelbuffer;

	if (selected_canvas) selected_canvas->sync_texture();

	CL_OpenGLState state(CL_Display::get_current_window()->get_gc());
	state.set_active();
	state.setup_2d();
	clEnable(CL_TEXTURE_2D);
	clBindTexture(CL_TEXTURE_2D, handle);

	CL_PixelFormat abgr_format;
	abgr_format.set_depth(32);
	abgr_format.enable_colorkey(false);
	abgr_format.set_alpha_mask(0xFF000000);
	abgr_format.set_blue_mask (0x00FF0000);
	abgr_format.set_green_mask(0x0000FF00);
	abgr_format.set_red_mask  (0x000000FF);

	CL_PixelBuffer buffer(
		texture_size.width, texture_size.height,
		texture_size.width * 4,
		abgr_format);

	buffer.lock();
	clGetTexImage(GL_TEXTURE_2D, 0, CL_RGBA, CL_UNSIGNED_BYTE, buffer.get_data());
	buffer.unlock();

	return buffer;
}
	
CL_GraphicContext *CL_Surface_Target_OpenGL::get_gc()
{
	return 0;
}

/////////////////////////////////////////////////////////////////////////////
// CL_Surface_Target_OpenGL operations:

void CL_Surface_Target_OpenGL::set_pixeldata(
	const CL_Point &pos,
	const CL_Rect &src_rect,
	CL_PixelBuffer &pixeldata)
{
	// check the coordinates
	{
		CL_Point p1(pos.x + src_rect.left, pos.y + src_rect.top);
		CL_Point p2(p1.x + src_rect.get_width() - 1, p1.y + src_rect.get_height() - 1);
		CL_Rect r(CL_Point(0, 0), texture_size);
		if (!r.is_inside(p1) || !r.is_inside(p2))
			throw CL_Error("The destination rectangle cannot be outside the texture rectangle");
	}

	if (selected_canvas) selected_canvas->sync_texture();

	CL_OpenGLState state(CL_Display::get_current_window()->get_gc());
	state.set_active();
	state.setup_2d();
	clEnable(CL_TEXTURE_2D);

	// check out if the original texture needs or doesn't need an alpha channel
	bool needs_alpha = pixeldata.get_format().get_alpha_mask() || pixeldata.get_format().has_colorkey();

	CLenum format;
	CLenum type;
	bool conv_needed = !CL_OpenGL::to_opengl_pixelformat(pixeldata.get_format(), format, type);

	// also check for the pitch (OpenGL can only skip pixels, not bytes)
	if (!conv_needed)
	{
		const int bytesPerPixel = (pixeldata.get_format().get_depth() + 7) / 8;
		if (pixeldata.get_pitch() % bytesPerPixel != 0)
			conv_needed = true;
	}

	// no conversion needed
	if (!conv_needed)
	{
		pixeldata.lock();

		// Upload to OpenGL:
		clBindTexture(CL_TEXTURE_2D, handle);

		// change alignment
		clPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		const int bytesPerPixel = (pixeldata.get_format().get_depth() + 7) / 8;
		clPixelStorei(CL_UNPACK_ROW_LENGTH, pixeldata.get_pitch() / bytesPerPixel);

		char *data = (char *)pixeldata.get_data() + (src_rect.top * pixeldata.get_pitch()) + (src_rect.left * bytesPerPixel);

		// upload
		clTexSubImage2D(
			CL_TEXTURE_2D,            // target
			0,                        // level
			0, 0,                     // xoffset, yoffset
			src_rect.get_width(),     // width
			src_rect.get_height(),    // height
			format,					  // format
			type,					  // type
			data);                    // texels

		pixeldata.unlock();
	}
	// conversion needed
	else
	{
		CL_PixelBuffer *buffer = new CL_PixelBuffer(
			src_rect.get_width(), src_rect.get_height(),
			src_rect.get_width() * (needs_alpha ? 4 : 3),
			needs_alpha ? CL_PixelFormat::abgr8888 : CL_PixelFormat::bgr888); // ClanLib's ABGR is like OpenGL RGBA
	
		pixeldata.convert(buffer);

		format = needs_alpha ? CL_RGBA : CL_RGB;

		buffer->lock();	

		// Upload to OpenGL:
		clBindTexture(CL_TEXTURE_2D, handle);

		// change alignment
		clPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		const int bytesPerPixel = (buffer->get_format().get_depth() + 7) / 8;
		clPixelStorei(CL_UNPACK_ROW_LENGTH, buffer->get_pitch() / bytesPerPixel);

		// upload
		clTexSubImage2D(
			CL_TEXTURE_2D,            // target
			0,                        // level
			0, 0,                     // xoffset, yoffset
			src_rect.get_width(),     // width
			src_rect.get_height(),    // height
			format,                   // format
			CL_UNSIGNED_BYTE,         // type
			buffer->get_data());      // texels

		buffer->unlock();
		delete buffer;
	}

	// Is this really needed according to OpenGL standard? (already set in constructor)
	// Think its a Linux OpenGL driver bug.. -- MBN 25. jan 2004
	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_MIN_FILTER, CL_LINEAR);
	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_MAG_FILTER, CL_LINEAR);
	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_WRAP_S, CL_CLAMP_TO_EDGE);
	clTexParameteri(CL_TEXTURE_2D, CL_TEXTURE_WRAP_T, CL_CLAMP_TO_EDGE);

	if (selected_canvas) selected_canvas->sync_pbuffer();
}

void CL_Surface_Target_OpenGL::draw(
	CL_Surface_Generic *attributes,
	CL_Surface_DrawParams1 *params1,
	CL_GraphicContext *gc)
{
	if (selected_canvas) selected_canvas->sync_texture();

	state.blendfunc[0] = params1->blend_src;
	state.blendfunc[1] = params1->blend_dest;
	state.color[0] = params1->red[0];
	state.color[1] = params1->green[0];
	state.color[2] = params1->blue[0];
	state.color[3] = params1->alpha[0];
	state.texture_handle = handle;
	state.fillmode = fillmode_solid;

	CL_GLStateChecker::from_gc(gc)->add_quad(
		state,
		params1->destX,
		params1->destY,
		params1->srcX,
		params1->srcY);
}

/////////////////////////////////////////////////////////////////////////////
// CL_Surface_Target_OpenGL implementation:
