#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "gl_init.h"
#include "asc.h"
#include "elconfig.h"
#include "errors.h"
#include "hud.h"
#include "init.h"
#include "interface.h"
#include "paste.h"
#include "textures.h"
#include "translate.h"
#include "sky.h"
#include "shader/shader.h"
#ifdef	FSAA
#include "fsaa/fsaa.h"
#endif	/* FSAA */

typedef struct
{
	int profile;
	int major;
	int minor;
	char version_string[80];
} GLContextInfo;

static GLContextInfo gl_context_info = { 0, 0, 0, { 0 } };

int window_width = 640;
int window_height = 480;
static float window_highdpi_scale_width = 1.0f;
static float window_highdpi_scale_height = 1.0f;

SDL_Window *el_gl_window = NULL;
static SDL_GLContext el_gl_context = NULL;
static SDL_Surface *icon_bmp = NULL;
static SDL_version el_gl_linked;
static int log_next_window_resize = 0;

int bpp = 0;
int have_stencil = 1;
int video_mode = 0;
int video_user_width = 640;
int video_user_height = 480;
int full_screen = 0;

int use_compiled_vertex_array = 0;
int use_vertex_buffers = 0;
int use_frame_buffer = 0;
int use_mipmaps = 0;
int use_draw_range_elements = 1;
float anisotropic_filter = 1.0f;
int enable_screensaver = 0;
int disable_gamma_adjust = 0;
int disable_focus_clickthrough = 0;
float gamma_var = 1.00f;
float perspective = 0.15f;
float near_plane = 0.1f; // don't cut off anything
float far_plane = 100.0;   // LOD helper. Cull distant objects. Lower value == higher framerates.
float far_reflection_plane = 100.0;   // LOD helper. Cull distant reflected objects. Lower value == higher framerates.
int gl_extensions_loaded = 0;

static void get_window_size(int mode, int *width, int *height)
{
	/* Video mode 0 is user defined size (via video_user_width and video_user_height)
	 * Video mode 1 and above are defined in the video_modes array where mode 1 is at position 0
	 * Therefore we heve to adjust the index by one
	 */
	int index = mode - 1;

	/* Safe fallback
	 * If the user select an invalid mode (like a wrong number in the config file) fallback
	 * to safe 640x480 mode
	 */
	if (index < 0 || index >= video_modes_count)
		index = 0;

	*width = (mode == 0) ?video_user_width :video_modes[index].width;
	*height = (mode == 0) ?video_user_height :video_modes[index].height;
}

static void load_window_icon(void)
{
	char *icon_name = "icon.bmp";
	size_t str_len = strlen(datadir) + strlen(icon_name) + 1;
	char *str_buf = malloc(str_len);
	safe_strncpy(str_buf, datadir, str_len);
	safe_strcat(str_buf, icon_name, str_len);
	icon_bmp = SDL_LoadBMP(str_buf);
	if (icon_bmp == NULL)
		LOG_ERROR("Failed to load window icon: %s\n",str_buf);
	else
		SDL_SetWindowIcon(el_gl_window, icon_bmp);
	free(str_buf);
}

