//============================================================
//
//  window.c - SDL window handling
//
//  Copyright (c) 1996-2010, Nicola Salmoria and the MAME Team.
//  Visit http://mamedev.org for licensing and usage restrictions.
//
//  SDLMAME by Olivier Galibert and R. Belmont
//
//============================================================

// standard SDL headers
#include <SDL/SDL.h>
#include <SDL/SDL_syswm.h>
#include <SDL/SDL_thread.h>

// standard C headers
#include <math.h>
#include <unistd.h>

// MAME headers

#include "emu.h"
#include "emuopts.h"
#include "ui.h"


// OSD headers

#include "window.h"
#include "input.h"
#include "osdsdl.h"

//============================================================
//  PARAMETERS
//============================================================

// these are arbitrary values since AFAIK there's no way to make X/SDL tell you
#define WINDOW_DECORATION_WIDTH	(8)	// should be more than plenty
#define WINDOW_DECORATION_HEIGHT (48)	// title bar + bottom drag region

#ifdef MAME_DEBUG
//#define ASSERT_USE(x) do { printf("%x %x\n", (int) SDL_ThreadID(), x); assert_always(SDL_ThreadID() == x, "Wrong Thread"); } while (0)
#define ASSERT_USE(x)	do { SDL_threadID _thid = SDL_ThreadID(); assert_always( _thid == x, "Wrong Thread"); } while (0)
#else
#define ASSERT_USE(x)	do {} while (0)
//#define ASSERT_USE(x) assert(SDL_ThreadID() == x)
#endif

#define ASSERT_REDRAW_THREAD()	ASSERT_USE(window_threadid)
#define ASSERT_WINDOW_THREAD()	ASSERT_USE(window_threadid)
#define ASSERT_MAIN_THREAD()	ASSERT_USE(main_threadid)

#define OSDWORK_CALLBACK(name)	void *name(void *param, ATTR_UNUSED int threadid)

// minimum window dimension
#define MIN_WINDOW_DIM					200

#ifndef SDLMAME_WIN32
#define WMSZ_TOP		(0)
#define WMSZ_BOTTOM		(1)
#define WMSZ_BOTTOMLEFT		(2)
#define WMSZ_BOTTOMRIGHT	(3)
#define WMSZ_LEFT		(4)
#define WMSZ_TOPLEFT		(5)
#define WMSZ_TOPRIGHT		(6)
#define WMSZ_RIGHT		(7)
#endif

//============================================================
//  GLOBAL VARIABLES
//============================================================

sdl_window_info *sdl_window_list;


//============================================================
//  LOCAL VARIABLES
//============================================================

static sdl_window_info **last_window_ptr;

// event handling
static int multithreading_enabled;
static osd_work_queue *work_queue;

#if !(SDL_VERSION_ATLEAST(1,3,0))
typedef int SDL_threadID;
#endif

static SDL_threadID main_threadid;
static SDL_threadID window_threadid;

// debugger
//static int in_background;

static sdl_draw_info draw;

typedef struct _worker_param worker_param;
struct _worker_param {
	running_machine &machine() const { assert(m_machine != NULL); return *m_machine; }
	sdl_window_info *window;
	render_primitive_list *list;
	running_machine *m_machine;
	int resize_new_width;
	int resize_new_height;
};


//============================================================
//  PROTOTYPES
//============================================================

static void sdlwindow_exit(running_machine &machine);
static void sdlwindow_video_window_destroy(running_machine &machine, sdl_window_info *window);
static OSDWORK_CALLBACK( draw_video_contents_wt );
static OSDWORK_CALLBACK( sdlwindow_video_window_destroy_wt );
static OSDWORK_CALLBACK( sdlwindow_resize_wt );
static OSDWORK_CALLBACK( sdlwindow_toggle_full_screen_wt );
static void sdlwindow_update_cursor_state(running_machine &machine, sdl_window_info *window);
static void sdlwindow_sync(void);

static void get_min_bounds(sdl_window_info *window, int *window_width, int *window_height, int constrain);
static void get_max_bounds(sdl_window_info *window, int *window_width, int *window_height, int constrain);

static void *complete_create_wt(void *param, int threadid);
static void set_starting_view(running_machine &machine, int index, sdl_window_info *window, const char *defview, const char *view);

//============================================================
//  clear the worker_param structure, inline - faster than memset
//============================================================

INLINE void clear_worker_param(worker_param *wp)
{
	wp->window=NULL;
	wp->list=NULL;
	wp->m_machine=NULL;
	wp->resize_new_width=0;
	wp->resize_new_height=0;
}


//============================================================
//  execute_async
//============================================================


INLINE void execute_async(osd_work_callback callback, worker_param *wp)
{
	worker_param *wp_temp = NULL;

	if (wp)
	{
		wp_temp = (worker_param *) osd_malloc(sizeof(worker_param));
		*wp_temp = *wp;
	}
	if (multithreading_enabled)
	{
		osd_work_item_queue(work_queue, callback, (void *) wp_temp, WORK_ITEM_FLAG_AUTO_RELEASE);
	} else
		callback((void *) wp_temp, 0);
}


