//Copyright Paul Reiche, Fred Ford. 1992-2002

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#if defined (GFXMODULE_SDL) && defined (HAVE_OPENGL)

#include "libs/graphics/sdl/opengl.h"
#include "bbox.h"
#include "scalers.h"
#include "libs/log.h"

typedef struct _gl_screeninfo {
	SDL_Surface *scaled;
	GLuint texture;
	BOOLEAN dirty, active;
	SDL_Rect updated;
} TFB_GL_SCREENINFO;

static TFB_GL_SCREENINFO GL_Screens[TFB_GFX_NUMSCREENS];

static int ScreenFilterMode;

static TFB_ScaleFunc scaler = NULL;
static BOOLEAN first_init = TRUE;

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define R_MASK 0xff000000
#define G_MASK 0x00ff0000
#define B_MASK 0x0000ff00
#define A_MASK 0x000000ff
#else
#define R_MASK 0x000000ff
#define G_MASK 0x0000ff00
#define B_MASK 0x00ff0000
#define A_MASK 0xff000000
#endif

static void TFB_GL_Preprocess (int force_full_redraw, int transition_amount, int fade_amount);
static void TFB_GL_Postprocess (void);
static void TFB_GL_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect);
static void TFB_GL_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect);
static void TFB_GL_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect);

static TFB_GRAPHICS_BACKEND opengl_scaled_backend = {
	TFB_GL_Preprocess,
	TFB_GL_Postprocess,
	TFB_GL_Scaled_ScreenLayer,
	TFB_GL_ColorLayer };

static TFB_GRAPHICS_BACKEND opengl_unscaled_backend = {
	TFB_GL_Preprocess,
	TFB_GL_Postprocess,
	TFB_GL_Unscaled_ScreenLayer,
	TFB_GL_ColorLayer };


static SDL_Surface *
Create_Screen (SDL_Surface *template, int w, int h)
{
	SDL_Surface *newsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h,
			template->format->BitsPerPixel,
			template->format->Rmask, template->format->Gmask,
			template->format->Bmask, 0);
	if (newsurf == 0) {
		log_add (log_Error, "Couldn't create screen buffers: %s",
				SDL_GetError());
	}
	return newsurf;
}

static int
ReInit_Screen (SDL_Surface **screen, SDL_Surface *template, int w, int h)
{
	if (*screen)
		SDL_FreeSurface (*screen);
	*screen = Create_Screen (template, w, h);
	
	return *screen == 0 ? -1 : 0;
}

static int
AttemptColorDepth (int flags, int width, int height, int bpp)
{
	int videomode_flags;
	ScreenColorDepth = bpp;
	ScreenWidthActual = width;
	ScreenHeightActual = height;

	switch (bpp) {
		case 15:
			SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 5);
			SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 5);
			SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 5);
			break;

		case 16:
			SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 5);
			SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 6);
			SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 5);
			break;

		case 24:
			SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8);
			SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8);
			SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8);
			break;

		case 32:
			SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8);
			SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8);
			SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8);
			break;
		default:
			break;
	}

	SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, 0);
	SDL_GL_SetAttribute (SDL_GL_DOUBLEBUFFER, 1);

	videomode_flags = SDL_OPENGL;
	if (flags & TFB_GFXFLAGS_FULLSCREEN)
		videomode_flags |= SDL_FULLSCREEN;
	videomode_flags |= SDL_ANYFORMAT;

	SDL_Video = SDL_SetVideoMode (ScreenWidthActual, ScreenHeightActual, 
		bpp, videomode_flags);
	if (SDL_Video == NULL)
	{
		log_add (log_Error, "Couldn't set OpenGL %ix%ix%i video mode: %s",
				ScreenWidthActual, ScreenHeightActual, bpp,
				SDL_GetError ());
		return -1;
	}
	else
	{
		log_add (log_Info, "Set the resolution to: %ix%ix%i"
				" (surface reports %ix%ix%i)",
				width, height, bpp,			 
				SDL_GetVideoSurface()->w, SDL_GetVideoSurface()->h,
				SDL_GetVideoSurface()->format->BitsPerPixel);

		log_add (log_Info, "OpenGL renderer: %s version: %s",
				glGetString (GL_RENDERER), glGetString (GL_VERSION));
	}
	return 0;
}