// check if the window fits within the available space and resize if not
// check if the window is on-screen and move it if not
enum size_and_position_enum { DO_SIZE = 0, DO_POSITION, DO_SIZE_AND_POSITION };
static void fix_window_size_and_position(enum size_and_position_enum mode)
{
#if SDL_VERSION_ATLEAST(2, 0, 5)
	SDL_Rect usable_rect;
	int display_index = SDL_GetWindowDisplayIndex(el_gl_window);
	if (SDL_VERSIONNUM(el_gl_linked.major, el_gl_linked.minor, el_gl_linked.patch) < 2005)
		return;
	if ((display_index >= 0) && (SDL_GetDisplayUsableBounds(display_index, &usable_rect) == 0))
	{
		int top, left, bottom, right;
		if (SDL_GetWindowBordersSize(el_gl_window, &top, &left, &bottom, &right) == 0)
		{
			int ww, wh;
			SDL_GL_GetDrawableSize(el_gl_window, &ww, &wh);

			if ((mode == DO_SIZE) || (mode == DO_SIZE_AND_POSITION))
			{
				int do_resize = 0;
				if ((ww + left + right) > usable_rect.w)
				{
					ww = usable_rect.w - left - right;
					do_resize = 1;
				}
				if ((wh + top + bottom) > usable_rect.h)
				{
					wh = usable_rect.h - top - bottom;
					do_resize = 1;
				}
				if (do_resize)
				{
					SDL_SetWindowSize(el_gl_window, ww, wh);
					SDL_RestoreWindow(el_gl_window);
				}
			}

			if ((mode == DO_POSITION) || (mode == DO_SIZE_AND_POSITION))
			{
				int posx, posy;
				SDL_GetWindowPosition(el_gl_window, &posx, &posy);
				if (((posx - left) < usable_rect.x) ||
					((posy - top) < usable_rect.y) ||
					((posx + ww + left + right) > (usable_rect.x + usable_rect.w)) ||
					((posy + wh + top + bottom) > (usable_rect.y + usable_rect.h)))
				{
					posx = usable_rect.x + left + (usable_rect.w - ww - left - right) / 2;
					posy = usable_rect.y + top + (usable_rect.h - wh - top - bottom) / 2;
					SDL_SetWindowPosition(el_gl_window, posx, posy);
				}
			}
		}
	}
#endif
}

void update_SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH(void)
{
#if SDL_VERSION_ATLEAST(2, 0, 5)
	if (SDL_VERSIONNUM(el_gl_linked.major, el_gl_linked.minor, el_gl_linked.patch) >= 2005)
	{
		if (disable_focus_clickthrough)
			SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "0");
		else
			SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
	}
#endif
}

void init_video(void)
{
	int versions_to_try[][3] = {
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 3, 3 }, // OpenGL 3.3 compatibility profile
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 3, 2 }, // OpenGL 3.2 compatibility profile
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 3, 1 }, // OpenGL 3.1
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 3, 0 }, // OpenGL 3.0
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 2, 1 }, // OpenGL 2.1
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 2, 0 }, // OpenGL 2.0. Not sure if lower versions will still work
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 1, 5 }, // OpenGL 1.5
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 1, 4 }, // OpenGL 1.4
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 1, 3 }, // OpenGL 1.3
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 1, 2 }, // OpenGL 1.2
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 1, 1 }, // OpenGL 1.1
		{ SDL_GL_CONTEXT_PROFILE_COMPATIBILITY, 1, 0 }, // OpenGL 1.0
	};
	int nr_versions_to_try = sizeof(versions_to_try) / sizeof(*versions_to_try);

	int target_width = 0, target_height = 0;
	int rgb_size[3];
	Uint32 flags;

	if (!full_screen)
		get_window_size(video_mode, &target_width, &target_height);

	if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		LOG_ERROR("%s: %s\n", no_sdl_str, SDL_GetError());
		fprintf(stderr, "%s: %s\n", no_sdl_str, SDL_GetError());
		SDL_Quit();
		FATAL_ERROR_WINDOW("Failed to initialise SDL, going to exit.");
		exit(1);
	}

	SDL_GetVersion(&el_gl_linked);

	/* Detect the display depth */
	if (!bpp)
	{
		SDL_DisplayMode current;
		SDL_GetCurrentDisplayMode(0, &current);
		if ( SDL_BITSPERPIXEL(current.format) <= 8 )
			bpp = 8;
		else if ( SDL_BITSPERPIXEL(current.format) <= 16 )
			bpp = 16;
		else
			bpp = 32;
	}

	// adjust the video mode depending on the BITSPERPIXEL available
	if (video_mode == 0)
	{
		// do nothing user defined
	}
	else if (bpp == 16)
	{
		if (!(video_mode%2))
			video_mode -= 1;
	}
	else
	{
		if (video_mode%2)
			video_mode += 1;
	}

	/* Initialize the display */
	switch (bpp) {
	case 8:
		rgb_size[0] = 2;
		rgb_size[1] = 3;
		rgb_size[2] = 3;
		break;
	case 15:
	case 16:
		rgb_size[0] = 5;
		rgb_size[1] = 5;
		rgb_size[2] = 5;
		break;
	default:
		rgb_size[0] = 8;
		rgb_size[1] = 8;
		rgb_size[2] = 8;
		break;
	}

	// Mac OS X will always use 8-8-8-8 ARGB for 32-bit screens and 5-5-5 RGB for 16-bit screens