//============================================================
//  execute_async_wait
//============================================================

INLINE void execute_async_wait(osd_work_callback callback, worker_param *wp)
{
	execute_async(callback, wp);
	sdlwindow_sync();
}


//============================================================
//  sdlwindow_thread_id
//  (window thread)
//============================================================

static OSDWORK_CALLBACK(sdlwindow_thread_id)
{
	window_threadid = SDL_ThreadID();

	if (SDLMAME_INIT_IN_WORKER_THREAD)
	{
#if (SDL_VERSION_ATLEAST(1,3,0))
		if (SDL_InitSubSystem(SDL_INIT_TIMER|SDL_INIT_AUDIO| SDL_INIT_VIDEO| SDL_INIT_JOYSTICK|SDL_INIT_NOPARACHUTE))
#else
		if (SDL_Init(SDL_INIT_TIMER|SDL_INIT_AUDIO| SDL_INIT_VIDEO| SDL_INIT_JOYSTICK|SDL_INIT_NOPARACHUTE))
#endif
		{
			mame_printf_error("Could not initialize SDL: %s.\n", SDL_GetError());
			exit(-1);
		}
	}
	return NULL;
}


//============================================================
//  win_init_window
//  (main thread)
//============================================================

int sdlwindow_init(running_machine &machine)
{
	mame_printf_verbose("Enter sdlwindow_init\n");
	// determine if we are using multithreading or not
	multithreading_enabled = downcast<sdl_options &>(machine.options()).multithreading();

	// get the main thread ID before anything else
	main_threadid = SDL_ThreadID();

	// ensure we get called on the way out
	machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(sdlwindow_exit), &machine));

	// if multithreading, create a thread to run the windows
	if (multithreading_enabled)
	{
		// create a thread to run the windows from
		work_queue = osd_work_queue_alloc(WORK_QUEUE_FLAG_IO);
		if (work_queue == NULL)
			return 1;
		osd_work_item_queue(work_queue, &sdlwindow_thread_id, NULL, WORK_ITEM_FLAG_AUTO_RELEASE);
	}
	else
	{
		// otherwise, treat the window thread as the main thread
		//window_threadid = main_threadid;
		sdlwindow_thread_id(NULL, 0);
	}

	// initialize the drawers
#if USE_OPENGL
	if (video_config.mode == VIDEO_MODE_OPENGL)
	{
		if (drawogl_init(machine, &draw))
			video_config.mode = VIDEO_MODE_SOFT;
	}
#endif
#if	SDL_VERSION_ATLEAST(1,3,0)
	if (video_config.mode == VIDEO_MODE_SDL13)
	{
		if (draw13_init(machine, &draw))
			video_config.mode = VIDEO_MODE_SOFT;
	}
#endif
	if (video_config.mode == VIDEO_MODE_SOFT)
	{
		if (drawsdl_init(&draw))
			return 1;
	}

	// set up the window list
	last_window_ptr = &sdl_window_list;
	mame_printf_verbose("Leave sdlwindow_init\n");
	return 0;
}


//============================================================
//  sdlwindow_sync
//============================================================

static void sdlwindow_sync(void)
{
	if (multithreading_enabled)
	{
		// Fallback
		while (!osd_work_queue_wait(work_queue, osd_ticks_per_second()*10))
		{
			mame_printf_warning("sdlwindow_sync: Sleeping...\n");
			osd_sleep(100000);
		}
	}
}


//============================================================
//  sdlwindow_exit
//  (main thread)
//============================================================

static OSDWORK_CALLBACK( sdlwindow_exit_wt )
{
	if (SDLMAME_INIT_IN_WORKER_THREAD)
		SDL_Quit();

	if (param)
		osd_free(param);
	return NULL;
}


static void sdlwindow_exit(running_machine &machine)
{
	ASSERT_MAIN_THREAD();

	mame_printf_verbose("Enter sdlwindow_exit\n");

	// free all the windows
	while (sdl_window_list != NULL)
	{
		sdl_window_info *temp = sdl_window_list;
		sdl_window_list = temp->next;
		sdlwindow_video_window_destroy(machine, temp);
	}

	// if we're multithreaded, clean up the window thread
	if (multithreading_enabled)
	{
		sdlwindow_sync();
	}

	// kill the drawers
	(*draw.exit)();

	execute_async_wait(&sdlwindow_exit_wt, NULL);

	if (multithreading_enabled)
	{
		osd_work_queue_wait(work_queue, 1000000);
		osd_work_queue_free(work_queue);
	}
	mame_printf_verbose("Leave sdlwindow_exit\n");

}


//============================================================
//  sdlwindow_blit_surface_size
//============================================================

INLINE int better_mode(int width0, int height0, int width1, int height1, float desired_aspect)
{
	float aspect0 = (float)width0 / (float)height0;
	float aspect1 = (float)width1 / (float)height1;
	return (fabs(desired_aspect - aspect0) < fabs(desired_aspect - aspect1)) ? 0 : 1;
}

