#include <gtk/gtk.h>

#if defined(__MSW__)
# include <gdk/gdkwin32.h>
# define HAVE_WIN32
#else
# include <gdk/gdkx.h>
# define HAVE_X
# ifdef HAVE_XF86_VIDMODE
#  include <X11/extensions/xf86vmode.h>
# endif
#endif

#include "guifullscreen.h"


static gint gui_vidmode_supported = -1;


gboolean gdk_video_mode_is_supported(void);

GdkVideoModeInfo *gdk_video_mode_new(void);
void gdk_video_mode_delete(GdkVideoModeInfo *m);

GList *gdk_video_modes_get(void);
void gdk_video_modes_delete(GList *glist);

GdkVideoModeInfo *gdk_video_mode_get_current(GList *glist);

gboolean gdk_video_mode_get_viewport(gint *x_rtn, gint *y_rtn);
void gdk_video_mode_set_viewport(gint x, gint y);

gboolean gdk_video_mode_switch(const GdkVideoModeInfo *m);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)


/*
 *	Checks if video modes are supported.
 */
gboolean gdk_video_mode_is_supported(void)
{
	/* Did not check if video modes are supported for the first
	 * time?
	 */
	if(gui_vidmode_supported < 0)
	{
#if defined(HAVE_WIN32)
	    gui_vidmode_supported = 1;
#elif defined(HAVE_X) && defined(HAVE_XF86_VIDMODE)
	    Display *dpy = GDK_DISPLAY();
	    int xvidmode_event_base, xvidmode_error_base;

	    /* XF86VidMode supported? */
	    if(XF86VidModeQueryExtension(
		dpy, &xvidmode_event_base, &xvidmode_error_base
	    ))
		gui_vidmode_supported = 1;
	    else
		gui_vidmode_supported = 0;
#else
	    gui_vidmode_supported = 0;
#endif
	    return((gboolean)gui_vidmode_supported);
	}
	else
	{
	    return((gboolean)gui_vidmode_supported);
	}
}


/*
 *	Creates a new Video Mode.
 */
GdkVideoModeInfo *gdk_video_mode_new(void)
{
	return(GDK_VIDEO_MODE_INFO(
	    g_malloc0(sizeof(GdkVideoModeInfo))
	));
}

/*
 *	Deletes the Video Mode.
 */
void gdk_video_mode_delete(GdkVideoModeInfo *m)
{
	if(m == NULL)
	    return;

	g_free(m);
}


/*
 *	Returns a list of Video Modes.
 */