#ifndef OSX
	SDL_GL_SetAttribute( SDL_GL_RED_SIZE, rgb_size[0] );
	SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, rgb_size[1] );
	SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, rgb_size[2] );
#endif

	SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 0 );
	SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 24 );
	SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 8);
	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

	flags = SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI;
	if(full_screen)
		flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;

#ifdef	FSAA
	if (fsaa > 1)
	{
		char str[400];
		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, fsaa);
		glDisable(GL_MULTISAMPLE);

		el_gl_window = SDL_CreateWindow("Eternal Lands", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, target_width, target_height, flags);
		if (el_gl_window == NULL)
		{
			safe_snprintf(str, sizeof(str), "Can't use fsaa mode x%d, disabling it.", fsaa);
			LOG_TO_CONSOLE(c_yellow1, str);
			LOG_WARNING("%s\n", str);
			SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
			SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
			fsaa = 0;
		}
	}
#endif	/* FSAA */

	//try to find a stencil buffer
	if (el_gl_window == NULL)
	{
		el_gl_window = SDL_CreateWindow("Eternal Lands", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, target_width, target_height, flags);
		if (el_gl_window == NULL)
		{
			LOG_TO_CONSOLE(c_red1,no_hardware_stencil_str);
			LOG_ERROR("%s\n",no_hardware_stencil_str);
			if(bpp!=32)
			{
                   LOG_TO_CONSOLE(c_grey1,suggest_24_or_32_bit);
                   LOG_ERROR("%s\n",suggest_24_or_32_bit);
            }
			SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,16);
			SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE,0);
			el_gl_window = SDL_CreateWindow("Eternal Lands", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, target_width, target_height, flags);
			if (el_gl_window == NULL)
			{
				LOG_ERROR("%s: %s\n", fail_opengl_mode, SDL_GetError());
				SDL_Quit();
				FATAL_ERROR_WINDOW("Failed to create game window, going to exit.");
				exit(1);
			}
			have_stencil=0;
		}
	}

	for (int i = 0; i < nr_versions_to_try; ++i)
	{
		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, versions_to_try[i][0]);
		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, versions_to_try[i][1]);
		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, versions_to_try[i][2]);
		el_gl_context = SDL_GL_CreateContext(el_gl_window);
		if (el_gl_context)
		{
			// OpenGL deprecated a lot of features we use in OpenGL 3.0, and removed them in
			// 3.1. OpenGL. OpenGL 3.2 introduced compatibility profiles, and 3.1 has the
			// ARB_compativility extension, but some systems fail with an OpenGL context >= 3.0
			// even when they report compatibility, or when the context is OpenGL 3.0 which should
			// still support the old functionality, Therefore, try a deprecated function here,
			// and if it fails, downgrade to a lower version context.
			const GLubyte* extensions_str = glGetString(GL_EXTENSIONS);
			DO_CHECK_GL_ERRORS();
			if (extensions_str)
				// That seemd to work, hopefully we're good
				break;
		}
	}
	if (el_gl_context == NULL)
	{
		LOG_ERROR("%s: %s\n", "SDL_GL_CreateContext() Failed", SDL_GetError());
		SDL_Quit();
		FATAL_ERROR_WINDOW("Failed to create game GL context, going to exit.");
		exit(1);
	}

	// check we have a working GL context, we will only fail later if we do not.
	{
		const GLubyte* test_string = glGetString(GL_VENDOR);
		if (test_string == NULL)
		{
			const char* error_str = "glGetString(GL_VENDOR) failed, going to exit.";
			DO_CHECK_GL_ERRORS();
			LOG_ERROR("%s: %s\n", error_str, SDL_GetError());
			SDL_Quit();
			FATAL_ERROR_WINDOW("%s", error_str);
			exit(1);
		}
	}

	// Set the GL verion info, so we can use different code paths for rendering if necessary
	SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &gl_context_info.profile);
	SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &gl_context_info.major);
	SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &gl_context_info.minor);
	switch (gl_context_info.profile)
	{
		case SDL_GL_CONTEXT_PROFILE_CORE:
			safe_snprintf(gl_context_info.version_string, sizeof(gl_context_info.version_string),
				"OpenGL Core %d.%d", gl_context_info.major, gl_context_info.minor);
			break;
		case SDL_GL_CONTEXT_PROFILE_COMPATIBILITY:
			safe_snprintf(gl_context_info.version_string, sizeof(gl_context_info.version_string),
				"OpenGL Compatibility %d.%d", gl_context_info.major, gl_context_info.minor);
			break;
		case SDL_GL_CONTEXT_PROFILE_ES:
			safe_snprintf(gl_context_info.version_string, sizeof(gl_context_info.version_string),
				"OpenGL ES %d.%d", gl_context_info.major, gl_context_info.minor);
			break;
		default:
			safe_snprintf(gl_context_info.version_string, sizeof(gl_context_info.version_string),
				"Unknown %d.%d", gl_context_info.major, gl_context_info.minor);
	}

	// set the minimum size for the window, this is too small perhaps but a config option
	SDL_SetWindowMinimumSize(el_gl_window, 640,  480);

	// read events for a few milliseconds, this will catch window size changes made by the window manger
	{
		SDL_Event event;
		Uint32 start_wait = SDL_GetTicks();
		while((SDL_GetTicks() < (start_wait + 100)))
		{
			SDL_PollEvent(&event);
			SDL_Delay(1);
		}
	}

	if (!full_screen)
		fix_window_size_and_position(DO_SIZE_AND_POSITION);

	// get the windos size, these variables are used globaly
	update_window_size_and_scale();
	// even though no windows have been created, their starting position need to be adjusted
	if (!full_screen)
		move_windows_proportionally((float)window_width / (float)target_width, (float)window_height / (float)target_height);

	// enable V-SYNC, choosing active as a preference
	if (SDL_GL_SetSwapInterval(-1) < 0)
		SDL_GL_SetSwapInterval(1);

	// set the hint that determines if clicks that focus the window, pass through for action too
	update_SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH();

	if (enable_screensaver)
		SDL_EnableScreenSaver();

	// set the gamma of we are controlling it
	if (!disable_gamma_adjust)
		SDL_SetWindowBrightness(el_gl_window, gamma_var);

	// set the initial window title, though it will change once we are logged in
	SDL_SetWindowTitle( el_gl_window, win_principal );

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);
	//glDepthFunc(GL_LEQUAL);
	glEnable(GL_TEXTURE_2D);
	glShadeModel(GL_SMOOTH);
	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);
	glEnable(GL_NORMALIZE);
	glClearStencil(0);