void sdlwindow_blit_surface_size(sdl_window_info *window, int window_width, int window_height)
{
	INT32 newwidth, newheight;
	int xscale = 1, yscale = 1;
	float desired_aspect = 1.0f;
	INT32 target_width = window_width;
	INT32 target_height = window_height;

	// start with the minimum size
	window->target->compute_minimum_size(newwidth, newheight);

	// compute the appropriate visible area if we're trying to keepaspect
	if (video_config.keepaspect)
	{
		// make sure the monitor is up-to-date
		sdlvideo_monitor_refresh(window->monitor);
		window->target->compute_visible_area(target_width, target_height, sdlvideo_monitor_get_aspect(window->monitor), window->target->orientation(), target_width, target_height);
		desired_aspect = (float)target_width / (float)target_height;
	}

    // non-integer scaling - often gives more pleasing results in full screen
    if (!video_config.fullstretch)
	{
		// compute maximum integral scaling to fit the window
		xscale = (target_width + 2) / newwidth;
		yscale = (target_height + 2) / newheight;

		// try a little harder to keep the aspect ratio if desired
		if (video_config.keepaspect)
		{
			// if we could stretch more in the X direction, and that makes a better fit, bump the xscale
			while (newwidth * (xscale + 1) <= window_width &&
				better_mode(newwidth * xscale, newheight * yscale, newwidth * (xscale + 1), newheight * yscale, desired_aspect))
				xscale++;

			// if we could stretch more in the Y direction, and that makes a better fit, bump the yscale
			while (newheight * (yscale + 1) <= window_height &&
				better_mode(newwidth * xscale, newheight * yscale, newwidth * xscale, newheight * (yscale + 1), desired_aspect))
				yscale++;

			// now that we've maxed out, see if backing off the maximally stretched one makes a better fit
			if (window_width - newwidth * xscale < window_height - newheight * yscale)
			{
				while (better_mode(newwidth * xscale, newheight * yscale, newwidth * (xscale - 1), newheight * yscale, desired_aspect) && (xscale >= 0))
					xscale--;
			}
			else
			{
				while (better_mode(newwidth * xscale, newheight * yscale, newwidth * xscale, newheight * (yscale - 1), desired_aspect) && (yscale >= 0))
					yscale--;
			}
		}

		// ensure at least a scale factor of 1
		if (xscale <= 0) xscale = 1;
		if (yscale <= 0) yscale = 1;

		// apply the final scale
		newwidth *= xscale;
		newheight *= yscale;
	}
	else
	{
		newwidth = target_width;
		newheight = target_height;
	}

    //FIXME: really necessary to distinguish for yuv_modes ?
	if (window->target->zoom_to_screen()
		&& (window->scale_mode == VIDEO_SCALE_MODE_NONE ))
		newwidth = window_width;

	if ((window->blitwidth != newwidth) || (window->blitheight != newheight))
		sdlwindow_clear(window);

	window->blitwidth = newwidth;
	window->blitheight = newheight;
}


//============================================================
//  sdlwindow_resize
//  (main thread)
//============================================================

static OSDWORK_CALLBACK( sdlwindow_resize_wt )
{
	worker_param *		wp = (worker_param *) param;
	sdl_window_info *	window = wp->window;

	ASSERT_WINDOW_THREAD();

	window->destroy_all_textures(window);
	window->resize(window, wp->resize_new_width, wp->resize_new_height);

	sdlwindow_blit_surface_size(window, wp->resize_new_width, wp->resize_new_height);

	sdlwindow_clear(window);

	osd_free(wp);
	return NULL;
}

void sdlwindow_resize(sdl_window_info *window, INT32 width, INT32 height)
{
	worker_param wp;

	ASSERT_MAIN_THREAD();

	if (width == window->width && height == window->height)
		return;

	clear_worker_param(&wp);
	wp.resize_new_width = width;
	wp.resize_new_height = height;
	wp.window = window;

	execute_async_wait(&sdlwindow_resize_wt, &wp);
}


//============================================================
//  sdlwindow_clear_surface
//  (window thread)
//============================================================

static OSDWORK_CALLBACK( sdlwindow_clear_surface_wt )
{
	worker_param *wp = (worker_param *) param;
	sdl_window_info *window = wp->window;

	ASSERT_WINDOW_THREAD();

	window->clear(window);
	osd_free(wp);
	return NULL;
}

void sdlwindow_clear(sdl_window_info *window)
{
	worker_param *wp = (worker_param *) osd_malloc(sizeof(worker_param));

	clear_worker_param(wp);
	wp->window = window;

	if (SDL_ThreadID() == main_threadid)
	{
		execute_async_wait(&sdlwindow_clear_surface_wt, wp);
		osd_free(wp);
	}
	else
		sdlwindow_clear_surface_wt( (void *) wp, 0);
}


//============================================================
//  sdlwindow_toggle_full_screen
//  (main thread)
//============================================================