GList *gdk_video_modes_get(void)
{
#if defined(HAVE_WIN32)
	int i;
	gboolean is_dup;
	DWORD dwFields;
	DEVMODE dmSettings;

	GList *glist = NULL, *glist2;
	GdkVideoModeInfo *m;

	dmSettings.dmSize = sizeof(DEVMODE);

	/* Iterate through all display devices */
	for(
	    i = 0;
	    EnumDisplaySettings(
		NULL,		/* Current display device name */
		i,              /* Device number */
		&dmSettings     /* Return values */
	    );
	    i++
	)
	{
	    /* Check for duplicate */
	    for(is_dup = False,
		glist2 = glist;
		glist2 != NULL;
		glist2 = g_list_next(glist2)
	    )
	    {
		m = GDK_VIDEO_MODE_INFO(glist2->data);
		if(m != NULL)
		{
		    if((m->viewport_width == (gint)dmSettings.dmPelsWidth) &&
		       (m->viewport_height == (gint)dmSettings.dmPelsHeight)
		    )
		    {
			is_dup = TRUE;
			break;
		    }
		}
	    }
	    if(is_dup) 
		continue;

	    /* Get flags for which members in dmSettings are defined */
	    dwFields = dmSettings.dmFields;

	    /* Append a new video mode info */
	    m = gdk_video_mode_new();
	    glist = g_list_append(glist, m);

	    /* First video mode in the list is assumed to be the default
	     * video mode
	     */
	    if(i == 0)
		m->flags |= GDK_VIDEO_MODE_DEFAULT;

	    m->dotclock = 0;

	    m->viewport_width = (dwFields & DM_PELSWIDTH) ?
		(gint)dmSettings.dmPelsWidth : 0;
	    m->viewport_height = (dwFields & DM_PELSHEIGHT) ?
		(gint)dmSettings.dmPelsHeight : 0;

	    m->total_width = m->viewport_width;
	    m->total_height = m->viewport_height;

	    m->hsyncstart = 0;
	    m->hsyncend = 0;

	    m->vsyncstart = 0;
	    m->vsyncend = 0;
	}

	return(glist);
#elif defined(HAVE_X) && defined(HAVE_XF86_VIDMODE)
	Display *dpy = GDK_DISPLAY();
	int scr = DefaultScreen(dpy);

	int i, total = 0;
	XF86VidModeModeInfo **xvidmode_list = NULL, *xvidmode;
	GList *glist = NULL;
	GdkVideoModeInfo *m;

	if(!gdk_video_mode_is_supported())
	    return(glist);

	/* Get list of X video modes */
	if(XF86VidModeGetAllModeLines(
	    dpy, scr, &total, &xvidmode_list
	))
	{
	    for(i = 0; i < total; i++)
	    {
		xvidmode = xvidmode_list[i];
		if(xvidmode == NULL)
		    continue;

		if(TRUE)
		{
		    m = gdk_video_mode_new();
		    glist = g_list_append(glist, m);

		    /* First video mode in the list is assumed to
		     * be the default video mode
		     */
		    if(i == 0)
			m->flags |= GDK_VIDEO_MODE_DEFAULT;

		    m->dotclock = (guint)xvidmode->dotclock;

		    m->viewport_width = (gint)xvidmode->hdisplay;
		    m->viewport_height = (gint)xvidmode->vdisplay;

		    m->total_width = (gint)xvidmode->htotal;
		    m->total_height = (gint)xvidmode->vtotal;

		    m->hsyncstart = (gint)xvidmode->hsyncstart;
		    m->hsyncend = (gint)xvidmode->hsyncend;

		    m->vsyncstart = (gint)xvidmode->vsyncstart;
		    m->vsyncend = (gint)xvidmode->vsyncend;
		}

		/* If xvidmode->private is not NULL then it needs to
		 * be deleted
		 *
		 * Currently this only occures for the S3 servers
		 */
		if((xvidmode->privsize > 0) && (xvidmode->private != NULL))
		{
		    XFree(xvidmode->private);
		    xvidmode->private = NULL;
		    xvidmode->privsize = 0;
		}

		/* Do not XFree() each xvidmode */
	    }

	    /* Delete the list of X video modes */
	    XFree(xvidmode_list);
	}

	return(glist);
#else
	return(NULL);
#endif
}

/*
 *	Deletes the list of Video Modes.
 */
void gdk_video_modes_delete(GList *glist)
{
	if(glist == NULL)
	    return;

	g_list_foreach(glist, (GFunc)gdk_video_mode_delete, NULL);
	g_list_free(glist);
}


/*
 *	Returns the video mode in the specified list who's values
 *	match the current video mode.
 */
GdkVideoModeInfo *gdk_video_mode_get_current(GList *glist)
{
#if defined(HAVE_WIN32)
	DWORD dwFields;
	DEVMODE dmSettings;
	static GdkVideoModeInfo mode_info;
	GdkVideoModeInfo *m = &mode_info;

	dmSettings.dmSize = sizeof(DEVMODE);

	EnumDisplaySettings(
	    NULL,		/* Current display device name */
	    0,			/* Device number */
	    &dmSettings		/* Return values */
	);

	/* Get flags for which members in dmSettings are defined */
	dwFields = dmSettings.dmFields;

	memset(m, 0x00, sizeof(GdkVideoModeInfo));

	m->viewport_width = (dwFields & DM_PELSWIDTH) ?
	    (gint)dmSettings.dmPelsWidth : 0;
	m->viewport_height = (dwFields & DM_PELSHEIGHT) ?
	    (gint)dmSettings.dmPelsHeight : 0;

	m->total_width = m->viewport_width;
	m->total_height = m->viewport_height;

	return(m);
#elif defined(HAVE_X) && defined(HAVE_XF86_VIDMODE) 
	Display *dpy = GDK_DISPLAY();
	int scr = DefaultScreen(dpy);
	int dotclock;
	XF86VidModeModeLine	xvidmode_mode_line,
				*xvidmode = &xvidmode_mode_line;
	GdkVideoModeInfo *m;

	if(!gdk_video_mode_is_supported())
	    return(NULL);

	/* Get current video mode */
	if(!XF86VidModeGetModeLine(
	    dpy, scr, &dotclock, xvidmode
	))
	    return(NULL);

	/* Iterate through list and look for one that matches the
	 * current video mode's values
	 */
	while(glist != NULL)
	{
	    m = GDK_VIDEO_MODE_INFO(glist->data);
	    if(m != NULL)
	    {
		if((m->viewport_width == (gint)xvidmode->hdisplay) &&
		   (m->viewport_height == (gint)xvidmode->vdisplay) &&
		   (m->total_width == (gint)xvidmode->htotal) &&
		   (m->total_height == (gint)xvidmode->vtotal) &&
		   (m->hsyncstart == (gint)xvidmode->hsyncstart) &&
		   (m->hsyncend == (gint)xvidmode->hsyncend) &&
		   (m->vsyncstart == (gint)xvidmode->vsyncstart) &&
		   (m->vsyncend == (gint)xvidmode->vsyncend)
		)
		    return(m);
	    }
	    glist = g_list_next(glist);
	}
	return(NULL);
#else
	return(NULL);
#endif
}


