/*  $Id: display_window_opengl.cpp,v 1.37 2004/03/10 22:30: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 "display_window_opengl.h"
#include "GL/graphic_context_opengl.h"
#include "GL/pixel_buffer_opengl_frame.h"
#include "GL/gl_state_checker.h"
#include "API/Display/display_window_description.h"
#include "API/Core/System/error.h"
#include "API/GL/opengl_state.h"
#include "API/GL/opengl_wrap.h"
#include "API/GL/opengl_window_description.h"
#include "GL/opengl_window_description_generic.h"

#ifndef AC_SRC_ALPHA
#define AC_SRC_ALPHA 1
#endif

#ifndef ULW_OPAQUE
#define ULW_OPAQUE 0
#endif

#ifndef ULW_COLORKEY
#define ULW_COLORKEY 1
#endif

#ifndef ULW_ALPHA
#define ULW_ALPHA 2
#endif

/////////////////////////////////////////////////////////////////////////////
// Construction:

CL_DisplayWindow_OpenGL::CL_DisplayWindow_OpenGL()
{
	layered_dc = 0;
	layered_bitmap = 0;
	context = 0;
	hdc = 0;
	gc = CL_GraphicContext(new CL_GraphicContext_OpenGL(this));
	buffer_front = CL_PixelBuffer(new CL_PixelBuffer_OpenGL_Frame(CL_FRONT, gc));
	buffer_back = CL_PixelBuffer(new CL_PixelBuffer_OpenGL_Frame(CL_BACK, gc));
	user32_dll = 0;
}

CL_DisplayWindow_OpenGL::~CL_DisplayWindow_OpenGL()
{
	destroy_window();
	if (layered_bitmap)
	{
		SelectObject(layered_dc, layered_orig_bitmap);
		DeleteObject(layered_bitmap);
	}
	if (layered_dc) DeleteDC(layered_dc);
	if (user32_dll) FreeLibrary(user32_dll);
}

/////////////////////////////////////////////////////////////////////////////
// Attributes:

CL_GraphicContext *CL_DisplayWindow_OpenGL::get_gc()
{
	return &gc;
}

CL_PixelBuffer &CL_DisplayWindow_OpenGL::get_buffer(int i)
{
	if (i == 0) return buffer_front;
	else return buffer_back;
}

const CL_PixelBuffer &CL_DisplayWindow_OpenGL::get_buffer(int i) const
{
	if (i == 0) return buffer_front;
	else return buffer_back;
}

int CL_DisplayWindow_OpenGL::get_buffer_count() const
{
	return 2;
}

HDC CL_DisplayWindow_OpenGL::get_dc()
{
	if (layered_dc) return layered_dc;
	return hdc;
}

/////////////////////////////////////////////////////////////////////////////
// Operations:

void CL_DisplayWindow_OpenGL::create_window(const CL_DisplayWindowDescription &desc)
{
	if (desc.is_fullscreen())
	{
		set_videomode(desc.get_size().width, desc.get_size().height, desc.get_bpp(), desc.get_refresh_rate());
	}
	else
	{
		restore_videomode();
	}

	CL_DisplayWindow_Win32::create_window(desc);

	if (context) return; // OpenGL already setup for this window

	// Setup OpenGL:

	hdc = GetDC(get_hwnd());
	
	PIXELFORMATDESCRIPTOR pfd;
	memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;

	CL_OpenGLWindowDescription_Generic *gldesc = dynamic_cast<CL_OpenGLWindowDescription_Generic*>(desc.impl);
	if (gldesc)
	{
		pfd.iPixelType = (gldesc->rgba) ? PFD_TYPE_RGBA : PFD_TYPE_COLORINDEX;
		if (gldesc->doublebuffer) pfd.dwFlags |= PFD_DOUBLEBUFFER;
		if (gldesc->stereo) pfd.dwFlags |= PFD_STEREO;
		pfd.cColorBits = gldesc->buffer_size;
		pfd.iLayerType = gldesc->level; // MSDN docs say this one is ignored! -- mbn 12. jan 2004
		pfd.cAuxBuffers = gldesc->aux_buffers;
		pfd.cRedBits = gldesc->red_size;
		pfd.cGreenBits = gldesc->green_size;
		pfd.cBlueBits = gldesc->blue_size;
		pfd.cAlphaBits = gldesc->alpha_size;
		pfd.cDepthBits = gldesc->depth_size;
		pfd.cStencilBits = gldesc->stencil_size;
		pfd.cAccumRedBits = gldesc->accum_red_size;
		pfd.cAccumGreenBits = gldesc->accum_green_size;
		pfd.cAccumBlueBits = gldesc->accum_blue_size;
		pfd.cAccumAlphaBits = gldesc->accum_alpha_size;
		// gldesc dont have an accum bits, so trying to calculate it:
		pfd.cAccumBits = (pfd.cAccumRedBits + pfd.cAccumGreenBits + pfd.cAccumBlueBits + pfd.cAccumAlphaBits + 7) / 8;
		pfd.cAccumBits *= 8;
		
	}
	else
	{
		pfd.iPixelType = PFD_TYPE_RGBA;
		pfd.iLayerType = PFD_MAIN_PLANE;
		pfd.dwFlags |= PFD_DOUBLEBUFFER;
		pfd.cColorBits = 24;
		pfd.cDepthBits = 16;
		pfd.cStencilBits = 8;
	}

	if (desc.is_layered())
	{
		pfd.cAlphaBits = 8;
		pfd.dwFlags |= PFD_DRAW_TO_BITMAP | PFD_DOUBLEBUFFER_DONTCARE;
	}

	if (desc.is_layered())
	{
		if (user32_dll == 0)
		{
			user32_dll = LoadLibrary("user32.dll");
			if (user32_dll == 0) throw CL_Error("Unable to load user32.dll!");

			ptr_UpdateLayeredWindow = (PtrUpdateLayeredWindow) GetProcAddress(user32_dll, "UpdateLayeredWindow");
			if (ptr_UpdateLayeredWindow == 0)
			{
				FreeLibrary(user32_dll);
				user32_dll = 0;
				throw CL_Error("Layered windows require Windows 2000 or Windows XP!");
			}
		}

		layered_dc = CreateCompatibleDC(hdc);
		cl_assert(layered_dc != 0);

		layered_bitmap = CreateCompatibleBitmap(hdc, desc.get_size().width, desc.get_size().height);
		cl_assert(layered_bitmap != 0);
		layered_orig_bitmap = (HBITMAP) SelectObject(layered_dc, layered_bitmap);
	}

	int pixelformat = ChoosePixelFormat(get_dc(), &pfd);
	SetPixelFormat(get_dc(), pixelformat, &pfd);

	context = wglCreateContext(get_dc());
	cl_assert(context != NULL);

	if (!opengl_contexts.empty()) wglShareLists(opengl_contexts.back(), context);
	opengl_contexts.push_back(context);

	ShowWindow(get_hwnd(), SW_SHOW);
	UpdateWindow(get_hwnd());
}

void CL_DisplayWindow_OpenGL::destroy_window()
{
	if (context)
	{
		wglMakeCurrent(0, 0);
		wglDeleteContext(context);
		context = 0;
		opengl_contexts.remove(context);
	}

	if (layered_bitmap)
	{
		SelectObject(layered_dc, layered_orig_bitmap);
		DeleteObject(layered_bitmap);

		layered_bitmap = 0;
	}

	if (layered_dc) DeleteDC(layered_dc);
	layered_dc = 0;

	if (is_fullscreen()) restore_videomode();
	CL_DisplayWindow_Win32::destroy_window();
}

bool CL_DisplayWindow_OpenGL::received_event(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_DESTROY:
		if (context)
		{
			wglMakeCurrent(0, 0);
			wglDeleteContext(context);
			context = 0;
			opengl_contexts.remove(context);
		}
		break;
	}

	return CL_DisplayWindow_Win32::received_event(uMsg, wParam, lParam);
}

void CL_DisplayWindow_OpenGL::flip(int interval)
{
	CL_GLStateChecker::from_gc(get_gc())->flush();
	CL_DisplayWindow_Win32::flip(interval);

	if (interval != -1)
	{
		typedef BOOL (APIENTRY *ptr_wglSwapIntervalEXT)(int interval);
		ptr_wglSwapIntervalEXT wglSwapIntervalEXT = (ptr_wglSwapIntervalEXT) wglGetProcAddress("wglSwapIntervalEXT");
		if (wglSwapIntervalEXT)
		{
			wglSwapIntervalEXT(interval);
		}
	}

	BOOL retval = SwapBuffers(get_dc());

	repaint_layered();
}

void CL_DisplayWindow_OpenGL::update(const CL_Rect &rect)
{
	CL_OpenGLState state(get_gc());
	state.set_active();
	state.setup_2d();

	clReadBuffer(CL_BACK);

	if (clGetError() == CL_NO_ERROR)		// Check if we got more than 1 buffer.
	{
		CLfloat raster_pos[4];

		clGetFloatv(CL_CURRENT_RASTER_POSITION, raster_pos);

		clDrawBuffer(CL_FRONT);
		clRasterPos3f((CLfloat) rect.left, (CLfloat) rect.bottom, raster_pos[2]);

		clCopyPixels(rect.top, rect.left, rect.get_width(), rect.get_height(), CL_COLOR);

		// Return to drawing to the back buffer
		clDrawBuffer(CL_BACK);
	}

	repaint_layered();
}

/////////////////////////////////////////////////////////////////////////////
// Implementation:

void CL_DisplayWindow_OpenGL::repaint_layered()
{
	if (layered_dc == 0) return; // Not a layered window.
	if (ptr_UpdateLayeredWindow == 0) return; // Not supported on this platform.

	POINT layer_src_position;
	layer_src_position.x = 0;
	layer_src_position.y = 0;

	POINT layer_dest_position;
	layer_dest_position.x = get_layer_position().left;
	layer_dest_position.y = get_layer_position().top;

	SIZE layer_size;
	layer_size.cx = get_width();
	layer_size.cy = get_height();

	BLENDFUNCTION blendfunc;
	blendfunc.BlendOp = AC_SRC_OVER;
	blendfunc.BlendFlags = 0;
	blendfunc.SourceConstantAlpha = 255;
	blendfunc.AlphaFormat = AC_SRC_ALPHA;

	ptr_UpdateLayeredWindow(
		get_hwnd(),
		hdc,
		&layer_dest_position,
		&layer_size,
		layered_dc,
		&layer_src_position,
		RGB(0,0,0),
		&blendfunc,
		ULW_COLORKEY/*ULW_ALPHA*/);
}

std::list<HGLRC> CL_DisplayWindow_OpenGL::opengl_contexts;