static OSDWORK_CALLBACK( sdlwindow_toggle_full_screen_wt )
{
	worker_param *wp = (worker_param *) param;
	sdl_window_info *window = wp->window;

	ASSERT_WINDOW_THREAD();

	// if we are in debug mode, never go full screen
	if (window->machine().debug_flags & DEBUG_FLAG_OSD_ENABLED)
		return NULL;

	// If we are going fullscreen (leaving windowed) remember our windowed size
	if (!window->fullscreen)
	{
		window->windowed_width = window->width;
		window->windowed_height = window->height;
	}

	window->destroy(window);
	sdlinput_release_keys(wp->machine());

	// toggle the window mode
	window->fullscreen = !window->fullscreen;

	complete_create_wt(param, 0);

	return NULL;
}

void sdlwindow_toggle_full_screen(running_machine &machine, sdl_window_info *window)
{
	worker_param wp;

	ASSERT_MAIN_THREAD();

	clear_worker_param(&wp);
	wp.window = window;
	wp.m_machine = &machine;

	execute_async_wait(&sdlwindow_toggle_full_screen_wt, &wp);
}

static OSDWORK_CALLBACK( destroy_all_textures_wt )
{
	worker_param *wp = (worker_param *) param;

	sdl_window_info *window = wp->window;

	window->destroy_all_textures(window);

	osd_free(wp);
	return NULL;
}

void sdlwindow_modify_prescale(running_machine &machine, sdl_window_info *window, int dir)
{
	worker_param wp;
	int new_prescale = window->prescale;

	clear_worker_param(&wp);

	wp.window = window;
	wp.m_machine = &machine;

	if (dir > 0 && window->prescale < 3)
		new_prescale = window->prescale + 1;
	if (dir < 0 && window->prescale > 1)
		new_prescale = window->prescale - 1;

	if (new_prescale != window->prescale)
	{
		if (window->fullscreen && video_config.switchres)
		{
			execute_async_wait(&sdlwindow_video_window_destroy_wt, &wp);

			window->prescale = new_prescale;

			execute_async_wait(&complete_create_wt, &wp);

		}
		else
		{
			execute_async_wait(destroy_all_textures_wt, &wp);
			window->prescale = new_prescale;
		}
		ui_popup_time(1, "Prescale %d", window->prescale);
	}
}

//============================================================
//  sdlwindow_update_cursor_state
//  (main or window thread)
//============================================================

static void sdlwindow_update_cursor_state(running_machine &machine, sdl_window_info *window)
{
#if (SDL_VERSION_ATLEAST(1,3,0))
	// do not do mouse capture if the debugger's enabled to avoid
	// the possibility of losing control
	if (!(machine.debug_flags & DEBUG_FLAG_OSD_ENABLED))
	{
		//FIXME: SDL1.3: really broken: the whole SDL code
		//       will only work correct with relative mouse movements ...
		//SDL_SetRelativeMouseMode
		if (!window->fullscreen && !sdlinput_should_hide_mouse(machine))
		{
			SDL_ShowCursor(SDL_ENABLE);
			if (SDL_GetWindowGrab(window->sdl_window ))
				SDL_SetWindowGrab(window->sdl_window, SDL_FALSE);
		}
		else
		{
			SDL_ShowCursor(SDL_DISABLE);
			if (!SDL_GetWindowGrab(window->sdl_window))
				SDL_SetWindowGrab(window->sdl_window, SDL_TRUE);
		}
		SDL_SetCursor(NULL); // Force an update in case the underlying driver has changed visibility
	}

#else
	// do not do mouse capture if the debugger's enabled to avoid
	// the possibility of losing control
	if (!(machine.debug_flags & DEBUG_FLAG_OSD_ENABLED))
	{
		if ( window->fullscreen || sdlinput_should_hide_mouse(machine) )
		{
			SDL_ShowCursor(SDL_DISABLE);
			if (!SDL_WM_GrabInput(SDL_GRAB_QUERY))
			{
				SDL_WM_GrabInput(SDL_GRAB_ON);
			}
		}
		else
		{
			SDL_ShowCursor(SDL_ENABLE);
			if (SDL_WM_GrabInput(SDL_GRAB_QUERY))
			{
				SDL_WM_GrabInput(SDL_GRAB_OFF);
			}
		}
	}
#endif
}


//============================================================
//  sdlwindow_video_window_create
//  (main thread)
//============================================================