/*
 *	Gets the current video mode's current viewport position.
 *
 *	Returns TRUE on success.
 */
gboolean gdk_video_mode_get_viewport(gint *x_rtn, gint *y_rtn)
{
#if defined(HAVE_X) && defined(HAVE_XF86_VIDMODE)
	Display *dpy = GDK_DISPLAY();   
	int scr = DefaultScreen(dpy);   
	int x, y;

	if(x_rtn != NULL)
	    *x_rtn = 0;
	if(y_rtn != NULL)
	    *y_rtn = 0;

	if(!gdk_video_mode_is_supported())
	    return(FALSE);

	/* Get viewport position */
	if(XF86VidModeGetViewPort(dpy, scr, &x, &y))
	{
	    if(x_rtn != NULL)
		*x_rtn = x;
	    if(y_rtn != NULL)
		*y_rtn = y;
	    return(TRUE);
	}
	else
	    return(FALSE);
#else
	if(x_rtn != NULL)
	    *x_rtn = 0;
	if(y_rtn != NULL)
	    *y_rtn = 0;

	return(FALSE);
#endif
}

/*
 *	Sets the current video mode's viewport position.
 */
void gdk_video_mode_set_viewport(gint x, gint y)
{
#if defined(HAVE_X) && defined(HAVE_XF86_VIDMODE)
	Display *dpy = GDK_DISPLAY();
	int scr = DefaultScreen(dpy);

	if(!gdk_video_mode_is_supported())
	    return;

	/* Set viewport position */
	XF86VidModeSetViewPort(dpy, scr, x, y);
#else

#endif
}


/*
 *	Switches to the specified video mode.
 *
 *	Returns TRUE on success.
 */
gboolean gdk_video_mode_switch(const GdkVideoModeInfo *m)
{
#if defined(HAVE_X) && defined(HAVE_XF86_VIDMODE)
	gboolean status = FALSE;
	Display *dpy = GDK_DISPLAY();
	int scr = DefaultScreen(dpy);
	int i, total = 0;
	XF86VidModeModeInfo **xvidmode_list = NULL, *xvidmode;

	if(!gdk_video_mode_is_supported() || (m == NULL))
	    return(status);

	/* Get list of X video modes */
	if(XF86VidModeGetAllModeLines(
	    dpy, scr, &total, &xvidmode_list
	))
	{ 
	    for(i = 0; i < total; i++)
	    {
		xvidmode = xvidmode_list[i];
		if(xvidmode == NULL)
		    continue;

		/* Is this xvidmode's values are suitable for the
		 * specified video mode?
		 */
		if((m->dotclock == (guint)xvidmode->dotclock) &&
		   (m->viewport_width == (gint)xvidmode->hdisplay) &&
		   (m->viewport_height == (gint)xvidmode->vdisplay) &&
		   (m->total_width == (gint)xvidmode->htotal) &&
		   (m->total_height == (gint)xvidmode->vtotal) &&
		   (m->hsyncstart == (gint)xvidmode->hsyncstart) &&
		   (m->hsyncend == (gint)xvidmode->hsyncend) &&
		   (m->vsyncstart == (gint)xvidmode->vsyncstart) &&
		   (m->vsyncend == (gint)xvidmode->vsyncend)
		)
		{
		    if(!status)
		    {
			/* Switch to the new video mode */
			if(XF86VidModeSwitchToMode(dpy, scr, xvidmode))
			    status = TRUE;
		    }
		}

		/* If xvidmode->private is not NULL then it needs to
		 * be deleted
		 *
		 * Currently this only occures for the S3 servers
		 */
		if((xvidmode->privsize > 0) && (xvidmode->private != NULL))
		{
		    XFree(xvidmode->private);
		    xvidmode->private = NULL;
		    xvidmode->privsize = 0;
		}

		/* Do not XFree() each xvidmode */
	    }

	    /* Delete the list of X video modes */
	    XFree(xvidmode_list);
	}
	return(status);
#else
	return(FALSE);
#endif
}
