/*
Copyright (C) 1997-2001 Id Software, Inc.

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.

*/
/*
** GLW_IMP.C
**
** This file contains ALL Linux specific stuff having to do with the
** OpenGL refresh.  When a port is being made the following functions
** must be implemented by the port:
**
** GLimp_EndFrame
** GLimp_Init
** GLimp_Shutdown
** GLimp_SwitchFullscreen
**
*/

#include <termios.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <dlfcn.h>

#include "x11.h"

#include "../ref_gl/r_local.h"
#include "../client/keys.h"

#include "glw_linux.h"

#define DISPLAY_MASK (VisibilityChangeMask | StructureNotifyMask | ExposureMask )
#define INIT_MASK (KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | DISPLAY_MASK )

#define KEY_MASK (KeyPressMask | KeyReleaseMask)
#define MOUSE_MASK (ButtonPressMask | ButtonReleaseMask |  PointerMotionMask | ButtonMotionMask )
#define X_MASK (KEY_MASK | MOUSE_MASK | VisibilityChangeMask | StructureNotifyMask )

x11display_t x11display;

glwstate_t glw_state;

static qboolean _xf86_vidmodes_supported = qfalse;
static XF86VidModeModeInfo **_xf86_vidmodes;
static int _xf86_vidmodes_num;
static qboolean _xf86_vidmodes_active = qfalse;

static void _xf86_VidmodesInit(void)
{
	int MajorVersion, MinorVersion;

	// Get video mode list
	MajorVersion = MinorVersion = 0;

	if (XF86VidModeQueryVersion (x11display.dpy, &MajorVersion, &MinorVersion))
	{
		Com_Printf("..XFree86-VidMode Extension Version %d.%d\n", MajorVersion, MinorVersion);
		XF86VidModeGetAllModeLines(x11display.dpy, x11display.scr, &_xf86_vidmodes_num, &_xf86_vidmodes);
		_xf86_vidmodes_supported = qtrue;
	} else {
		Com_Printf("..XFree86-VidMode Extension not available\n");
		_xf86_vidmodes_supported = qfalse;
	}
}

static void _xf86_VidmodesFree(void)
{
	if (_xf86_vidmodes_supported) XFree(_xf86_vidmodes);

	_xf86_vidmodes_supported = qfalse;
}

static void _xf86_VidmodesSwitch(int mode)
{
	if (_xf86_vidmodes_supported) {
		XF86VidModeSwitchToMode(x11display.dpy, x11display.scr, _xf86_vidmodes[mode]);
		XF86VidModeSetViewPort(x11display.dpy, x11display.scr, 0, 0);
	}

	_xf86_vidmodes_active = qtrue;
}

static void _xf86_VidmodesSwitchBack(void)
{
	if (_xf86_vidmodes_supported && _xf86_vidmodes_active)
		_xf86_VidmodesSwitch(0);

	_xf86_vidmodes_active = 0;
}

static void _xf86_VidmodesFindBest(int *mode, int *pwidth, int *pheight){
	int i, best_fit, best_dist, dist, x, y;

	best_fit = -1;
	best_dist = 999999999;

	if (_xf86_vidmodes_supported) {
		for (i = 0; i <_xf86_vidmodes_num; i++) {
			if (_xf86_vidmodes[i]->hdisplay <*pwidth || _xf86_vidmodes[i]->vdisplay <*pheight)
				continue;

			x = _xf86_vidmodes[i]->hdisplay - *pwidth;
			y = _xf86_vidmodes[i]->vdisplay - *pheight;

			if (x > y) dist = y;
			else dist = x;

			if (dist < 0) dist = -dist; // Only positive number please
		
			if( dist < best_dist) {
				best_dist = dist;
				best_fit = i;
			}

			Com_Printf("%ix%i -> %ix%i: %i\n", *pwidth, *pheight, _xf86_vidmodes[i]->hdisplay, _xf86_vidmodes[i]->vdisplay, dist);
		}

		if (best_fit >= 0) {
			Com_Printf("%ix%i selected\n", _xf86_vidmodes[best_fit]->hdisplay, _xf86_vidmodes[best_fit]->vdisplay);

			*pwidth =_xf86_vidmodes[best_fit]->hdisplay;
			*pheight =_xf86_vidmodes[best_fit]->vdisplay;
		}
	}

	*mode = best_fit;

}