int sdlwindow_video_window_create(running_machine &machine, int index, sdl_monitor_info *monitor, const sdl_window_config *config)
{
	sdl_window_info *window;
	worker_param *wp = (worker_param *) osd_malloc(sizeof(worker_param));
	int result;

	ASSERT_MAIN_THREAD();

	clear_worker_param(wp);

	// allocate a new window object
	window = global_alloc_clear(sdl_window_info);
	window->maxwidth = config->width;
	window->maxheight = config->height;
	window->depth = config->depth;
	window->refresh = config->refresh;
	window->monitor = monitor;
	window->m_machine = &machine;
	window->index = index;

	//FIXME: these should be per_window in config-> or even better a bit set
	window->fullscreen = !video_config.windowed;
	window->prescale = video_config.prescale;
	window->scale_mode = video_config.scale_mode;

	// set the initial maximized state
	// FIXME: Does not belong here
	sdl_options &options = downcast<sdl_options &>(machine.options());
	window->startmaximized = options.maximize();

	if (!window->fullscreen)
	{
		window->windowed_width = config->width;
		window->windowed_height = config->height;
	}
	window->totalColors = config->totalColors;

	// add us to the list
	*last_window_ptr = window;
	last_window_ptr = &window->next;

	draw.attach(&draw, window);

	// create an event that we can use to skip blitting
	window->rendered_event = osd_event_alloc(FALSE, TRUE);

	// load the layout
	window->target = machine.render().target_alloc();

	// set the specific view
	set_starting_view(machine, index, window, options.view(), options.view(index));

	// make the window title
	if (video_config.numscreens == 1)
		sprintf(window->title, "%s: %s [%s]", emulator_info::get_appname(), machine.system().description, machine.system().name);
	else
		sprintf(window->title, "%s: %s [%s] - Screen %d", emulator_info::get_appname(), machine.system().description, machine.system().name, index);

	wp->window = window;

	if (multithreading_enabled)
	{
		osd_work_item *wi;

		wi = osd_work_item_queue(work_queue, &complete_create_wt, (void *) wp, 0);
		sdlwindow_sync();
		result = *((int *) (osd_work_item_result)(wi));
		osd_work_item_release(wi);
	}
	else
		result = *((int *) complete_create_wt((void *) wp, 0));

	// handle error conditions
	if (result == 1)
		goto error;

	return 0;

error:
	sdlwindow_video_window_destroy(machine, window);
	return 1;
}


//============================================================
//  sdlwindow_video_window_destroy
//  (main thread)
//============================================================

static OSDWORK_CALLBACK( sdlwindow_video_window_destroy_wt )
{
	worker_param *		wp = (worker_param *) param;
	sdl_window_info *	window = wp->window;

	ASSERT_WINDOW_THREAD();

	// free the textures etc
	window->destroy(window);

	// release all keys ...
	sdlinput_release_keys(wp->machine());


	osd_free(wp);
	return NULL;
}

static void sdlwindow_video_window_destroy(running_machine &machine, sdl_window_info *window)
{
	sdl_window_info **prevptr;
	worker_param wp;

	ASSERT_MAIN_THREAD();
	if (multithreading_enabled)
	{
		sdlwindow_sync();
	}

	//osd_event_wait(window->rendered_event, osd_ticks_per_second()*10);

	// remove us from the list
	for (prevptr = &sdl_window_list; *prevptr != NULL; prevptr = &(*prevptr)->next)
		if (*prevptr == window)
		{
			*prevptr = window->next;
			break;
		}

	// free the textures etc
	clear_worker_param(&wp);
	wp.window = window;
	wp.m_machine = &machine;
	execute_async_wait(&sdlwindow_video_window_destroy_wt, &wp);

	// free the render target, after the textures!
	window->machine().render().target_free(window->target);

	// free the event
	osd_event_free(window->rendered_event);

	// free the window itself
	global_free(window);
}


//============================================================
//  pick_best_mode
//============================================================

#if SDL_VERSION_ATLEAST(1,3,0)
static void pick_best_mode(sdl_window_info *window, int *fswidth, int *fsheight)
{
	int minimum_width, minimum_height, target_width, target_height;
	int i;
	int num;
	float size_score, best_score = 0.0f;

	// determine the minimum width/height for the selected target
	window->target->compute_minimum_size(minimum_width, minimum_height);

	// use those as the target for now
	target_width = minimum_width * MAX(1, window->prescale);
	target_height = minimum_height * MAX(1, window->prescale);

	// if we're not stretching, allow some slop on the minimum since we can handle it
	{
		minimum_width -= 4;
		minimum_height -= 4;
	}

	num = SDL_GetNumDisplayModes(window->monitor->handle);

	if (num == 0)
	{
		mame_printf_error("SDL: No modes available?!\n");
		exit(-1);
	}
	else
	{
		for (i = 0; i < num; ++i)
		{
			SDL_DisplayMode mode;
			SDL_GetDisplayMode(window->monitor->handle, i, &mode);

			// compute initial score based on difference between target and current
			size_score = 1.0f / (1.0f + fabsf((INT32)mode.w - target_width) + fabsf((INT32)mode.h - target_height));

			// if the mode is too small, give a big penalty
			if (mode.w < minimum_width || mode.h < minimum_height)
				size_score *= 0.01f;

			// if mode is smaller than we'd like, it only scores up to 0.1
			if (mode.w < target_width || mode.h < target_height)
				size_score *= 0.1f;

			// if we're looking for a particular mode, that's a winner
			if (mode.w == window->maxwidth && mode.h == window->maxheight)
				size_score = 2.0f;

			// refresh adds some points
			if (window->refresh)
				size_score *= 1.0f / (1.0f + fabsf(window->refresh - mode.refresh_rate) / 10.0f);

			mame_printf_verbose("%4dx%4d@%2d -> %f\n", (int)mode.w, (int)mode.h, (int) mode.refresh_rate, size_score);

			// best so far?
			if (size_score > best_score)
			{
				best_score = size_score;
				*fswidth = mode.w;
				*fsheight = mode.h;
			}

		}
	}
}
#else
static void pick_best_mode(sdl_window_info *window, int *fswidth, int *fsheight)
{
	int minimum_width, minimum_height, target_width, target_height;
	int i;
	float size_score, best_score = 0.0f;
	SDL_Rect **modes;

	// determine the minimum width/height for the selected target
	window->target->compute_minimum_size(minimum_width, minimum_height);

	// use those as the target for now
	target_width = minimum_width * MAX(1, window->prescale);
	target_height = minimum_height * MAX(1, window->prescale);

	// if we're not stretching, allow some slop on the minimum since we can handle it
	{
		minimum_width -= 4;
		minimum_height -= 4;
	}

#if 1 // defined(SDLMAME_WIN32)
	/*
     *  We need to do this here. If SDL_ListModes is
     * called in init_monitors, the call will crash
     * on win32
     */
	modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_DOUBLEBUF);