#ifdef ANTI_ALIAS
	if (anti_alias) {
		glHint(GL_POINT_SMOOTH_HINT,   GL_NICEST);	
		glHint(GL_LINE_SMOOTH_HINT,    GL_NICEST);	
		glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);	
		glEnable(GL_POINT_SMOOTH);
		glEnable(GL_LINE_SMOOTH);
		glEnable(GL_POLYGON_SMOOTH);
	} else {
		glHint(GL_POINT_SMOOTH_HINT,   GL_FASTEST);	
		glHint(GL_LINE_SMOOTH_HINT,    GL_FASTEST);	
		glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST);	
		glDisable(GL_POINT_SMOOTH);
		glDisable(GL_LINE_SMOOTH);
		glDisable(GL_POLYGON_SMOOTH);
	}
#endif

	last_texture = -1;		//no active texture
	video_mode_set = 1;		//now you may set the video mode using the %<foo> in-game

#if !defined OSX && !defined WINDOWS
	init_x11_copy_paste();
#endif

	load_window_icon();

	check_options();

	DO_CHECK_GL_ERRORS();
}

#ifdef	GL_EXTENSION_CHECK
static void evaluate_extension(void)
{
	char str[1024];
	int has_arb_texture_env_add;
	int has_arb_texture_env_crossbar;
	int has_arb_texture_rectangle;
	int has_arb_fragment_shader_shadow;
	int has_ati_fragment_shader;
	int has_ati_texture_env_combine3;
	int has_nv_texture_env_combine4;
	int has_nv_texture_shader;
	int has_nv_texture_shader2;
	int options;
	char* extensions;

	extensions = (char*)glGetString(GL_EXTENSIONS);

	has_arb_texture_env_add = strstr(extensions, "GL_ARB_texture_env_add") > 0;
	has_arb_texture_env_crossbar = strstr(extensions, "GL_ARB_texture_env_crossbar") > 0;
	has_arb_texture_rectangle = strstr(extensions, "GL_ARB_texture_rectangle") > 0;
	has_arb_fragment_shader_shadow = strstr(extensions, "GL_ARB_fragment_program_shadow") > 0;
	has_ati_fragment_shader = strstr(extensions, "GL_ATI_fragment_shader") > 0;
	has_ati_texture_env_combine3 = strstr(extensions, "GL_ATI_texture_env_combine3") > 0;
	has_nv_texture_env_combine4 = strstr(extensions, "GL_NV_texture_env_combine4") > 0;
	has_nv_texture_shader = strstr(extensions, "GL_NV_texture_shader") > 0;
	has_nv_texture_shader2 = strstr(extensions, "GL_NV_texture_shader2") > 0;

	options = (get_texture_units() >= 2) && has_arb_texture_env_add &&
		have_extension(arb_texture_env_combine) && have_extension(arb_vertex_program) &&
		have_extension(arb_texture_compression) && have_extension(arb_vertex_buffer_object) &&
		have_extension(ext_texture_compression_s3tc);

	if (!options)
	{
		safe_snprintf(str,sizeof(str),"%s%s%s","Your graphic card/driver don't support the minimum",
			" requirements for the next EL release. Please upgrade your driver.",
			" If this don't help, you need a better graphic card.");
        LOG_TO_CONSOLE(c_red1, str);
		LOG_ERROR("%s\n",str);
		return;
	}

	if (!have_extension(arb_vertex_shader) || (have_extension(arb_fragment_program) &&
		!have_extension(arb_fragment_shader)) || (has_ati_fragment_shader &&
		!have_extension(arb_fragment_program) && !have_extension(arb_fragment_shader)))
	{
        safe_snprintf(str,sizeof(str),"Please update your graphic card driver!");
		LOG_TO_CONSOLE(c_yellow1, str);
		LOG_WARNING("%s\n",str);
	}

	options = ((has_ati_texture_env_combine3 && has_arb_texture_env_crossbar) ||
		has_nv_texture_env_combine4) && (get_texture_units() >= 4) &&
		have_extension(ext_draw_range_elements) && have_extension(arb_shadow) &&
		have_extension(arb_point_parameters) && have_extension(arb_point_sprite);

	if (!options)
	{
		safe_snprintf(str,sizeof(str),"%s%s%s","Your graphic card supports the absolute minimum",
			" requirements for the next EL release, but don't expect that you can use",
			" all features.");
        LOG_TO_CONSOLE(c_yellow1, str);
		LOG_DEBUG("%s\n",str);
        
	}
	else
	{
		options = (has_ati_fragment_shader || (has_nv_texture_shader &&
			has_nv_texture_shader2)) && have_extension(arb_occlusion_query) &&
			has_arb_texture_rectangle && have_extension(ext_framebuffer_object);
		if (!options)
		{
            safe_snprintf(str,sizeof(str),"%s%s","Your graphic card supports the default ",
				"requirements for the next EL release.");
			LOG_TO_CONSOLE(c_green2, str);
			LOG_DEBUG("%s\n",str);
		}
		else
		{
			if (have_extension(arb_fragment_shader) &&
				have_extension(arb_shader_objects) &&
				have_extension(arb_vertex_shader) &&
				have_extension(arb_shading_language_100))
			{
				safe_snprintf(str,sizeof(str),"%s%s","Your graphic card supports all ",
					"features EL will use in the future.");
                LOG_TO_CONSOLE(c_blue2, str);
                LOG_DEBUG("%s\n",str);
			}
			else
			{
                safe_snprintf(str,sizeof(str),"%s%s","Your graphic card supports more than the",
				" default requirements for the next EL release.");
				LOG_TO_CONSOLE(c_blue2, str);
                LOG_DEBUG("%s\n",str);
			}
		}
	}
}
#endif	//GL_EXTENSION_CHECK