static void _x11_SetNoResize(Window w, int width, int height)
{
	XSizeHints *hints;

	if (x11display.dpy) {
		hints = XAllocSizeHints();

		if (hints) {
			hints->min_width = hints->max_width = width;
			hints->min_height = hints->max_height = height;

			hints->flags = PMaxSize | PMinSize;

			XSetWMNormalHints(x11display.dpy, w, hints);
			XFree(hints);
		}
	}
}


/*****************************************************************************/

static void signal_handler(int sig)
{
	printf("Received signal %d, exiting...\n", sig);
	GLimp_Shutdown();
	_exit(0);
}

static void InitSig(void) 
{
	signal(SIGHUP, signal_handler);
	signal(SIGQUIT, signal_handler);
	signal(SIGILL, signal_handler);
	signal(SIGTRAP, signal_handler);
	signal(SIGIOT, signal_handler);
	signal(SIGBUS, signal_handler);
	signal(SIGFPE, signal_handler);
	signal(SIGSEGV, signal_handler);
	signal(SIGTERM, signal_handler);
}

/*
** GLimp_SetMode
*/
int GLimp_SetMode(int mode, qboolean fullscreen)
{
	int width, height, screen_width, screen_height, screen_mode;
	float ratio;
	XSetWindowAttributes wa;
	unsigned long mask;

	if (!VID_GetModeInfo( &width, &height, mode)) {
		Com_Printf( " invalid mode\n" );
		return rserr_invalid_mode;
	}

	screen_width = width;
	screen_height = height;

	if (fullscreen) {
		_xf86_VidmodesFindBest (&screen_mode, &screen_width, &screen_height);

		if (screen_mode < 0) {
			Com_Printf(" no mode found\n");
			return rserr_invalid_mode;
		}

		if (screen_width < width || screen_height < height) {
			if (width > height) {
				ratio = width / height;
				height = height *ratio;
				width = screen_width;
			} else {
				ratio = height / width;
				width = width *ratio;
				height = screen_height;
			}
		}

		Com_Printf ("...setting fullscreen mode %d:", mode);

		/* Create fulscreen window */
		x11display.old_win = x11display.win;

		wa.background_pixel = 0;
		wa.border_pixel = 0;
		wa.event_mask = INIT_MASK;
		wa.override_redirect = True;
		mask = CWBackPixel | CWBorderPixel | CWEventMask | CWOverrideRedirect;

		x11display.win = XCreateWindow(x11display.dpy, x11display.root, 0, 0, screen_width, screen_height,
		                               0, CopyFromParent, InputOutput, CopyFromParent, mask, &wa);

		XResizeWindow (x11display.dpy, x11display.gl_win, width, height);
		XReparentWindow (x11display.dpy, x11display.gl_win, x11display.win, (screen_width/2)-(width/2), (screen_height/2)-(height/2));

		XMapWindow (x11display.dpy, x11display.gl_win);
		XMapWindow (x11display.dpy, x11display.win);
	
		_x11_SetNoResize (x11display.win, screen_width, screen_height);

		_xf86_VidmodesSwitch (screen_mode);
	} else {
		Com_Printf ("...setting mode %d:", mode);

		/* Create managed window */
		x11display.old_win = x11display.win;

		wa.background_pixel = 0;
		wa.border_pixel = 0;
		wa.event_mask = INIT_MASK;
		mask = CWBackPixel | CWBorderPixel | CWEventMask;

		x11display.win = XCreateWindow (x11display.dpy, x11display.root, 0, 0, screen_width, screen_height,
		                                0, CopyFromParent, InputOutput, CopyFromParent, mask, &wa);
		XSetStandardProperties (x11display.dpy, x11display.win, APPLICATION, None, None, NULL, 0, NULL);
		x11display.wmDeleteWindow = XInternAtom(x11display.dpy, "WM_DELETE_WINDOW", False);
		XSetWMProtocols (x11display.dpy, x11display.win, &x11display.wmDeleteWindow, 1);

		XResizeWindow (x11display.dpy, x11display.gl_win, width, height);
		XReparentWindow (x11display.dpy, x11display.gl_win, x11display.win, 0, 0);

		XMapWindow (x11display.dpy, x11display.gl_win);
		XMapWindow (x11display.dpy, x11display.win);
	
		_x11_SetNoResize(x11display.win, width, height);
	}

	XSetIconName(x11display.dpy, x11display.win, APPLICATION);
	XStoreName(x11display.dpy, x11display.win, APPLICATION);

	// save the parent window size for mouse use. this is not the gl context window
	x11display.win_width = width;
	x11display.win_height = height;

	if (x11display.old_win) {
		XDestroyWindow (x11display.dpy, x11display.old_win);
		x11display.old_win = 0;
	}
	
	XFlush (x11display.dpy);

	glState.width = width;
	glState.height = height;
	glState.fullScreen = fullscreen;

	// let the sound and input subsystems know about the new window
	VID_NewWindow (width, height);

	return rserr_ok;
}