#else
	modes = window->monitor->modes;
#endif

	if (modes == (SDL_Rect **)0)
	{
		mame_printf_error("SDL: No modes available?!\n");
		exit(-1);
	}
	else if (modes == (SDL_Rect **)-1)	// all modes are possible
	{
		*fswidth = window->maxwidth;
		*fsheight = window->maxheight;
	}
	else
	{
		for (i = 0; modes[i]; ++i)
		{
			// compute initial score based on difference between target and current
			size_score = 1.0f / (1.0f + fabsf((INT32)modes[i]->w - target_width) + fabsf((INT32)modes[i]->h - target_height));

			// if the mode is too small, give a big penalty
			if (modes[i]->w < minimum_width || modes[i]->h < minimum_height)
				size_score *= 0.01f;

			// if mode is smaller than we'd like, it only scores up to 0.1
			if (modes[i]->w < target_width || modes[i]->h < target_height)
				size_score *= 0.1f;

			// if we're looking for a particular mode, that's a winner
			if (modes[i]->w == window->maxwidth && modes[i]->h == window->maxheight)
				size_score = 2.0f;

			mame_printf_verbose("%4dx%4d -> %f\n", (int)modes[i]->w, (int)modes[i]->h, size_score);

			// best so far?
			if (size_score > best_score)
			{
				best_score = size_score;
				*fswidth = modes[i]->w;
				*fsheight = modes[i]->h;
			}

		}
	}
}
#endif

//============================================================
//  sdlwindow_video_window_update
//  (main thread)
//============================================================

void sdlwindow_video_window_update(running_machine &machine, sdl_window_info *window)
{

	osd_ticks_t		event_wait_ticks;
	ASSERT_MAIN_THREAD();

	// adjust the cursor state
	sdlwindow_update_cursor_state(machine, window);

	// if we're visible and running and not in the middle of a resize, draw
	if (window->target != NULL)
	{
		int tempwidth, tempheight;

		// see if the games video mode has changed
		window->target->compute_minimum_size(tempwidth, tempheight);
		if (tempwidth != window->minwidth || tempheight != window->minheight)
		{
			window->minwidth = tempwidth;
			window->minheight = tempheight;
			if (!window->fullscreen)
			{
				sdlwindow_blit_surface_size(window, window->width, window->height);
				sdlwindow_resize(window, window->blitwidth, window->blitheight);
			}
			else if (video_config.switchres)
			{
				pick_best_mode(window, &tempwidth, &tempheight);
				sdlwindow_resize(window, tempwidth, tempheight);
			}
		}

		if (video_config.waitvsync && video_config.syncrefresh)
			event_wait_ticks = osd_ticks_per_second(); // block at most a second
		else
			event_wait_ticks = 0;

		if (osd_event_wait(window->rendered_event, event_wait_ticks))
		{
			worker_param wp;
			render_primitive_list *primlist;

			clear_worker_param(&wp);

			// ensure the target bounds are up-to-date, and then get the primitives
			primlist = &window->get_primitives(window);

			// and redraw now

			wp.list = primlist;
			wp.window = window;
			wp.m_machine = &machine;

			execute_async(&draw_video_contents_wt, &wp);
		}
	}
}


//============================================================
//  set_starting_view
//  (main thread)
//============================================================

static void set_starting_view(running_machine &machine, int index, sdl_window_info *window, const char *defview, const char *view)
{
	int viewindex;

	ASSERT_MAIN_THREAD();

	// choose non-auto over auto
	if (strcmp(view, "auto") == 0 && strcmp(defview, "auto") != 0)
		view = defview;

	// query the video system to help us pick a view
	viewindex = window->target->configured_view(view, index, video_config.numscreens);

	// set the view
	window->target->set_view(viewindex);
	window->start_viewscreen=viewindex;
}


