#include "../client/client.h"
#include "../client/qmenu.h"

/*
 * ====================================================================
 *
 * REF stuff ... Used to dynamically load the menu with only those vid_ref's
 * that are present on this system
 *
 * ====================================================================
 */

/* this will have to be updated if ref's are added/removed from ref_t */
#define NUMBER_OF_REFS 2

/* all the refs should be initially set to 0 */
static char    *refs[NUMBER_OF_REFS + 1] = {0};

/* make all these have illegal values, as they will be redefined */
static int	REF_GLX = NUMBER_OF_REFS;
static int	REF_SDLGL = NUMBER_OF_REFS;

/* static int REF_FXGL    = NUMBER_OF_REFS; */

static int	GL_REF_START = NUMBER_OF_REFS;

typedef struct {
	char		menuname  [32];
	char		realname  [32];
	int            *pointer;
} ref_t;

static const ref_t possible_refs[NUMBER_OF_REFS] =
{
	{"[OpenGL GLX  ]", "q2glx", &REF_GLX},
	{"[SDL OpenGL  ]", "q2sdlgl", &REF_SDLGL}
};

/*
 * ====================================================================
 */

extern cvar_t  *vid_ref;
extern cvar_t  *vid_fullscreen;
extern cvar_t  *vid_gamma;
extern cvar_t  *scr_viewsize;

static cvar_t  *gl_mode;
static cvar_t  *gl_driver;
static cvar_t  *gl_picmip;

static cvar_t  *_windowed_mouse;

static cvar_t  *gl_texturemode;
static cvar_t  *gl_anisotropic;
static cvar_t  *gl_anisotropic_avail;
static cvar_t  *gl_bitdepth;

extern void	M_ForceMenuOff(void);
extern qboolean	VID_CheckRefExists(const char *name);

/*
 * ====================================================================
 *
 * MENU INTERACTION
 *
 * ====================================================================
 */
#define OPENGL_MENU   1

static menuframework_s s_opengl_menu;
static menuframework_s *s_current_menu;
static int	s_current_menu_index;

static menulist_s s_mode_list[2];
static menulist_s s_ref_list[2];
static menuslider_s s_tq_slider;
static menulist_s s_qudos_texfilter;
static menulist_s s_qudos_aniso;
static menulist_s s_qudos_texcompress;
static menulist_s s_qudos_bitdepth;
static menuslider_s s_screensize_slider[2];
static menuslider_s s_brightness_slider[2];
static menulist_s s_fs_box[2];
static menulist_s s_stipple_box;
static menulist_s s_paletted_texture_box;
static menulist_s s_windowed_mouse;
static menuaction_s s_apply_action[2];
static menuaction_s s_defaults_action[2];

static void
DriverCallback(void *unused)
{
	s_ref_list[!s_current_menu_index].curvalue = s_ref_list[s_current_menu_index].curvalue;
	s_current_menu = &s_opengl_menu;
	s_current_menu_index = 1;

}

static void
ScreenSizeCallback(void *s)
{
	menuslider_s   *slider = (menuslider_s *) s;

	Cvar_SetValue("viewsize", slider->curvalue * 10);
}

static void
BrightnessCallback(void *s)
{
	menuslider_s   *slider = (menuslider_s *) s;

	if (s_current_menu_index == 0)
		s_brightness_slider[1].curvalue = s_brightness_slider[0].curvalue;
	else
		s_brightness_slider[0].curvalue = s_brightness_slider[1].curvalue;

	if ( stricmp(vid_ref->string, "q2sdlgl") == 0 ||
	     stricmp(vid_ref->string, "q2glx") == 0) {
		float		gamma = (0.8 - (slider->curvalue / 10.0 - 0.5)) + 0.5;

		Cvar_SetValue("vid_gamma", gamma);
	}
}

static void
ResetDefaults(void *unused)
{
	VID_MenuInit();
}