/*
** GLimp_Shutdown
*/
void GLimp_Shutdown (void)
{
	if (x11display.dpy) {
		_xf86_VidmodesSwitchBack();
		_xf86_VidmodesFree();
		
		if (x11display.cmap) XFreeColormap(x11display.dpy, x11display.cmap);
		if (x11display.ctx) qglXDestroyContext(x11display.dpy, x11display.ctx);
		if (x11display.gl_win) XDestroyWindow(x11display.dpy, x11display.gl_win);
		if (x11display.win) XDestroyWindow(x11display.dpy, x11display.win);

		XCloseDisplay(x11display.dpy);
	}

	x11display.visinfo = NULL;
	x11display.cmap = 0;
	x11display.ctx = NULL;
	x11display.gl_win = 0;
	x11display.win = 0;
	x11display.dpy = NULL;
}

static qboolean gotstencil = qfalse; // evil hack!
static qboolean ChooseVisual(int colorbits, int stencilbits)
{
	int colorsize;
	int depthbits = colorbits;

	if (colorbits == 24) colorsize = 8;
	else colorsize = 4;

	{
		int attributes[] = {
			GLX_RGBA,
			GLX_DOUBLEBUFFER,
			GLX_RED_SIZE, colorsize,
			GLX_GREEN_SIZE, colorsize,
			GLX_BLUE_SIZE, colorsize,
			GLX_DEPTH_SIZE, depthbits,
			GLX_STENCIL_SIZE, stencilbits,
			None
		};

		x11display.visinfo = qglXChooseVisual(x11display.dpy, x11display.scr, attributes);
		if (!x11display.visinfo) {
			Com_Printf("..Failed to get colorbits %i, depthbits %i, stencilbits %i\n", colorbits, depthbits, stencilbits);
			return qfalse;
		} else {
			Com_Printf("..Got colorbits %i, depthbits %i, stencilbits %i\n", colorbits, depthbits, stencilbits);
			if( stencilbits > 0 ) gotstencil = qtrue;
			return qtrue;
		}
	}
}