//============================================================
//  complete_create
//  (window thread)
//============================================================

static OSDWORK_CALLBACK( complete_create_wt )
{
	worker_param *		wp = (worker_param *) param;
	sdl_window_info *	window = wp->window;

	int tempwidth, tempheight;
	static int result[2] = {0,1};

	ASSERT_WINDOW_THREAD();
	osd_free(wp);

	if (window->fullscreen)
	{
		// default to the current mode exactly
		tempwidth = window->monitor->monitor_width;
		tempheight = window->monitor->monitor_height;

		// if we're allowed to switch resolutions, override with something better
		if (video_config.switchres)
			pick_best_mode(window, &tempwidth, &tempheight);
	}
	else if (window->windowed_width)
	{
		// if we have a remembered size force the new window size to it
		tempwidth = window->windowed_width;
		tempheight = window->windowed_height;
	}
	else
	{
		if (window->startmaximized)
		{
			tempwidth = tempheight = 0;
			get_max_bounds(window, &tempwidth, &tempheight, video_config.keepaspect );
		}
		else
		{
			/* Create the window directly with the correct aspect
               instead of letting sdlwindow_blit_surface_size() resize it
               this stops the window from "flashing" from the wrong aspect
               size to the right one at startup. */
			tempwidth = (window->maxwidth != 0) ? window->maxwidth : 640;
			tempheight = (window->maxheight != 0) ? window->maxheight : 480;

			get_min_bounds(window, &tempwidth, &tempheight, video_config.keepaspect );
		}
	}

	// initialize the drawing backend
	if (window->create(window, tempwidth, tempheight))
		return (void *) &result[1];

	// Make sure we have a consistent state
	SDL_ShowCursor(0);
	SDL_ShowCursor(1);

	return (void *) &result[0];
}


//============================================================
//  draw_video_contents
//  (window thread)
//============================================================

static void measure_fps(sdl_window_info *window, UINT32 dc, int update)
{
	const unsigned long frames_skip4fps = 100;
	static int64_t lastTime=0, sumdt=0, startTime=0;
	static unsigned long frames = 0;
	int64_t currentTime, t0;
	double dt;
	double tps;
	osd_ticks_t tps_t;

	tps_t = osd_ticks_per_second();
	tps = (double) tps_t;

	t0 = osd_ticks();

	window->draw(window, dc, update);

	frames++;
	currentTime = osd_ticks();
	if(startTime==0||frames==frames_skip4fps)
		startTime=currentTime;
	if( frames>=frames_skip4fps )
		sumdt+=currentTime-t0;
	if( (currentTime-lastTime)>1L*osd_ticks_per_second() && frames>frames_skip4fps )
	{
		dt = (double) (currentTime-startTime) / tps; // in decimale sec.
		mame_printf_info("%6.2lfs, %4lu F, "
	          "avrg game: %5.2lf FPS %.2lf ms/f, "
	          "avrg video: %5.2lf FPS %.2lf ms/f, "
	          "last video: %5.2lf FPS %.2lf ms/f\n",
		  dt, frames-frames_skip4fps,
		  (double)(frames-frames_skip4fps)/dt,                             // avrg game fps
		  ( (currentTime-startTime) / ((frames-frames_skip4fps)) ) * 1000.0 / osd_ticks_per_second(),
		  (double)(frames-frames_skip4fps)/((double)(sumdt) / tps), // avrg vid fps
		  ( sumdt / ((frames-frames_skip4fps)) ) * 1000.0 / tps,
		  1.0/((currentTime-t0) / osd_ticks_per_second()), // this vid fps
		  (currentTime-t0) * 1000.0 / tps
		);
		lastTime = currentTime;
	}
}

static OSDWORK_CALLBACK( draw_video_contents_wt )
{
	UINT32	dc =		0;
	int 	update =	1;
	worker_param *wp = (worker_param *) param;
	sdl_window_info *window = wp->window;

	ASSERT_REDRAW_THREAD();

	// Some configurations require events to be polled in the worker thread
	sdlinput_process_events_buf(wp->machine());

	window->primlist = wp->list;

	// if no bitmap, just fill
	if (window->primlist == NULL)
	{
	}
	// otherwise, render with our drawing system
	else
	{
		if( video_config.perftest )
			measure_fps(window, dc, update);
		else
			window->draw(window, dc, update);
	}

	/* all done, ready for next */
	osd_event_set(window->rendered_event);
	osd_free(wp);

	return NULL;
}


//============================================================
//  constrain_to_aspect_ratio
//  (window thread)
//============================================================