static void
ApplyChanges(void *unused)
{
	float		gamma;
	int		ref;

	/*
	 * * make values consistent
	 */
	s_fs_box[!s_current_menu_index].curvalue = s_fs_box[s_current_menu_index].curvalue;
	s_brightness_slider[!s_current_menu_index].curvalue = s_brightness_slider[s_current_menu_index].curvalue;
	s_ref_list[!s_current_menu_index].curvalue = s_ref_list[s_current_menu_index].curvalue;

	/*
	 * * invert sense so greater = brighter, and scale to a range of 0.5
	 * to 1.3
	 */
	gamma = (0.8 - (s_brightness_slider[s_current_menu_index].curvalue / 10.0 - 0.5)) + 0.5;

	Cvar_SetValue("vid_gamma", gamma);

	if (s_qudos_texfilter.curvalue == 0)
		Cvar_Set("gl_texturemode", "GL_LINEAR_MIPMAP_NEAREST");
	else if (s_qudos_texfilter.curvalue == 1)
		Cvar_Set("gl_texturemode", "GL_LINEAR_MIPMAP_LINEAR");

	switch ((int)s_qudos_aniso.curvalue) {
	case 1:
		Cvar_SetValue("gl_anisotropic", 2.0);
		break;
	case 2:
		Cvar_SetValue("gl_anisotropic", 4.0);
		break;
	case 3:
		Cvar_SetValue("gl_anisotropic", 8.0);
		break;
	case 4:
		Cvar_SetValue("gl_anisotropic", 16.0);
		break;
	default:
	case 0:
		Cvar_SetValue("gl_anisotropic", 0.0);
		break;
	}

	Cvar_SetValue("sw_stipplealpha", s_stipple_box.curvalue);
	Cvar_SetValue("gl_picmip", 3 - s_tq_slider.curvalue);
	Cvar_SetValue("vid_fullscreen", s_fs_box[s_current_menu_index].curvalue);
	Cvar_SetValue("gl_ext_palettedtexture", s_paletted_texture_box.curvalue);
	Cvar_SetValue("gl_mode", s_mode_list[OPENGL_MENU].curvalue);
	Cvar_SetValue("_windowed_mouse", s_windowed_mouse.curvalue);
	Cvar_SetValue("gl_ext_texture_compression", s_qudos_texcompress.curvalue);
	Cvar_SetValue("gl_bitdepth", s_qudos_bitdepth.curvalue * 16);

	/*
	 * * must use an if here (instead of a switch), since the REF_'s are
	 * now variables * and not #DEFINE's (constants)
	 */
	ref = s_ref_list[s_current_menu_index].curvalue;
	if (ref == REF_GLX) {
		Cvar_Set("vid_ref", "q2glx");

		/*
		 * below is wrong if we use different libs for different GL
		 * reflibs
		 */
		Cvar_Get("gl_driver", "libGL.so", CVAR_ARCHIVE);	/* ??? create if it doesn't exit */
		if (gl_driver->modified)
			vid_ref->modified = true;
	} else if (ref == REF_SDLGL) {
		Cvar_Set("vid_ref", "q2sdlgl");

		/*
		 * below is wrong if we use different libs for different GL
		 * reflibs
		 */
		Cvar_Get("gl_driver", "libGL.so", CVAR_ARCHIVE);	/* ??? create if it doesn't exist */
		if (gl_driver->modified)
			vid_ref->modified = true;
	}
	M_ForceMenuOff();
}



/* Knightmare */
int
texfilter_box_setval(void)
{
	char           *texmode = Cvar_VariableString("gl_texturemode");

	if (!Q_strcasecmp(texmode, "GL_LINEAR_MIPMAP_NEAREST"))
		return 0;
	else
		return 1;
}

static float
ClampCvar(float min, float max, float value)
{
	if (value < min)
		return min;
	if (value > max)
		return max;
	return value;
}

static const char *mip_names[] =
{
	"Bilinear ",
	"Trilinear",
	0
};


static const char *aniso0_names[] =
{
	"Not supported",
	0
};

static const char *aniso2_names[] =
{
	"Off",
	"2x ",
	0
};

static const char *aniso4_names[] =
{
	"Off",
	"2x ",
	"4x ",
	0
};

static const char *aniso8_names[] =
{
	"Off",
	"2x ",
	"4x ",
	"8x ",
	0
};

static const char *aniso16_names[] =
{
	"Off",
	"2x ",
	"4x ",
	"8x ",
	"16x",
	0
};