int
TFB_GL_ConfigureVideo (int driver, int flags, int width, int height)
{
	int i, texture_width, texture_height;
	GraphicsDriver = driver;

	if (AttemptColorDepth (flags, width, height, 32) &&
			AttemptColorDepth (flags, width, height, 24) &&
			AttemptColorDepth (flags, width, height, 16))
	{
		log_add (log_Error, "Couldn't set any OpenGL %ix%i video mode!",
			 width, height);
		return -1;
	}		

	if (format_conv_surf)
		SDL_FreeSurface (format_conv_surf);
	format_conv_surf = SDL_CreateRGBSurface (SDL_SWSURFACE, 0, 0, 32,
		R_MASK, G_MASK, B_MASK, A_MASK);
	if (format_conv_surf == NULL)
	{
		log_add (log_Error, "Couldn't create format_conv_surf: %s",
				SDL_GetError());
		return -1;
	}

	for (i = 0; i < TFB_GFX_NUMSCREENS; i++)
	{
		if (0 != ReInit_Screen (&SDL_Screens[i], format_conv_surf,
				ScreenWidth, ScreenHeight))
			return -1;
	}

	SDL_Screen = SDL_Screens[0];
	TransitionScreen = SDL_Screens[2];

	if (first_init)
	{
		for (i = 0; i < TFB_GFX_NUMSCREENS; i++)
		{
			GL_Screens[i].scaled = NULL;
			GL_Screens[i].dirty = TRUE;
			GL_Screens[i].active = TRUE;
		}
		GL_Screens[1].active = FALSE;
		first_init = FALSE;
	}

	if (GfxFlags & TFB_GFXFLAGS_SCALE_SOFT_ONLY)
	{
		for (i = 0; i < TFB_GFX_NUMSCREENS; i++)
		{
			if (!GL_Screens[i].active)
				continue;
			if (0 != ReInit_Screen (&GL_Screens[i].scaled, format_conv_surf,
					ScreenWidth * 2, ScreenHeight * 2))
			return -1;
		}

		texture_width = 1024;
		texture_height = 512;

		scaler = Scale_PrepPlatform (flags, SDL_Screen->format);
		graphics_backend = &opengl_scaled_backend;
	}
	else
	{
		texture_width = 512;
		texture_height = 256;

		scaler = NULL;
		graphics_backend = &opengl_unscaled_backend;
	}

	if (GfxFlags & TFB_GFXFLAGS_SCALE_ANY)
		ScreenFilterMode = GL_LINEAR;
	else
		ScreenFilterMode = GL_NEAREST;

	glViewport (0, 0, ScreenWidthActual, ScreenHeightActual);
	glClearColor (0,0,0,0);
	glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	SDL_GL_SwapBuffers ();
	glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	glDisable (GL_DITHER);
	glDepthMask(GL_FALSE);

	for (i = 0; i < TFB_GFX_NUMSCREENS; i++)
	{
		if (!GL_Screens[i].active)
			continue;
		glGenTextures (1, &GL_Screens[i].texture);
		glBindTexture (GL_TEXTURE_2D, GL_Screens[i].texture);
		glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, texture_width, texture_height,
				0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
	}

	return 0;
}

int
TFB_GL_InitGraphics (int driver, int flags, int width, int height)
{
	char VideoName[256];

	log_add (log_Info, "Initializing SDL with OpenGL support.");

	SDL_VideoDriverName (VideoName, sizeof (VideoName));
	log_add (log_Info, "SDL driver used: %s", VideoName);
	log_add (log_Info, "SDL initialized.");
	log_add (log_Info, "Initializing Screen.");

	ScreenWidth = 320;
	ScreenHeight = 240;

	if (TFB_GL_ConfigureVideo (driver, flags, width, height))
	{
		log_add (log_Fatal, "Could not initialize video: "
				"no fallback at start of program!");
		exit (EXIT_FAILURE);
	}	 

	// Initialize scalers (let them precompute whatever)
	Scale_Init ();

	return 0;
}