/*
** GLimp_Init
*/
int GLimp_Init( void *hinstance, void *wndproc)
{
	int colorbits, stencilbits;
	XSetWindowAttributes attr;
	unsigned long mask;

	if (x11display.dpy) GLimp_Shutdown();

	InitSig();

	Com_Printf("Display initialization\n");

	x11display.dpy = XOpenDisplay(NULL);
	if (!x11display.dpy) {
		Com_Printf ("..Error couldn't open the X display\n");
		return 0;
	}

	x11display.scr = DefaultScreen(x11display.dpy);
	x11display.root = RootWindow(x11display.dpy, x11display.scr);

	_xf86_VidmodesInit();

	if (r_colorbits->integer == 16 || r_colorbits->integer == 24) colorbits = r_colorbits->integer;
	else colorbits = 0;

	if (r_stencilbits->integer == 8 || r_stencilbits->integer == 16) stencilbits = r_stencilbits->integer;
	else stencilbits = 0;

	gotstencil = qfalse;
	if (colorbits > 0) {
		ChooseVisual(colorbits, stencilbits);
		if (!x11display.visinfo && stencilbits > 8) ChooseVisual(colorbits, 8);
		if (!x11display.visinfo && stencilbits > 0) ChooseVisual(colorbits, 0);
		if (!x11display.visinfo && colorbits > 16) ChooseVisual(16, 0);
	} else {
		ChooseVisual(24, stencilbits);
		if (!x11display.visinfo) ChooseVisual(16, stencilbits);
		if (!x11display.visinfo && stencilbits > 8) ChooseVisual(24, 8);
		if (!x11display.visinfo && stencilbits > 8) ChooseVisual(16, 8);
		if (!x11display.visinfo && stencilbits > 0) ChooseVisual(24, 0);
		if (!x11display.visinfo && stencilbits > 0) ChooseVisual(16, 0);
	}

	if (!x11display.visinfo) {
		GLimp_Shutdown(); // hope this doesn't do anything evil when we don't have window etc.
		Com_Printf("..Error couldn't set GLX visual\n");
		return 0;
	}

	glState.stencilEnabled = gotstencil;

	x11display.ctx = qglXCreateContext(x11display.dpy, x11display.visinfo, NULL, True);
	x11display.cmap = XCreateColormap(x11display.dpy, x11display.root, x11display.visinfo->visual, AllocNone);

	attr.colormap = x11display.cmap;
	attr.border_pixel = 0;
	attr.event_mask = DISPLAY_MASK;
	attr.override_redirect = False;
	mask = CWBorderPixel | CWColormap;

	x11display.gl_win = XCreateWindow(x11display.dpy, x11display.root, 0, 0, 1, 1, 0,
	                                  x11display.visinfo->depth, InputOutput, x11display.visinfo->visual, mask, &attr);
	qglXMakeCurrent(x11display.dpy, x11display.gl_win, x11display.ctx);

	XSync (x11display.dpy, False);

	return 1;
}

/*
** GLimp_BeginFrame
*/
void GLimp_BeginFrame( void )
{
}

/*
** GLimp_EndFrame
** 
** Responsible for doing a swapbuffers and possibly for other stuff
** as yet to be determined.  Probably better not to make this a GLimp
** function and instead do a call to GLimp_SwapBuffers.
*/
void GLimp_EndFrame (void)
{
	qglXSwapBuffers(x11display.dpy, x11display.gl_win);
}

/*
** GLimp_BeginFrame
*/
qboolean GLimp_GetGammaRamp( size_t stride, unsigned short *ramp )
{
	if (XF86VidModeGetGammaRamp( x11display.dpy, x11display.scr, stride, ramp, ramp + stride, ramp + (stride << 1) ) != 0)
		return qtrue;
	return qfalse;
}

/*
** GLimp_BeginFrame
*/
void GLimp_SetGammaRamp( size_t stride, unsigned short *ramp )
{
    XF86VidModeSetGammaRamp(x11display.dpy, x11display.scr, stride, ramp, ramp + stride, ramp + (stride << 1));
}

/*
** GLimp_AppActivate
*/
void GLimp_AppActivate( qboolean active )
{
}