static void constrain_to_aspect_ratio(sdl_window_info *window, int *window_width, int *window_height, int adjustment)
{
	INT32 extrawidth = 0;
	INT32 extraheight = 0;
	INT32 propwidth, propheight;
	INT32 minwidth, minheight;
	INT32 maxwidth, maxheight;
	INT32 viswidth, visheight;
	float pixel_aspect;

	// make sure the monitor is up-to-date
	sdlvideo_monitor_refresh(window->monitor);

	// get the pixel aspect ratio for the target monitor
	pixel_aspect = sdlvideo_monitor_get_aspect(window->monitor);

	// determine the proposed width/height
	propwidth = *window_width - extrawidth;
	propheight = *window_height - extraheight;

	// based on which edge we are adjusting, take either the width, height, or both as gospel
	// and scale to fit using that as our parameter
	switch (adjustment)
	{
		case WMSZ_BOTTOM:
		case WMSZ_TOP:
			window->target->compute_visible_area(10000, propheight, pixel_aspect, window->target->orientation(), propwidth, propheight);
			break;

		case WMSZ_LEFT:
		case WMSZ_RIGHT:
			window->target->compute_visible_area(propwidth, 10000, pixel_aspect, window->target->orientation(), propwidth, propheight);
			break;

		default:
			window->target->compute_visible_area(propwidth, propheight, pixel_aspect, window->target->orientation(), propwidth, propheight);
			break;
	}

	// get the minimum width/height for the current layout
	window->target->compute_minimum_size(minwidth, minheight);

	// clamp against the absolute minimum
	propwidth = MAX(propwidth, MIN_WINDOW_DIM);
	propheight = MAX(propheight, MIN_WINDOW_DIM);

	// clamp against the minimum width and height
	propwidth = MAX(propwidth, minwidth);
	propheight = MAX(propheight, minheight);

	// clamp against the maximum (fit on one screen for full screen mode)
	if (window->fullscreen)
	{
		maxwidth = window->monitor->center_width - extrawidth;
		maxheight = window->monitor->center_height - extraheight;
	}
	else
	{
		maxwidth = window->monitor->center_width - extrawidth;
		maxheight = window->monitor->center_height - extraheight;

		// further clamp to the maximum width/height in the window
		if (window->maxwidth != 0)
			maxwidth = MIN(maxwidth, window->maxwidth + extrawidth);
		if (window->maxheight != 0)
			maxheight = MIN(maxheight, window->maxheight + extraheight);
	}

	// clamp to the maximum
	propwidth = MIN(propwidth, maxwidth);
	propheight = MIN(propheight, maxheight);

	// compute the visible area based on the proposed rectangle
	window->target->compute_visible_area(propwidth, propheight, pixel_aspect, window->target->orientation(), viswidth, visheight);

	*window_width = viswidth;
	*window_height = visheight;
}


//============================================================
//  get_min_bounds
//  (window thread)
//============================================================

static void get_min_bounds(sdl_window_info *window, int *window_width, int *window_height, int constrain)
{
	INT32 minwidth, minheight;

	// get the minimum target size
	window->target->compute_minimum_size(minwidth, minheight);

	// expand to our minimum dimensions
	if (minwidth < MIN_WINDOW_DIM)
		minwidth = MIN_WINDOW_DIM;
	if (minheight < MIN_WINDOW_DIM)
		minheight = MIN_WINDOW_DIM;

	// if we want it constrained, figure out which one is larger
	if (constrain)
	{
		int test1w, test1h;
		int test2w, test2h;

		// first constrain with no height limit
		test1w = minwidth; test1h = 10000;
		constrain_to_aspect_ratio(window, &test1w, &test1h, WMSZ_BOTTOMRIGHT);

		// then constrain with no width limit
		test2w = 10000; test2h = minheight;
		constrain_to_aspect_ratio(window, &test2w, &test2h, WMSZ_BOTTOMRIGHT);

		// pick the larger
		if ( test1w > test2w )
		{
			minwidth = test1w;
			minheight = test1h;
		}
		else
		{
			minwidth = test2w;
			minheight = test2h;
		}
	}

	*window_width = minwidth;
	*window_height = minheight;
}


//============================================================
//  get_max_bounds
//  (window thread)
//============================================================

static void get_max_bounds(sdl_window_info *window, int *window_width, int *window_height, int constrain)
{
	INT32 maxwidth, maxheight;

	// compute the maximum client area
	maxwidth = window->monitor->center_width;
	maxheight = window->monitor->center_height;

	// clamp to the window's max
	if (window->maxwidth != 0)
	{
		int temp = window->maxwidth + WINDOW_DECORATION_WIDTH;
		if (temp < maxwidth)
			maxwidth = temp;
	}
	if (window->maxheight != 0)
	{
		int temp = window->maxheight + WINDOW_DECORATION_HEIGHT;
		if (temp < maxheight)
			maxheight = temp;
	}

	// constrain to fit
	if (constrain)
		constrain_to_aspect_ratio(window, &maxwidth, &maxheight, WMSZ_BOTTOMRIGHT);
	//else
	{
		maxwidth -= WINDOW_DECORATION_WIDTH;
		maxheight -= WINDOW_DECORATION_HEIGHT;
		*window_width = maxwidth;
		*window_height = maxheight;
	}
}