void init_gl_extensions(void)
{
	char str[1024];

	init_opengl_extensions();

	/*	GL_ARB_multitexture			*/
	if (have_extension(arb_multitexture))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_multitexture");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_multitexture");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_multitexture			*/

	/*	GL_ARB_texture_env_combine		*/
	if (have_extension(arb_texture_env_combine))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_texture_env_combine");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_texture_env_combine");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_texture_env_combine		*/

	/*	GL_EXT_compiled_vertex_array		*/
	if (have_extension(ext_compiled_vertex_array))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_EXT_compiled_vertex_array");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_EXT_compiled_vertex_array");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_EXT_compiled_vertex_array		*/

	/*	GL_ARB_point_sprite			*/
	if (have_extension(arb_point_sprite))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_point_sprite");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_point_sprite");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_point_sprite		*/

	/*	GL_ARB_texture_compression		*/
	if (have_extension(arb_texture_compression))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_texture_compression");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_texture_compression");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_texture_compression		*/

	/*	GL_EXT_texture_compression_s3tc		*/
	if (have_extension(ext_texture_compression_s3tc))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_EXT_texture_compression_s3tc");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_EXT_texture_compression_s3tc");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_EXT_texture_compression_s3tc		*/
	
	/*	GL_SGIS_generate_mipmap			*/
	if (have_extension(sgis_generate_mipmap))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_SGIS_generate_mipmap");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_SGIS_generate_mipmap");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_SGIS_generate_mipmap			*/

	/*	GL_ARB_shadow				*/
	if (have_extension(arb_shadow))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_shadow");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_shadow");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_shadow				*/

	/*	GL_ARB_vertex_buffer_object		*/
	if (have_extension(arb_vertex_buffer_object))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_vertex_buffer_object");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_vertex_buffer_object");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_vertex_buffer_object		*/

	/*	GL_EXT_framebuffer_object		*/
	if (have_extension(ext_framebuffer_object))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_EXT_framebuffer_object");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_EXT_framebuffer_object");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_EXT_framebuffer_object		*/
	
	/*	GL_EXT_draw_range_elements		*/
	if (have_extension(ext_draw_range_elements))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_EXT_draw_range_elements");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_EXT_draw_range_elements");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_EXT_draw_range_elements		*/

	/*	GL_ARB_texture_non_power_of_two		*/
	if (have_extension(arb_texture_non_power_of_two))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_texture_non_power_of_two");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_texture_non_power_of_two");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_texture_non_power_of_two		*/

	/*	GL_ARB_fragment_program			*/
	if (have_extension(arb_fragment_program))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_fragment_program");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_fragment_program");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_fragment_program			*/

	/*	GL_ARB_vertex_program			*/
	if (have_extension(arb_vertex_program))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_vertex_program");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_vertex_program");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_vertex_program			*/

	/*	GL_ARB_fragment_shader			*/
	if (have_extension(arb_fragment_shader))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_fragment_shader");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_fragment_shader");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_fragment_shader			*/

	/*	GL_ARB_vertex_shader			*/
	if (have_extension(arb_vertex_shader))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_vertex_shader");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_vertex_shader");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_vertex_shader			*/

	/*	GL_ARB_shader_objects			*/
	if (have_extension(arb_shader_objects))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_shader_objects");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_shader_objects");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_shader_objects			*/

	/*	GL_ARB_shading_language_100		*/
	if (have_extension(arb_shading_language_100))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ARB_shading_language_100");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_shading_language_100");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_shading_language_100		*/

	/*	GL_ARB_texture_mirrored_repeat		*/
	if (have_extension(arb_texture_mirrored_repeat))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found_not_used, "GL_ARB_texture_mirrored_repeat");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_texture_mirrored_repeat");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_texture_mirrored_repeat		*/

	/*	GL_ARB_texture_rectangle		*/
	if (have_extension(arb_texture_rectangle))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found_not_used, "GL_ARB_texture_rectangle");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ARB_texture_rectangle");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ARB_texture_rectangle		*/

	/*	GL_EXT_fog_coord			*/
	if (have_extension(ext_fog_coord))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found_not_used, "GL_EXT_fog_coord");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_EXT_fog_coord");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_EXT_fog_coord			*/

	/*	GL_ATI_texture_compression_3dc		*/
	if (have_extension(ati_texture_compression_3dc))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_ATI_texture_compression_3dc");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_ATI_texture_compression_3dc");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_ATI_texture_compression_3dc		*/

	/*	GL_EXT_texture_compression_latc		*/
	if (have_extension(ext_texture_compression_latc))
	{
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_EXT_texture_compression_latc");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_EXT_texture_compression_latc");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_EXT_texture_compression_latc		*/

	/*	GL_EXT_texture_filter_anisotropic	*/
	if (have_extension(ext_texture_filter_anisotropic))
	{
		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic_filter);
		safe_snprintf(str, sizeof(str), gl_ext_found, "GL_EXT_texture_filter_anisotropic");
		LOG_TO_CONSOLE(c_green2, str);
		LOG_DEBUG("%s\n",str);
	}
	else
	{
		anisotropic_filter = 1.0f;
		safe_snprintf(str, sizeof(str), gl_ext_not_found, "GL_EXT_texture_filter_anisotropic");
		LOG_TO_CONSOLE(c_red1, str);
		LOG_DEBUG("%s\n",str);
	}
	/*	GL_EXT_texture_filter_anisotropic	*/