void TFB_GL_UploadTransitionScreen (void)
{
	GL_Screens[TFB_SCREEN_TRANSITION].updated.x = 0;
	GL_Screens[TFB_SCREEN_TRANSITION].updated.y = 0;
	GL_Screens[TFB_SCREEN_TRANSITION].updated.w = ScreenWidth;
	GL_Screens[TFB_SCREEN_TRANSITION].updated.h = ScreenHeight;
	GL_Screens[TFB_SCREEN_TRANSITION].dirty = TRUE;
}

void
TFB_GL_ScanLines (void)
{
	int y;

	glDisable (GL_TEXTURE_2D);
	glEnable (GL_BLEND);
	glBlendFunc (GL_DST_COLOR, GL_ZERO);
	glColor3f (0.85f, 0.85f, 0.85f);
	for (y = 0; y < ScreenHeightActual; y += 2)
	{
		glBegin (GL_LINES);
		glVertex2i (0, y);
		glVertex2i (ScreenWidthActual, y);
		glEnd ();
	}

	glBlendFunc (GL_DST_COLOR, GL_ONE);
	glColor3f (0.2f, 0.2f, 0.2f);
	for (y = 1; y < ScreenHeightActual; y += 2)
	{
		glBegin (GL_LINES);
		glVertex2i (0, y);
		glVertex2i (ScreenWidthActual, y);
		glEnd ();
	}
}

static void
TFB_GL_DrawQuad (SDL_Rect *r)
{
	if (r != NULL)
	{
		float scale_x = (ScreenWidthActual / (float)ScreenWidth);
		float scale_y = (ScreenHeightActual / (float)ScreenHeight);

		glScissor (
			(GLint) (r->x * scale_x),
			(GLint) ((ScreenHeight - (r->y + r->h)) * scale_y),
			(GLsizei) (r->w * scale_x),
			(GLsizei) (r->h * scale_y)
		);		
		glEnable (GL_SCISSOR_TEST);
	}
	glBegin (GL_TRIANGLE_FAN);
	glTexCoord2f (0, 0);
	glVertex2i (0, 0);
	glTexCoord2f (ScreenWidth / 512.0f, 0);
	glVertex2i (ScreenWidthActual, 0);	
	glTexCoord2f (ScreenWidth / 512.0f, ScreenHeight / 256.0f);
	glVertex2i (ScreenWidthActual, ScreenHeightActual);
	glTexCoord2f (0, ScreenHeight / 256.0f);
	glVertex2i (0, ScreenHeightActual);
	glEnd ();
	if (r != NULL)
	{
		glDisable (GL_SCISSOR_TEST);
	}
}

static void
TFB_GL_Preprocess (int force_full_redraw, int transition_amount, int fade_amount)
{
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	glOrtho (0,ScreenWidthActual,ScreenHeightActual, 0, -1, 1);
	glMatrixMode (GL_MODELVIEW);
	glLoadIdentity ();
	(void) force_full_redraw;
	(void) transition_amount;
	(void) fade_amount;
	if (TFB_BBox.valid)
	{
		GL_Screens[TFB_SCREEN_MAIN].updated.x = TFB_BBox.region.corner.x;
		GL_Screens[TFB_SCREEN_MAIN].updated.y = TFB_BBox.region.corner.y;
		GL_Screens[TFB_SCREEN_MAIN].updated.w = TFB_BBox.region.extent.width;
		GL_Screens[TFB_SCREEN_MAIN].updated.h = TFB_BBox.region.extent.height;
		GL_Screens[TFB_SCREEN_MAIN].dirty = TRUE;
	}
}