static const char **
GetAnisoNames()
{
	float		aniso_avail = Cvar_VariableValue("gl_anisotropic_avail");

	if (aniso_avail < 2.0)
		return aniso0_names;
	else if (aniso_avail < 4.0)
		return aniso2_names;
	else if (aniso_avail < 8.0)
		return aniso4_names;
	else if (aniso_avail < 16.0)
		return aniso8_names;
	else			/* >= 16.0 */
		return aniso16_names;
}


float
GetAnisoCurValue()
{
	float		aniso_avail = Cvar_VariableValue("gl_anisotropic_avail");
	float		anisoValue = ClampCvar(0, aniso_avail, Cvar_VariableValue("gl_anisotropic"));

	if (aniso_avail == 0)	/* not available */
		return 0;
	if (anisoValue < 2.0)
		return 0;
	else if (anisoValue < 4.0)
		return 1;
	else if (anisoValue < 8.0)
		return 2;
	else if (anisoValue < 16.0)
		return 3;
	else			/* >= 16.0 */
		return 4;
}

/*
 * * VID_MenuInit
 */
void
VID_MenuInit(void)
{
	int		i         , counter;

	static const char *resolutions[] =
	{
		"[320 240  ]",
		"[400 300  ]",
		"[512 384  ]",
		"[640 480  ]",
		"[800 600  ]",
		"[960 720  ]",
		"[1024 768 ]",
		"[1152 864 ]",
		"[1280 1024]",
		"[1600 1200]",
		"[2048 1536]",
		"[1024 480 ]",	/* sony vaio pocketbook */
		"[1152 768 ]",	/* Apple TiBook */
		"[1152 854 ]",	/* Apple TiBook */
		"[1280 800 ]",	/* apple TiBook */
		"[1280 854 ]",	/* apple TiBook */
		"[1280 960 ]",	/* apple TiBook */
		"[640 400  ]",	/* generic 16:10 widescreen resolutions */
		"[800 500  ]",	/* as found on many modern notebooks    */
		"[1024 640 ]",
		"[1280 800 ]",
		"[1680 1050]",
		"[1920 1200]",
		0
	};
	static const char *yesno_names[] =
	{
		"No ",
		"Yes",
		0
	};

	static const char *bitdepth_names[] =
	{
		"Desktop",
		"16 bit",
		"32 bit",
		0
	};

	/* make sure these are invalided before showing the menu again */
	REF_GLX = NUMBER_OF_REFS;
	REF_SDLGL = NUMBER_OF_REFS;


	GL_REF_START = NUMBER_OF_REFS;

	/* now test to see which ref's are present */
	i = counter = 0;
	while (i < NUMBER_OF_REFS) {
		if (VID_CheckRefExists(possible_refs[i].realname)) {
			*(possible_refs[i].pointer) = counter;

			/* free any previous string */
			if (refs[i])
				free(refs[i]);
			refs[counter] = strdup(possible_refs[i].menuname);

			/*
			 * * if we reach the 3rd item in the list, this
			 * indicates that a * GL ref has been found; this
			 * will change if more software * modes are added to
			 * the possible_ref's array
			 */
			if (i == 3)
				GL_REF_START = counter;

			counter++;
		}
		i++;
	}
	refs[counter] = (char *)0;

	if (!gl_driver)
		gl_driver = Cvar_Get("gl_driver", "libGL.so", 0);
	if (!gl_picmip)
		gl_picmip = Cvar_Get("gl_picmip", "0", 0);
	if (!gl_mode)
		gl_mode = Cvar_Get("gl_mode", "4", 0);
	if (!gl_anisotropic)
		gl_anisotropic = Cvar_Get("gl_anisotropic", "0", CVAR_ARCHIVE);
	if (!gl_texturemode)
		gl_texturemode = Cvar_Get("gl_texturemode", "GL_LINEAR_MIPMAP_NEAREST", CVAR_ARCHIVE);
	if (!gl_anisotropic_avail)
		gl_anisotropic_avail = Cvar_Get("gl_anisotropic_avail", "0", 0);
	if (!gl_bitdepth)
		gl_bitdepth = Cvar_Get("gl_bitdepth", "0", CVAR_ARCHIVE);

	if (!_windowed_mouse)
		_windowed_mouse = Cvar_Get("_windowed_mouse", "1", CVAR_ARCHIVE);

	s_mode_list[OPENGL_MENU].curvalue = gl_mode->value;

	if (!scr_viewsize)
		scr_viewsize = Cvar_Get("viewsize", "100", CVAR_ARCHIVE);
		
	s_screensize_slider[OPENGL_MENU].curvalue = scr_viewsize->value / 10;

	if (strcmp(vid_ref->string, "q2glx") == 0) {
		s_current_menu_index = OPENGL_MENU;
		s_ref_list[s_current_menu_index].curvalue = REF_GLX;
	} else if (strcmp(vid_ref->string, "q2sdlgl") == 0) {
		s_current_menu_index = OPENGL_MENU;
		s_ref_list[s_current_menu_index].curvalue = REF_SDLGL;
	}
	
	s_opengl_menu.x = viddef.width * 0.50;
	s_opengl_menu.nitems = 0;

	for (i = 0; i < 2; i++) {
		s_ref_list[i].generic.type = MTYPE_SPINCONTROL;
		s_ref_list[i].generic.name = "Driver";
		s_ref_list[i].generic.x = 0;
		s_ref_list[i].generic.y = 10;
		s_ref_list[i].generic.callback = DriverCallback;
		s_ref_list[i].itemnames = (const char **)refs;

		s_mode_list[i].generic.type = MTYPE_SPINCONTROL;
		s_mode_list[i].generic.name = "Video mode";
		s_mode_list[i].generic.x = 0;
		s_mode_list[i].generic.y = 20;
		s_mode_list[i].itemnames = resolutions;

		s_fs_box[i].generic.type = MTYPE_SPINCONTROL;
		s_fs_box[i].generic.x = 0;
		s_fs_box[i].generic.y = 30;
		s_fs_box[i].generic.name = "Fullscreen";
		s_fs_box[i].itemnames = yesno_names;
		s_fs_box[i].curvalue = vid_fullscreen->value;

		s_qudos_texfilter.generic.type = MTYPE_SPINCONTROL;
		s_qudos_texfilter.generic.x = 0;
		s_qudos_texfilter.generic.y = 50;
		s_qudos_texfilter.generic.name = "Texture filter";
		s_qudos_texfilter.curvalue = texfilter_box_setval();
		s_qudos_texfilter.itemnames = mip_names;

		s_qudos_aniso.generic.type = MTYPE_SPINCONTROL;
		s_qudos_aniso.generic.x = 0;
		s_qudos_aniso.generic.y = 60;
		s_qudos_aniso.generic.name = "Anisotropic filter";
		s_qudos_aniso.curvalue = GetAnisoCurValue();
		s_qudos_aniso.itemnames = GetAnisoNames();

		s_qudos_texcompress.generic.type = MTYPE_SPINCONTROL;
		s_qudos_texcompress.generic.x = 0;
		s_qudos_texcompress.generic.y = 70;
		s_qudos_texcompress.generic.name = "Texture compression";
		s_qudos_texcompress.itemnames = yesno_names;
		s_qudos_texcompress.curvalue = Cvar_VariableValue("gl_ext_texture_compression");

		s_tq_slider.generic.type = MTYPE_SLIDER;
		s_tq_slider.generic.x = 0;
		s_tq_slider.generic.y = 80;
		s_tq_slider.generic.name = "Texture quality";
		s_tq_slider.minvalue = 0;
		s_tq_slider.maxvalue = 3;
		s_tq_slider.curvalue = 3 - gl_picmip->value;

		s_qudos_bitdepth.generic.type = MTYPE_SPINCONTROL;
		s_qudos_bitdepth.generic.x = 0;
		s_qudos_bitdepth.generic.y = 90;
		s_qudos_bitdepth.generic.name = "Colour depth";
		s_qudos_bitdepth.curvalue = (gl_bitdepth->value) / 16;
		s_qudos_bitdepth.itemnames = bitdepth_names;

		s_screensize_slider[i].generic.type = MTYPE_SLIDER;
		s_screensize_slider[i].generic.x = 0;
		s_screensize_slider[i].generic.y = 110;
		s_screensize_slider[i].generic.name = "Screen size";
		s_screensize_slider[i].minvalue = 3;
		s_screensize_slider[i].maxvalue = 12;
		s_screensize_slider[i].generic.callback = ScreenSizeCallback;

		s_brightness_slider[i].generic.type = MTYPE_SLIDER;
		s_brightness_slider[i].generic.x = 0;
		s_brightness_slider[i].generic.y = 120;
		s_brightness_slider[i].generic.name = "Brightness";
		s_brightness_slider[i].generic.callback = BrightnessCallback;
		s_brightness_slider[i].minvalue = 5;
		s_brightness_slider[i].maxvalue = 13;
		s_brightness_slider[i].curvalue = (1.3 - vid_gamma->value + 0.5) * 10;

		s_defaults_action[i].generic.type = MTYPE_ACTION;
		s_defaults_action[i].generic.name = "Reset to defaults";
		s_defaults_action[i].generic.x = 0;
		s_defaults_action[i].generic.y = 150;
		s_defaults_action[i].generic.callback = ResetDefaults;

		s_apply_action[i].generic.type = MTYPE_ACTION;
		s_apply_action[i].generic.name = "Apply";
		s_apply_action[i].generic.x = 0;
		s_apply_action[i].generic.y = 160;
		s_apply_action[i].generic.callback = ApplyChanges;
	}

	Menu_AddItem(&s_opengl_menu, (void *)&s_ref_list[OPENGL_MENU]);
	Menu_AddItem(&s_opengl_menu, (void *)&s_mode_list[OPENGL_MENU]);
	Menu_AddItem(&s_opengl_menu, (void *)&s_fs_box[OPENGL_MENU]);

	Menu_AddItem(&s_opengl_menu, (void *)&s_qudos_texfilter);
	Menu_AddItem(&s_opengl_menu, (void *)&s_qudos_aniso);
	Menu_AddItem(&s_opengl_menu, (void *)&s_qudos_texcompress);
	Menu_AddItem(&s_opengl_menu, (void *)&s_tq_slider);
	Menu_AddItem(&s_opengl_menu, (void *)&s_qudos_bitdepth);
	Menu_AddItem(&s_opengl_menu, (void *)&s_screensize_slider[OPENGL_MENU]);
	Menu_AddItem(&s_opengl_menu, (void *)&s_brightness_slider[OPENGL_MENU]);

	Menu_AddItem(&s_opengl_menu, (void *)&s_defaults_action[OPENGL_MENU]);
	Menu_AddItem(&s_opengl_menu, (void *)&s_apply_action[OPENGL_MENU]);

	Menu_Center(&s_opengl_menu);
	s_opengl_menu.x -= 8;
}