#if	0
	// Disabled because of bad drivers
	if (have_extension(ext_framebuffer_object))
	{
		check_fbo_formats();
	}
#endif
	init_shaders();

#ifdef	GL_EXTENSION_CHECK
	evaluate_extension();
#endif	//GL_EXTENSION_CHECK

	gl_extensions_loaded = 1;

	CHECK_GL_ERRORS();
}

void resize_root_window(void)
{
	float window_ratio;
	//float hud_x_adjust=0;
	//float hud_y_adjust=0;

	if (window_height==0)window_height=1;			// Prevent A Divide By Zero

	//glViewport(0, hud_y, window_width-hud_x, window_height);	// Reset The Current Viewport
	//glViewport(0, 0, window_width-hud_x, -(window_height-hud_y));	// Reset The Current Viewport

	glMatrixMode(GL_PROJECTION);					// Select The Projection Matrix
	glLoadIdentity();							// Reset The Projection Matrix

	window_ratio=(GLfloat)(window_width-hud_x)/(GLfloat)(window_height-hud_y);

	//hud_y_adjust=(2.0/window_height)*hud_y;
	//hud_x_adjust=(2.0/window_width)*hud_x;
	//Setup matrix for the sky. If we don't do this the sky looks unhinged when perspective changes.
	glLoadIdentity();
	glFrustum(-perspective*window_ratio*near_plane,
			   perspective*window_ratio*near_plane,
			  -perspective*near_plane,
			   perspective*near_plane,
			   near_plane, 1000.0);
	glGetDoublev(GL_PROJECTION_MATRIX, skybox_view);
	glLoadIdentity(); // Reset The Projection Matrix

	//new zoom
	if (isometric)
	{
		glOrtho( -1.0*zoom_level*window_ratio, 1.0*zoom_level*window_ratio, -1.0*zoom_level, 1.0*zoom_level, -near_plane*zoom_level, 60.0 );
	}
	else
	{
		glFrustum(-perspective*window_ratio*near_plane,
				   perspective*window_ratio*near_plane,
				  -perspective*near_plane,
				   perspective*near_plane,
				  near_plane, far_plane);
		if (!first_person)
		{
			glTranslatef(0.0, 0.0, zoom_level*camera_distance);
			glTranslatef(0.0, 0.0, -zoom_level/perspective);
		}
	}

	glMatrixMode(GL_MODELVIEW);					// Select The Modelview Matrix
	glLoadIdentity();							// Reset The Modelview Matrix
	last_texture=-1;	//no active texture
}