static void
TFB_GL_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect)
{
	glBindTexture (GL_TEXTURE_2D, GL_Screens[screen].texture);
	glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	if (GL_Screens[screen].dirty)
	{
		glPixelStorei (GL_UNPACK_ROW_LENGTH, ScreenWidth);
		/* Matrox OpenGL drivers do not handle GL_UNPACK_SKIP_*
		   correctly */
		glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);
		glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
		SDL_LockSurface (SDL_Screens[screen]);
		glTexSubImage2D (GL_TEXTURE_2D, 0, GL_Screens[screen].updated.x, 
				GL_Screens[screen].updated.y,
				GL_Screens[screen].updated.w, 
				GL_Screens[screen].updated.h,
				GL_RGBA, GL_UNSIGNED_BYTE,
				(Uint32 *)SDL_Screens[screen]->pixels +
					(GL_Screens[screen].updated.y * ScreenWidth + 
					GL_Screens[screen].updated.x));
		SDL_UnlockSurface (SDL_Screens[screen]);
		GL_Screens[screen].dirty = FALSE;
	}

	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ScreenFilterMode);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ScreenFilterMode);
	glEnable (GL_TEXTURE_2D);

	if (a == 255)
	{
		glDisable (GL_BLEND);
		glColor4f (1, 1, 1, 1);
	}
	else
	{
		float a_f = a / 255.0f;
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glEnable (GL_BLEND);
		glColor4f (1, 1, 1, a_f);
	}
	
	TFB_GL_DrawQuad (rect);
}

static void
TFB_GL_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect)
{
	glBindTexture (GL_TEXTURE_2D, GL_Screens[screen].texture);
	glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	if (GL_Screens[screen].dirty)
	{
		scaler (SDL_Screens[screen], GL_Screens[screen].scaled, &GL_Screens[screen].updated);
		glPixelStorei (GL_UNPACK_ROW_LENGTH, ScreenWidth * 2);

		 /* Matrox OpenGL drivers do not handle GL_UNPACK_SKIP_*
		    correctly */
		glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);
		glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
		SDL_LockSurface (GL_Screens[screen].scaled);
		glTexSubImage2D (GL_TEXTURE_2D, 0, GL_Screens[screen].updated.x * 2, 
				GL_Screens[screen].updated.y * 2,
				GL_Screens[screen].updated.w * 2, 
				GL_Screens[screen].updated.h * 2,
				GL_RGBA, GL_UNSIGNED_BYTE,
				(Uint32 *)GL_Screens[screen].scaled->pixels +
				(GL_Screens[screen].updated.y * 4 * ScreenWidth + 
				GL_Screens[screen].updated.x * 2));
		SDL_UnlockSurface (GL_Screens[screen].scaled);
		GL_Screens[screen].dirty = FALSE;
	}

	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ScreenFilterMode);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ScreenFilterMode);
	glEnable (GL_TEXTURE_2D);

	if (a == 255)
	{
		glDisable (GL_BLEND);
		glColor4f (1, 1, 1, 1);
	}
	else
	{
		float a_f = a / 255.0f;
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glEnable (GL_BLEND);
		glColor4f (1, 1, 1, a_f);
	}
	
	TFB_GL_DrawQuad (rect);
}

static void
TFB_GL_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect)
{
	float r_f = r / 255.0f;
	float g_f = g / 255.0f;
	float b_f = b / 255.0f;
	float a_f = a / 255.0f;
	glColor4f(r_f, g_f, b_f, a_f);

	glDisable (GL_TEXTURE_2D);
	if (a != 255)
	{
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glEnable (GL_BLEND);
	}
	else
	{
		glDisable (GL_BLEND);
	}
	
	TFB_GL_DrawQuad (rect);
}

static void
TFB_GL_Postprocess (void)
{
	if (GfxFlags & TFB_GFXFLAGS_SCANLINES)
		TFB_GL_ScanLines ();

	SDL_GL_SwapBuffers ();
}	

#endif