/*
 * ================ VID_MenuShutdown ================
 */
void
VID_MenuShutdown(void)
{
	int		i;

	for (i = 0; i < NUMBER_OF_REFS; i++) {
		if (refs[i])
			free(refs[i]);
	}
}

/*
 * ================ VID_MenuDraw ================
 */
void
VID_MenuDraw(void)
{
	int		w         , h;

	s_current_menu = &s_opengl_menu;

	/*
	 * * draw the banner
	 */
	re.DrawGetPicSize(&w, &h, "m_banner_video");
	re.DrawPic(viddef.width / 2 - w / 2, viddef.height / 2 - 120, "m_banner_video", 1.0f);

	/*
	 * * move cursor to a reasonable starting position
	 */
	Menu_AdjustCursor(s_current_menu, 1);

	/*
	 * * draw the menu
	 */
	Menu_Draw(s_current_menu);
}

/*
 * ================ VID_MenuKey ================
 */
const char     *
VID_MenuKey(int key)
{
	extern void	M_PopMenu(void);

	menuframework_s *m = s_current_menu;
	static const char *sound = "misc/menu1.wav";

	switch (key) {
	case K_ESCAPE:
		M_PopMenu();
		return NULL;
	case K_UPARROW:
		m->cursor--;
		Menu_AdjustCursor(m, -1);
		break;
	case K_DOWNARROW:
		m->cursor++;
		Menu_AdjustCursor(m, 1);
		break;
	case K_LEFTARROW:
		Menu_SlideItem(m, -1);
		break;
	case K_RIGHTARROW:
		Menu_SlideItem(m, 1);
		break;
	case K_ENTER:
		Menu_SelectItem(m);
		break;
	}

	return sound;
}