int switch_video(int mode, int full_screen)
{
	video_mode = mode;
	if (full_screen)
		SDL_SetWindowFullscreen(el_gl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
	else
	{
		int target_width, target_height;
		get_window_size(mode, &target_width, &target_height);
		set_client_window_size(target_width, target_height);
	}
	return 1;
}

//	Set the window size as specified without changing the window mode.
void set_client_window_size(int width, int height)
{
	// limit the window size to the actual available space
#if SDL_VERSION_ATLEAST(2, 0, 5)
	if (SDL_VERSIONNUM(el_gl_linked.major, el_gl_linked.minor, el_gl_linked.patch) >= 2005)
	{
		SDL_Rect rect;
		int display_index = SDL_GetWindowDisplayIndex(el_gl_window);
		int top, left, bottom, right;
		SDL_GetWindowBordersSize(el_gl_window, &top, &left, &bottom, &right);
		SDL_GetDisplayUsableBounds(display_index, &rect);
		width = (width > (rect.w - left - right)) ?rect.w - left - right :width;
		height = (height > (rect.h - top - bottom)) ?rect.h - top - bottom :height;
	}
#endif

	log_next_window_resize = 1;
	SDL_RestoreWindow(el_gl_window);
	SDL_SetWindowFullscreen(el_gl_window, 0);
	SDL_SetWindowSize(el_gl_window, width, height);
	fix_window_size_and_position(DO_POSITION);
}

//	Get a single value for highhdpi scaling.
float get_highdpi_scale(void)
{
	return (window_highdpi_scale_width > window_highdpi_scale_height) ?window_highdpi_scale_width :window_highdpi_scale_height;
}

//	Use the calulated values to scale mouse values.
void highdpi_scale(int *width, int *height)
{
	*width = (int)((0.5 - (*width < 0)) + (float)*width * window_highdpi_scale_width);
	*height = (int)((0.5 - (*height < 0)) + (float)*height * window_highdpi_scale_height);
}

//	For high DPI displays,  SDL_GetWindowSize() reports different values to SDL_GL_GetDrawableSize()
//	You need the SDL_GL_GetDrawableSize() to enable use of the full window.
//	You need to ration of SDL_GL_GetDrawableSize()/SDL_GetWindowSize() to scale the mouse location.
void update_window_size_and_scale(void)
{
	float old_width = window_highdpi_scale_width, old_height = window_highdpi_scale_height;
	int non_dpi_w, non_dpi_h;
	SDL_GetWindowSize(el_gl_window, &non_dpi_w, &non_dpi_h);
	SDL_GL_GetDrawableSize(el_gl_window, &window_width, &window_height);
	window_highdpi_scale_width = (float)window_width / (float)non_dpi_w;
	window_highdpi_scale_height = (float)window_height / (float)non_dpi_h;
	if ((old_width != window_highdpi_scale_width) || (old_height != window_highdpi_scale_height))
		update_highdpi_auto_scaling();
	if (log_next_window_resize)
	{
		char modestr[100];
		char str[100];
		safe_snprintf(modestr, sizeof(modestr), "%dx%d", window_width, window_height);
		safe_snprintf(str, sizeof(str), window_size_adjusted_str, modestr);
		LOG_TO_CONSOLE(c_yellow1, str);
		LOG_DEBUG("%s",str);
		log_next_window_resize = 0;
	}
}

void toggle_full_screen(void)
{
	full_screen=!full_screen;
	switch_video(video_mode, full_screen);
}

int print_gl_errors(const char *file, int line)
{
	GLenum	glErr, anyErr=GL_NO_ERROR;

	while ((glErr=glGetError()) != GL_NO_ERROR )
	 {
		anyErr=glErr;
		log_error(file, line, "OpenGL %s", gluErrorString(glErr));
	}
	return anyErr;
}

void gl_window_cleanup(void)
{
	if (el_gl_window != NULL)
	{
		SDL_DestroyWindow(el_gl_window);
		el_gl_window = NULL;
	}
	if (icon_bmp != NULL)
	{
		SDL_FreeSurface(icon_bmp);
		icon_bmp = NULL;
	}
}

const char* gl_context_version_string()
{
	return gl_context_info.version_string;
}

int gl_context_version()
{
	return 100*gl_context_info.major + gl_context_info.minor;
}

int max_supported_glsl_version()
{
	switch (gl_context_info.profile)
	{
		case SDL_GL_CONTEXT_PROFILE_COMPATIBILITY:
		case SDL_GL_CONTEXT_PROFILE_CORE:
			switch (gl_context_version())
			{
				case 303: return 330;
				case 302: return 150;
				case 301: return 140;
				case 300: return 130;
				case 201: return 120;
				case 200: return 110;
				default: return 0;
			}
		case SDL_GL_CONTEXT_PROFILE_ES:
			switch (10*gl_context_info.major + gl_context_info.minor)
			{
				case 31: return 10310;
				case 30: return 10300;
				case 20: return 10100;
				default: return 0;
			}
		default:
			return 0;
	}
}
