/*
 * videomode.c - common code for changing of the display resolution
 *
 * Copyright (C) 2010 Tomasz Krasuski
 * Copyright (C) 2011 Atari800 development team (see DOC/CREDITS)
 *
 * This file is part of the Atari800 emulator project which emulates
 * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers.
 *
 * Atari800 is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Atari800 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Atari800; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <stdlib.h>

#include "videomode.h"

#if NTSC_FILTER
#include "atari_ntsc/atari_ntsc.h"
#endif
#ifdef AF80
#include "af80.h"
#endif
#ifdef BIT3
#include "bit3.h"
#endif
#include "artifact.h"
#include "atari.h"
#include "cfg.h"
#include "config.h"
#include "log.h"
#ifdef PBI_PROTO80
#include "pbi_proto80.h"
#endif
#include "platform.h"
#include "screen.h"
#include "util.h"
#ifdef XEP80_EMULATION
#include "xep80.h"
#endif

#if defined(XEP80_EMULATION) || defined(PBI_PROTO80) || defined(AF80) || defined(BIT3)
#define COLUMN_80 1
#else
#define COLUMN_80 0
#endif

#if NTSC_FILTER
#define VIDEOMODE_MODE_LASTWITHHUD VIDEOMODE_MODE_NTSC_FILTER
#else
#define VIDEOMODE_MODE_LASTWITHHUD VIDEOMODE_MODE_NORMAL
#endif

int VIDEOMODE_windowed = 1;

int VIDEOMODE_horizontal_area = VIDEOMODE_HORIZONTAL_TV;
unsigned int VIDEOMODE_custom_horizontal_area = Screen_WIDTH;
int VIDEOMODE_vertical_area = VIDEOMODE_VERTICAL_TV;
unsigned int VIDEOMODE_custom_vertical_area = Screen_HEIGHT;
int VIDEOMODE_horizontal_offset = 0;
int VIDEOMODE_vertical_offset = 0;
int VIDEOMODE_stretch = VIDEOMODE_STRETCH_INTEGER;
double VIDEOMODE_custom_stretch = 1.0f;
int VIDEOMODE_fit = VIDEOMODE_FIT_BOTH;
int VIDEOMODE_keep_aspect = VIDEOMODE_KEEP_ASPECT_SQUARE_PIXELS;
#if SUPPORTS_ROTATE_VIDEOMODE
int VIDEOMODE_rotate90 = FALSE;
#endif

/* Values of 0.0 indicate that aspect ratio should be autodetected
   in VIDEOMODE_Initialise(). */
double VIDEOMODE_host_aspect_ratio_w = 0.0;
double VIDEOMODE_host_aspect_ratio_h = 0.0;

unsigned int VIDEOMODE_src_offset_left;
unsigned int VIDEOMODE_src_offset_top;
unsigned int VIDEOMODE_src_width;
unsigned int VIDEOMODE_src_height;

unsigned int VIDEOMODE_actual_width;

unsigned int VIDEOMODE_dest_offset_left;
unsigned int VIDEOMODE_dest_offset_top;
unsigned int VIDEOMODE_dest_width;
unsigned int VIDEOMODE_dest_height;
double VIDEOMODE_dest_scale_factor = 1.0;

/* To have square pixels in NTSC, the screen should have a pixel clock of
   12+3/11 MHz (actually half of that, because Atari displays non-interlaced
   picture with half the vertical resolution). However Atari pixel clock
   equals 14,31818/2 MHz, which causes its pixels to be taller.
   Similarly for PAL, the square pixel clock is 14.75 MHz and the Atari pixel
   clock equals 14,187576/2 MHz, which gives slightly wider pixels.
   The two values below are used to stretch the screen a bit in order to
   exactly emulate the pixel aspect ratio. In order for this to work,
   VIDEOMODE_host_aspect_ratio_w and VIDEOMODE_host_aspect_ratio_h must be
   properly set.
   Source: http://en.wikipedia.org/wiki/Pixel_aspect_ratio */
static double const pixel_aspect_ratio_ntsc = (12.0 + 3.0/11.0) / 14.31818;
static double const pixel_aspect_ratio_pal = 14.75 / 14.187576;

#ifdef XEP80_EMULATION
/* XEP80 dot clock is 12 MHz. */
static double const xep80_aspect_ratio_ntsc = (12.0 + 3.0/11.0) / 2.0 / 12.0;
static double const xep80_aspect_ratio_pal = 14.75 / 2.0 / 12.0;
#endif /* XEP80_EMULATION */

static char const * const horizontal_area_cfg_strings[VIDEOMODE_HORIZONTAL_SIZE] = {
	"NARROW",
	"TV",
	"FULL"
};
static char const * const vertical_area_cfg_strings[VIDEOMODE_VERTICAL_SIZE] = {
	"SHORT",
	"TV",
	"FULL"
};
static char const * const stretch_cfg_strings[VIDEOMODE_STRETCH_SIZE] = {
	"NONE",
	"2",
	"3",
	"INTEGRAL",
	"FULL"
};
static char const * const fit_cfg_strings[VIDEOMODE_FIT_SIZE] = {
	"WIDTH",
	"HEIGHT",
	"BOTH"
};
static char const * const keep_aspect_cfg_strings[VIDEOMODE_KEEP_ASPECT_SIZE] = {
	"NONE",
	"SQUARE-PIXELS",
	"REAL"
};

typedef struct display_mode_t {
	unsigned int min_w; /* Minimum horizontal display resolution to show this mode */
	unsigned int min_h; /* Minimum vertical display resolution to show this mode */
	unsigned int src_width; /* Horizontal resolution of source image in this mode */
	unsigned int src_height; /* Vertical resolution of source image in this mode */
	int param2src_w_mult; /* Use this multiplier to compute needed horizontal amount of source image based on current "horizontal area" */
	/* Height multiplier for modes whose aspect ratio is far from 1. Blit
	   functions that don't stretch, should anyway stretch their output
	   vertically by this value. */
	int h_mult;
	double asp_ratio; /* Pixel aspect ratio of this mode */
	unsigned int (*src2out_w_func)(unsigned int); /* Function that converts source image width to output image width */
	unsigned int (*out2src_w_func)(unsigned int); /* Function that converts output image width to source image width */
} display_mode_t;

static unsigned int ReturnSame(unsigned int value);
#if NTSC_FILTER
static unsigned int UpscaleWidthNtsc(unsigned int w);
static unsigned int DownscaleWidthNtsc(unsigned int w);
#endif

/* TODO determine pixel aspect ratio for 80 column cards. */
static display_mode_t display_modes[VIDEOMODE_MODE_SIZE] = {
	{ 320, 200, Screen_WIDTH, Screen_HEIGHT, 1, 1, 1.0, &ReturnSame, &ReturnSame }
#if NTSC_FILTER
	, { 640, 400, Screen_WIDTH, Screen_HEIGHT, 1, 2, 0.5, &UpscaleWidthNtsc, &DownscaleWidthNtsc, }
#endif
#ifdef XEP80_EMULATION
	, { XEP80_SCRN_WIDTH, 400, XEP80_SCRN_WIDTH, XEP80_MAX_SCRN_HEIGHT, 2, 2, 0.5, &ReturnSame, &ReturnSame }
#endif
#ifdef PBI_PROTO80
	, { 640, 400, 640, 192, 2, 2, 0.5, &ReturnSame, &ReturnSame }
#endif
#ifdef AF80
	, { 640, 400, 640, 250, 2, 2, 0.5, &ReturnSame, &ReturnSame }
#endif
#ifdef BIT3
	, { 640, 400, 640, 240, 2, 2, 0.5, &ReturnSame, &ReturnSame }
#endif
};

#if COLUMN_80
int VIDEOMODE_80_column = TRUE;
#endif

static int force_windowed = FALSE;
static int force_standard_screen = FALSE;

static VIDEOMODE_resolution_t *resolutions = NULL;
static unsigned int resolutions_size;

static VIDEOMODE_resolution_t window_resolution = { 336, 240 };
/* This variable is used only during initialisation. */
static VIDEOMODE_resolution_t init_fs_resolution = { 336, 240 };
/* Currently chosen fullscreen resolution. */
static unsigned int current_resolution;

unsigned int VIDEOMODE_NumAvailableResolutions(void)
{
	return resolutions_size;
}

void VIDEOMODE_CopyResolutionName(unsigned int res_id, char *target, unsigned int size)
{
	snprintf(target, size, "%ix%i", resolutions[res_id].width, resolutions[res_id].height);
}

unsigned int VIDEOMODE_GetFullscreenResolution(void)
{
	return current_resolution;
}

/* Chooses the current display mode based on current configuration. */
static VIDEOMODE_MODE_t CurrentDisplayMode(void)
{
#if COLUMN_80
	if (!force_standard_screen && VIDEOMODE_80_column) {
#ifdef XEP80_EMULATION
		if (XEP80_enabled)
			return VIDEOMODE_MODE_XEP80;
#endif
#ifdef PBI_PROTO80
		if (PBI_PROTO80_enabled)
			return VIDEOMODE_MODE_PROTO80;
#endif
#ifdef AF80
		if (AF80_enabled)
			return VIDEOMODE_MODE_AF80;
#endif
#ifdef BIT3
		if (BIT3_enabled)
			return VIDEOMODE_MODE_BIT3;
#endif
	}
#endif /* COLUMN_80 */
#if NTSC_FILTER
	if (ARTIFACT_mode == ARTIFACT_NTSC_FULL)
		return VIDEOMODE_MODE_NTSC_FILTER;
#endif
	return VIDEOMODE_MODE_NORMAL;
}

/* Finds a good fullscreen resolution, taking into consideration defined
   minimal resolutions for various display modes. */
static VIDEOMODE_resolution_t *ResolutionForMode(VIDEOMODE_MODE_t mode, int rotate)
{
	unsigned int i;
	unsigned int w, h;
	if (rotate) {
		w = display_modes[mode].min_h;
		h = display_modes[mode].min_w;
	} else {
		w = display_modes[mode].min_w;
		h = display_modes[mode].min_h;
	}
	if (resolutions[current_resolution].width >= w &&
	    resolutions[current_resolution].height >= h)
		/* Current resolution is OK for this display mode. */
		return &resolutions[current_resolution];

	/* Search for resolution higher than minimal. */
	for (i = 0; i < resolutions_size; i ++) {
		if (resolutions[i].width >= w &&
		    resolutions[i].height >= h)
			return &resolutions[i];
	}

	/* No resolution found! */
	return NULL;
}

static unsigned int ReturnSame(unsigned int value)
{
	return value;
}

#if NTSC_FILTER
static unsigned int UpscaleWidthNtsc(unsigned int w)
{
	return ATARI_NTSC_OUT_WIDTH(w);
}

static unsigned int DownscaleWidthNtsc(unsigned int w)
{
	return ATARI_NTSC_IN_WIDTH(w);
}
#endif /* NTSC_FILTER */

/* Switches RES's width and height. */
static void RotateResolution(VIDEOMODE_resolution_t *res)
{
	unsigned int tmp = res->width;
	res->width = res->height;
	res->height = tmp;
}

/* Updates Screen_visible_* variables to allow proper positioning of
   on-screen indicators (disk drive activity etc.) */
static void SetScreenVisible(void)
{
	if (VIDEOMODE_src_offset_left < 24)
		Screen_visible_x1 = 24;
	else
		Screen_visible_x1 = VIDEOMODE_src_offset_left;
	if (VIDEOMODE_src_offset_left + VIDEOMODE_src_width > 360)
		Screen_visible_x2 = 360;
	else
		Screen_visible_x2 = VIDEOMODE_src_offset_left + VIDEOMODE_src_width;
	Screen_visible_y1 = VIDEOMODE_src_offset_top;
	Screen_visible_y2 = VIDEOMODE_src_offset_top + VIDEOMODE_src_height;
}

/* Computes source screen's visible area based on current settings. */
static void GetOutArea(unsigned int *w, unsigned int *h, VIDEOMODE_MODE_t display_mode)
{
	switch (VIDEOMODE_horizontal_area) {
	case VIDEOMODE_HORIZONTAL_NARROW:
		VIDEOMODE_custom_horizontal_area = 320;
		break;
	case VIDEOMODE_HORIZONTAL_TV:
		VIDEOMODE_custom_horizontal_area = 336;
		break;
	case VIDEOMODE_HORIZONTAL_FULL:
		VIDEOMODE_custom_horizontal_area = VIDEOMODE_MAX_HORIZONTAL_AREA;
		break;
	}
	*w = VIDEOMODE_custom_horizontal_area * display_modes[display_mode].param2src_w_mult;
	if (*w > display_modes[display_mode].src_width)
		*w = display_modes[display_mode].src_width;
	*w = (*display_modes[display_mode].src2out_w_func)(*w);

	switch (VIDEOMODE_vertical_area) {
	case VIDEOMODE_VERTICAL_SHORT:
		VIDEOMODE_custom_vertical_area = 200;
		break;
	case VIDEOMODE_VERTICAL_TV:
		if (Atari800_tv_mode == Atari800_TV_NTSC)
			VIDEOMODE_custom_vertical_area = 224;
		else
			VIDEOMODE_custom_vertical_area = 240;
		break;
	case VIDEOMODE_VERTICAL_FULL:
		VIDEOMODE_custom_vertical_area = VIDEOMODE_MAX_VERTICAL_AREA;
		break;
	}
	*h = VIDEOMODE_custom_vertical_area;
	if (*h > display_modes[display_mode].src_height)
		*h = display_modes[display_mode].src_height;
}

static void UpdateCustomStretch(void)
{
	switch (VIDEOMODE_stretch) {
	case VIDEOMODE_STRETCH_NONE:
	case VIDEOMODE_STRETCH_2X:
	case VIDEOMODE_STRETCH_3X:
		VIDEOMODE_custom_stretch = (double)(VIDEOMODE_stretch + 1);
		break;
	case VIDEOMODE_STRETCH_INTEGER:
	case VIDEOMODE_STRETCH_FULL:
		VIDEOMODE_custom_stretch = 1.0;
		break;
	}
}

/* Computes VIDEOMODE_dest_width/height based on various parameters. */
static void ComputeVideoArea(VIDEOMODE_resolution_t const *res, VIDEOMODE_resolution_t const *screen_res,
                             VIDEOMODE_MODE_t display_mode, unsigned int out_w, unsigned int out_h,
                             double *mult_w, double *mult_h, int rotate, double scale)
{
	double asp_ratio;
	/* asp_ratio = <Atari pixel aspect ratio>/<host pixel aspect ratio>.
	   If the screen is to be rotated, then <host pixel aspect ratio> gets inverted. */
	if (VIDEOMODE_keep_aspect == VIDEOMODE_KEEP_ASPECT_REAL) {
		if (rotate)
			asp_ratio = display_modes[display_mode].asp_ratio * screen_res->height / VIDEOMODE_host_aspect_ratio_h
			            * VIDEOMODE_host_aspect_ratio_w / screen_res->width;
		else
			asp_ratio = display_modes[display_mode].asp_ratio * screen_res->width / VIDEOMODE_host_aspect_ratio_w
			            * VIDEOMODE_host_aspect_ratio_h / screen_res->height;
	}
	else
		asp_ratio = 1.0 / display_modes[display_mode].h_mult;

	if (!PLATFORM_SupportsVideomode(display_mode, TRUE, rotate)) {
		*mult_w = 1.0;
		*mult_h = display_modes[display_mode].h_mult;
		VIDEOMODE_dest_width = out_w * *mult_w;
		VIDEOMODE_dest_height = out_h * *mult_h;
		return;
	}

	switch (VIDEOMODE_stretch) {
	case VIDEOMODE_STRETCH_NONE:
	case VIDEOMODE_STRETCH_2X:
	case VIDEOMODE_STRETCH_3X:
	case VIDEOMODE_STRETCH_CUSTOM:
		*mult_w = asp_ratio * VIDEOMODE_custom_stretch;
		*mult_h = VIDEOMODE_custom_stretch;
		break;
	case VIDEOMODE_STRETCH_INTEGER:
		{
			unsigned int imult_w = (double) res->width / out_w / asp_ratio;
			unsigned int imult_h = res->height / out_h;
			if (imult_w == 0)
				imult_w = 1;
			if (imult_h == 0)
				imult_h = 1;
			if (VIDEOMODE_keep_aspect == VIDEOMODE_KEEP_ASPECT_NONE) {
				*mult_w = (double)imult_w * asp_ratio;
			} else {
				switch (VIDEOMODE_fit) {
				case VIDEOMODE_FIT_WIDTH:
					imult_h = imult_w;
					break;
				case VIDEOMODE_FIT_HEIGHT:
					break;
				default: /* VIDEOMODE_FIT_BOTH */
					if (imult_w < imult_h)
						imult_h = imult_w;
				}
				*mult_w = (double)imult_h * asp_ratio;
			}
			*mult_h = imult_h;
		}
		break;
	default: /* VIDEOMODE_STRETCH_FULL */
		{
			double imult_w = (double) res->width / out_w / asp_ratio;
			double imult_h = (double) res->height / out_h;
			if (imult_w < 1.0)
				imult_w = 1.0;
			if (imult_h < 1.0)
				imult_h = 1.0;
			if (VIDEOMODE_keep_aspect == VIDEOMODE_KEEP_ASPECT_NONE) {
				*mult_w = imult_w * asp_ratio;
			} else {
				switch (VIDEOMODE_fit) {
				case VIDEOMODE_FIT_WIDTH:
					imult_h = imult_w;
					break;
				case VIDEOMODE_FIT_HEIGHT:
					break;
				default: /* VIDEOMODE_FIT_BOTH */
					if (imult_w < imult_h)
						imult_h = imult_w;
				}
				*mult_w = imult_h * asp_ratio;
			}
			*mult_h = imult_h;

		}
	}
	VIDEOMODE_dest_width = *mult_w * out_w * scale;
	VIDEOMODE_dest_height = *mult_h * out_h * scale;
}

static void CropVideoArea(VIDEOMODE_resolution_t const *screen_res, unsigned int *out_w, unsigned int *out_h, double mult_w, double mult_h)
{
	if (VIDEOMODE_dest_width > screen_res->width) {
		VIDEOMODE_dest_width = screen_res->width;
		*out_w = (double)VIDEOMODE_dest_width / mult_w;
	}
	if (VIDEOMODE_dest_height > screen_res->height) {
		VIDEOMODE_dest_height = screen_res->height;
		*out_h = (double)VIDEOMODE_dest_height / mult_h;
	}
}

/* Computes videomode parameters and calls the platform-specific videomode setup. */
static void SetVideoMode(VIDEOMODE_resolution_t *res, VIDEOMODE_MODE_t display_mode,
                         unsigned int out_w, unsigned int out_h,
                         int windowed, int rotate)
{
	VIDEOMODE_actual_width = out_w;
	VIDEOMODE_src_width = (*display_modes[display_mode].out2src_w_func)(out_w);
	VIDEOMODE_src_height = out_h;
	VIDEOMODE_src_offset_left = (display_modes[display_mode].src_width - VIDEOMODE_src_width) / 2;
	VIDEOMODE_src_offset_top = (display_modes[display_mode].src_height - VIDEOMODE_src_height) / 2;

	if (VIDEOMODE_horizontal_offset < -VIDEOMODE_MAX_HORIZONTAL_AREA)
		VIDEOMODE_horizontal_offset = -VIDEOMODE_MAX_HORIZONTAL_AREA;
	else if (VIDEOMODE_horizontal_offset > VIDEOMODE_MAX_HORIZONTAL_AREA)
		VIDEOMODE_horizontal_offset = VIDEOMODE_MAX_HORIZONTAL_AREA;
	if (VIDEOMODE_vertical_offset < -VIDEOMODE_MAX_VERTICAL_AREA)
		VIDEOMODE_vertical_offset = -VIDEOMODE_MAX_VERTICAL_AREA;
	else if (VIDEOMODE_vertical_offset > VIDEOMODE_MAX_VERTICAL_AREA)
		VIDEOMODE_vertical_offset = VIDEOMODE_MAX_VERTICAL_AREA;
	{
		int x = VIDEOMODE_horizontal_offset * display_modes[display_mode].param2src_w_mult;
		int y = VIDEOMODE_vertical_offset;
		if (x < -(int)VIDEOMODE_src_offset_left)
			x = -(int)VIDEOMODE_src_offset_left;
		else if (x > (int)(display_modes[display_mode].src_width - VIDEOMODE_src_offset_left - VIDEOMODE_src_width))
			x = (int)display_modes[display_mode].src_width - VIDEOMODE_src_offset_left - VIDEOMODE_src_width;
		if (y < -(int)VIDEOMODE_src_offset_top)
			y = -(int)VIDEOMODE_src_offset_top;
		else if (y > (int)(display_modes[display_mode].src_height - VIDEOMODE_src_offset_top - VIDEOMODE_src_height))
			y = (int)display_modes[display_mode].src_height - VIDEOMODE_src_offset_top - VIDEOMODE_src_height;
		VIDEOMODE_src_offset_left += x;
		VIDEOMODE_src_offset_top += y;
	}
	if (rotate) {
		unsigned int tmp = VIDEOMODE_dest_width;
		VIDEOMODE_dest_width = VIDEOMODE_dest_height;
		VIDEOMODE_dest_height = tmp;
		RotateResolution(res);
	}
	VIDEOMODE_dest_offset_left = (res->width - VIDEOMODE_dest_width) / 2;
	VIDEOMODE_dest_offset_top = (res->height - VIDEOMODE_dest_height) / 2;
	if (display_mode <= VIDEOMODE_MODE_LASTWITHHUD)
		SetScreenVisible();
	PLATFORM_SetVideoMode(res, windowed, display_mode, rotate);
}

/* Sets up the fullscreen video mode. */
static int UpdateVideoFullscreen(void)
{
	VIDEOMODE_MODE_t display_mode = CurrentDisplayMode();
#if SUPPORTS_ROTATE_VIDEOMODE
	int rotate = VIDEOMODE_rotate90 && (PLATFORM_SupportsVideomode(display_mode, TRUE, TRUE)
	                                    || PLATFORM_SupportsVideomode(display_mode, FALSE, TRUE));
#else
	int rotate = FALSE;
#endif
	unsigned int out_w, out_h;
	double mult_w, mult_h;
	VIDEOMODE_resolution_t *res_for_mode = ResolutionForMode(display_mode, rotate);
	VIDEOMODE_resolution_t res;
	if (res_for_mode == NULL)
		return FALSE;

	res = *res_for_mode;
	if (rotate)
		RotateResolution(&res);

	GetOutArea(&out_w, &out_h, display_mode);
	UpdateCustomStretch();
	ComputeVideoArea(&res, res_for_mode, display_mode, out_w, out_h, &mult_w, &mult_h, rotate, 1);
	CropVideoArea(&res, &out_w, &out_h, mult_w, mult_h);
#if SDL2
	SetVideoMode(&res, display_mode, out_w, out_h, FALSE, rotate);
	ComputeVideoArea(&res, res_for_mode, display_mode, out_w, out_h, &mult_w, &mult_h, rotate, VIDEOMODE_dest_scale_factor);
#else
	SetVideoMode(&res, display_mode, out_w, out_h, FALSE, rotate);
#endif
	return TRUE;
}

/* Sets up the windowed video mode. */
static int UpdateVideoWindowed(int window_resized)
{
	VIDEOMODE_MODE_t display_mode = CurrentDisplayMode();
#if SUPPORTS_ROTATE_VIDEOMODE
	int rotate = VIDEOMODE_rotate90 && (PLATFORM_SupportsVideomode(display_mode, TRUE, TRUE)
	                                    || PLATFORM_SupportsVideomode(display_mode, FALSE, TRUE));
#else
	int rotate = FALSE;
#endif
	unsigned int out_w, out_h;
	double mult_w, mult_h;
	VIDEOMODE_resolution_t desk_res = *PLATFORM_DesktopResolution();
	VIDEOMODE_resolution_t res = window_resolution;
	VIDEOMODE_resolution_t *max_res;
	int maximised = PLATFORM_WindowMaximised();

	if (rotate)
		RotateResolution(&res);

	GetOutArea(&out_w, &out_h, display_mode);
	UpdateCustomStretch();
	ComputeVideoArea(&res, &desk_res, display_mode, out_w, out_h, &mult_w, &mult_h, rotate, 1);
	if (window_resized || maximised)
		/* If the window was user-resized we don't allow it to grow, only shrink. */
		max_res = &res;
	else
		/* Don't allow the window to be larger than the desktop. */
		max_res = &desk_res;
	CropVideoArea(max_res, &out_w, &out_h, mult_w, mult_h);
	if (!window_resized && !maximised) {
		res.width = VIDEOMODE_dest_width;
		res.height = VIDEOMODE_dest_height;
	}
	SetVideoMode(&res, display_mode, out_w, out_h, TRUE, rotate);

	ComputeVideoArea(&res, &desk_res, display_mode, out_w, out_h, &mult_w, &mult_h, rotate, VIDEOMODE_dest_scale_factor);
	/*TODO:
	  CropVideoArea(max_res, &out_w, &out_h, mult_w, mult_h);*/
	return TRUE;
}

int VIDEOMODE_SetFullscreenResolution(unsigned int res_id)
{
	current_resolution = res_id;
	if (!VIDEOMODE_windowed && !force_windowed)
		return UpdateVideoFullscreen();
	return TRUE;
}

int VIDEOMODE_SetWindowSize(unsigned int width, unsigned int height)
{
	window_resolution.width = width;
	window_resolution.height = height;
	if (VIDEOMODE_windowed || force_windowed)
		return UpdateVideoWindowed(TRUE);
	return TRUE;
}

int VIDEOMODE_Update(void)
{
	if (VIDEOMODE_windowed || force_windowed)
		return UpdateVideoWindowed(FALSE);
	else
		return UpdateVideoFullscreen();
}

/* Sets an integer parameter and updates the video mode if needed. */
static int SetIntAndUpdateVideo(int *ptr, int value)
{
	int old_value = *ptr;
	if (old_value != value) {
		*ptr = value;
		if (!VIDEOMODE_Update()) {
			*ptr = old_value;
			return FALSE;
		}
	}
	return TRUE;
}

int VIDEOMODE_SetWindowed(int value)
{
	return SetIntAndUpdateVideo(&VIDEOMODE_windowed, value);
}

int VIDEOMODE_ToggleWindowed(void)
{
	return VIDEOMODE_SetWindowed(!VIDEOMODE_windowed);
}

void VIDEOMODE_ForceWindowed(int value)
{
	int prev_windowed = VIDEOMODE_windowed || force_windowed;
	force_windowed = value;
	if (prev_windowed != VIDEOMODE_windowed || force_windowed)
		VIDEOMODE_Update();
}

int VIDEOMODE_SetHorizontalArea(int value)
{
	return SetIntAndUpdateVideo(&VIDEOMODE_horizontal_area, value);
}

int VIDEOMODE_ToggleHorizontalArea(void)
{
	return VIDEOMODE_SetHorizontalArea((VIDEOMODE_horizontal_area + 1) % VIDEOMODE_HORIZONTAL_SIZE);
}

int VIDEOMODE_SetCustomHorizontalArea(unsigned int value)
{
	unsigned int old_value = VIDEOMODE_custom_horizontal_area;
	unsigned int old_area = VIDEOMODE_horizontal_area;
	if (value < VIDEOMODE_MIN_HORIZONTAL_AREA)
		return FALSE;
	if (value > VIDEOMODE_MAX_HORIZONTAL_AREA)
		value = VIDEOMODE_MAX_HORIZONTAL_AREA;
	if (value != VIDEOMODE_custom_horizontal_area || VIDEOMODE_horizontal_area != VIDEOMODE_HORIZONTAL_CUSTOM) {
		VIDEOMODE_custom_horizontal_area = value;
		VIDEOMODE_horizontal_area = VIDEOMODE_HORIZONTAL_CUSTOM;
		if (!VIDEOMODE_Update()) {
			VIDEOMODE_custom_horizontal_area = old_value;
			VIDEOMODE_horizontal_area = old_area;
			return FALSE;
		}
	}
	return TRUE;
}

int VIDEOMODE_SetVerticalArea(int value)
{
	return SetIntAndUpdateVideo(&VIDEOMODE_vertical_area, value);
}

int VIDEOMODE_ToggleVerticalArea(void)
{
	return VIDEOMODE_SetVerticalArea((VIDEOMODE_vertical_area + 1) % VIDEOMODE_VERTICAL_SIZE);
}

int VIDEOMODE_SetCustomVerticalArea(unsigned int value)
{
	unsigned int old_value = VIDEOMODE_custom_vertical_area;
	unsigned int old_area = VIDEOMODE_vertical_area;
	if (value < VIDEOMODE_MIN_VERTICAL_AREA)
		return FALSE;
	if (value > VIDEOMODE_MAX_VERTICAL_AREA)
		value = VIDEOMODE_MAX_VERTICAL_AREA;
	if (value != VIDEOMODE_custom_vertical_area || VIDEOMODE_vertical_area != VIDEOMODE_VERTICAL_CUSTOM) {
		VIDEOMODE_custom_vertical_area = value;
		VIDEOMODE_vertical_area = VIDEOMODE_VERTICAL_CUSTOM;
		if (!VIDEOMODE_Update()) {
			VIDEOMODE_custom_vertical_area = old_value;
			VIDEOMODE_vertical_area = old_area;
			return FALSE;
		}
	}
	return TRUE;
}

int VIDEOMODE_SetHorizontalOffset(int value)
{
	return SetIntAndUpdateVideo(&VIDEOMODE_horizontal_offset, value);
}

int VIDEOMODE_SetVerticalOffset(int value)
{
	return SetIntAndUpdateVideo(&VIDEOMODE_vertical_offset, value);
}

int VIDEOMODE_SetStretch(int value)
{
	return SetIntAndUpdateVideo(&VIDEOMODE_stretch, value);
}

int VIDEOMODE_ToggleStretch(void)
{
	return VIDEOMODE_SetStretch((VIDEOMODE_stretch + 1) % VIDEOMODE_STRETCH_SIZE);
}

int VIDEOMODE_SetCustomStretch(double value)
{
	double old_value = VIDEOMODE_custom_stretch;
	unsigned int old_stretch = VIDEOMODE_stretch;
	if (value < 1.0)
		return FALSE;
	if (value != VIDEOMODE_custom_stretch || VIDEOMODE_stretch != VIDEOMODE_STRETCH_CUSTOM) {
		VIDEOMODE_custom_stretch = value;
		VIDEOMODE_stretch = VIDEOMODE_STRETCH_CUSTOM;
		if (!VIDEOMODE_Update()) {
			VIDEOMODE_custom_stretch = old_value;
			VIDEOMODE_stretch = old_stretch;
			return FALSE;
		}
	}
	return TRUE;
}

int VIDEOMODE_SetFit(int value)
{
	return SetIntAndUpdateVideo(&VIDEOMODE_fit, value);
}

int VIDEOMODE_ToggleFit(void)
{
	return VIDEOMODE_SetFit((VIDEOMODE_fit + 1) % VIDEOMODE_FIT_SIZE);
}

int VIDEOMODE_SetKeepAspect(int value)
{
	return SetIntAndUpdateVideo(&VIDEOMODE_keep_aspect, value);
}

int VIDEOMODE_ToggleKeepAspect(void)
{
	return VIDEOMODE_SetKeepAspect((VIDEOMODE_keep_aspect + 1) % VIDEOMODE_KEEP_ASPECT_SIZE);
}

int VIDEOMODE_GetKeepAspect(void) {
	return VIDEOMODE_keep_aspect;
}

double VIDEOMODE_GetPixelAspectRatio(VIDEOMODE_MODE_t display_mode) {
	return display_modes[display_mode].asp_ratio;
}

#if SUPPORTS_ROTATE_VIDEOMODE
int VIDEOMODE_SetRotate90(int value)
{
	return SetIntAndUpdateVideo(&VIDEOMODE_rotate90, value);
}

int VIDEOMODE_ToggleRotate90(void)
{
	return VIDEOMODE_SetRotate90(!VIDEOMODE_rotate90);
}
#endif /* SUPPORTS_ROTATE_VIDEOMODE */

#if COLUMN_80
int VIDEOMODE_Set80Column(int value)
{
	return SetIntAndUpdateVideo(&VIDEOMODE_80_column, value);
}

int VIDEOMODE_Toggle80Column(void)
{
	return VIDEOMODE_Set80Column(!VIDEOMODE_80_column);
}
#endif /* COLUMN_80 */

void VIDEOMODE_ForceStandardScreen(int value)
{
	VIDEOMODE_MODE_t prev_mode = CurrentDisplayMode();
	force_standard_screen = value;
	if (prev_mode != CurrentDisplayMode())
		VIDEOMODE_Update();
}

/* Parses string S to get aspect ratio W:H. */
static int ParseAspectRatio(char const *s, double *w, double *h)
{
	char *s2;
	char *s3;
	*w = strtod(s, &s2);
	if (s2 == s || *w < 0.0)
		return FALSE;
	if (*s2 != ':')
		return FALSE;
	*h = strtod(++s2, &s3);
	if (s3 == s2 || *h < 0.0)
		return FALSE;
	return TRUE;
}

/* Find greatest common divisor of M and N. */
static unsigned int gcd(unsigned int m, unsigned int n) {
	unsigned int t;
	if (m < n) {
		t = m;
		m = n;
		n = t;
	}

	for (;;) {
		if (n == 0)
			return m;
		t = n;
		n = m % n;
		m = t;
	}
}

/* Autodetect host aspect ratio and write it into W and H. */
static void AutodetectHostAspect(double *w, double *h)
{
	VIDEOMODE_resolution_t *res = PLATFORM_DesktopResolution();
	unsigned int d = gcd(res->width, res->height);
	*w = (double)res->width / d;
	*h = (double)res->height / d;
}

int VIDEOMODE_SetHostAspect(double w, double h)
{
	double old_w = VIDEOMODE_host_aspect_ratio_w;
	double old_h = VIDEOMODE_host_aspect_ratio_h;
	if (w < 0.0 || h < 0.0)
		return FALSE;
	if (w == 0.0 || h == 0.0)
		AutodetectHostAspect(&w, &h);
	VIDEOMODE_host_aspect_ratio_w = w;
	VIDEOMODE_host_aspect_ratio_h = h;
	if (!VIDEOMODE_Update()) {
		VIDEOMODE_host_aspect_ratio_w = old_w;
		VIDEOMODE_host_aspect_ratio_h = old_h;
		return FALSE;
	}
	return TRUE;
}

int VIDEOMODE_SetHostAspectString(char const *s)
{
	double w, h;
	if (!ParseAspectRatio(s, &w, &h))
		return FALSE;
	return VIDEOMODE_SetHostAspect(w, h);
}

static void UpdateTvSystemSettings(void)
{
	display_modes[VIDEOMODE_MODE_NORMAL].asp_ratio = Atari800_tv_mode == Atari800_TV_PAL ? pixel_aspect_ratio_pal : pixel_aspect_ratio_ntsc;
}

void VIDEOMODE_SetVideoSystem(int mode)
{
	UpdateTvSystemSettings();
	VIDEOMODE_Update();
}

#ifdef XEP80_EMULATION
void VIDEOMODE_UpdateXEP80(void)
{
	display_modes[VIDEOMODE_MODE_XEP80].src_height = XEP80_scrn_height;
	if (XEP80_char_height == 12) /* PAL */
		display_modes[VIDEOMODE_MODE_XEP80].asp_ratio = xep80_aspect_ratio_pal;
	else
		display_modes[VIDEOMODE_MODE_XEP80].asp_ratio = xep80_aspect_ratio_ntsc;
	if (resolutions != NULL /* Display already initialised */
	    && CurrentDisplayMode() == VIDEOMODE_MODE_XEP80)
		VIDEOMODE_Update();
}
#endif /* XEP80_EMULATION */


void VIDEOMODE_CopyHostAspect(char *target, unsigned int size)
{
	snprintf(target, size, "%g:%g", VIDEOMODE_host_aspect_ratio_w, VIDEOMODE_host_aspect_ratio_h);
}

int VIDEOMODE_AutodetectHostAspect(void)
{
	double w, h;
	AutodetectHostAspect(&w, &h);
	return VIDEOMODE_SetHostAspect(w, h);
}

int VIDEOMODE_ReadConfig(char *option, char *ptr)
{
	if (strcmp(option, "VIDEOMODE_WINDOW_WIDTH") == 0)
		return (window_resolution.width = Util_sscandec(ptr)) != -1;
	else if (strcmp(option, "VIDEOMODE_WINDOW_HEIGHT") == 0)
		return (window_resolution.height = Util_sscandec(ptr)) != -1;
	else if (strcmp(option, "VIDEOMODE_FULLSCREEN_WIDTH") == 0)
		return (init_fs_resolution.width = Util_sscandec(ptr)) != -1;
	else if (strcmp(option, "VIDEOMODE_FULLSCREEN_HEIGHT") == 0)
		return (init_fs_resolution.height = Util_sscandec(ptr)) != -1;
	else if (strcmp(option, "VIDEOMODE_WINDOWED") == 0)
		return (VIDEOMODE_windowed = Util_sscanbool(ptr)) != -1;
	else if (strcmp(option, "VIDEOMODE_HORIZONTAL_AREA") == 0) {

		int i = CFG_MatchTextParameter(ptr, horizontal_area_cfg_strings, VIDEOMODE_HORIZONTAL_SIZE);
		if (i < 0) {
			VIDEOMODE_horizontal_area = VIDEOMODE_HORIZONTAL_CUSTOM;
			return (VIDEOMODE_custom_horizontal_area = Util_sscandec(ptr)) != -1
			       && VIDEOMODE_custom_horizontal_area >= VIDEOMODE_MIN_HORIZONTAL_AREA
			       && VIDEOMODE_custom_horizontal_area <= VIDEOMODE_MAX_HORIZONTAL_AREA;
		}
		VIDEOMODE_horizontal_area = i;
	}
	else if (strcmp(option, "VIDEOMODE_VERTICAL_AREA") == 0) {
		int i = CFG_MatchTextParameter(ptr, vertical_area_cfg_strings, VIDEOMODE_VERTICAL_SIZE);
		if (i < 0) {
			VIDEOMODE_vertical_area = VIDEOMODE_VERTICAL_CUSTOM;
			return (VIDEOMODE_custom_vertical_area = Util_sscandec(ptr)) != -1
			       && VIDEOMODE_custom_vertical_area >= VIDEOMODE_MIN_VERTICAL_AREA
			       && VIDEOMODE_custom_vertical_area <= VIDEOMODE_MAX_VERTICAL_AREA;
		}
		VIDEOMODE_vertical_area = i;
	}
	else if (strcmp(option, "VIDEOMODE_HORIZONTAL_SHIFT") == 0)
		return Util_sscansdec(ptr, &VIDEOMODE_horizontal_offset);
	else if (strcmp(option, "VIDEOMODE_VERTICAL_SHIFT") == 0)
		return Util_sscansdec(ptr, &VIDEOMODE_vertical_offset);
	else if (strcmp(option, "VIDEOMODE_STRETCH") == 0) {
		int i = CFG_MatchTextParameter(ptr, stretch_cfg_strings, VIDEOMODE_STRETCH_SIZE);
		if (i < 0) {
			VIDEOMODE_stretch = VIDEOMODE_STRETCH_CUSTOM;
			return Util_sscandouble(ptr, &VIDEOMODE_custom_stretch)
			        && VIDEOMODE_custom_stretch >= 1.0;
		}
		VIDEOMODE_stretch = i;
	}
	else if (strcmp(option, "VIDEOMODE_FIT") == 0) {
		int i = CFG_MatchTextParameter(ptr, fit_cfg_strings, VIDEOMODE_FIT_SIZE);
		if (i < 0)
			return FALSE;
		VIDEOMODE_fit = i;
	}
	else if (strcmp(option, "VIDEOMODE_IMAGE_ASPECT") == 0) {
		int i = CFG_MatchTextParameter(ptr, keep_aspect_cfg_strings, VIDEOMODE_KEEP_ASPECT_SIZE);
		if (i < 0)
			return FALSE;
		VIDEOMODE_keep_aspect = i;
	}
#if SUPPORTS_ROTATE_VIDEOMODE
	else if (strcmp(option, "VIDEOMODE_ROTATE90") == 0)
		return (VIDEOMODE_rotate90 = Util_sscanbool(ptr)) != -1;
#endif
	else if (strcmp(option, "VIDEOMODE_HOST_ASPECT_RATIO") == 0) {
		if (strcmp(ptr, "AUTO") == 0)
			VIDEOMODE_host_aspect_ratio_w = VIDEOMODE_host_aspect_ratio_h = 0.0;
		else
			return ParseAspectRatio(ptr, &VIDEOMODE_host_aspect_ratio_w, &VIDEOMODE_host_aspect_ratio_h);
	}
#if COLUMN_80
	else if (strcmp(option, "VIDEOMODE_80_COLUMN") == 0)
		return (VIDEOMODE_80_column = Util_sscanbool(ptr)) != -1;
#endif
	else
		return FALSE;
	return TRUE;
}

void VIDEOMODE_WriteConfig(FILE *fp) {
	fprintf(fp, "VIDEOMODE_WINDOW_WIDTH=%u\n", window_resolution.width);
	fprintf(fp, "VIDEOMODE_WINDOW_HEIGHT=%u\n", window_resolution.height);
	fprintf(fp, "VIDEOMODE_FULLSCREEN_WIDTH=%u\n", (resolutions == NULL ? init_fs_resolution.width : resolutions[current_resolution].width));
	fprintf(fp, "VIDEOMODE_FULLSCREEN_HEIGHT=%u\n", (resolutions == NULL ? init_fs_resolution.height : resolutions[current_resolution].height));
	fprintf(fp, "VIDEOMODE_WINDOWED=%d\n", VIDEOMODE_windowed);
	if (VIDEOMODE_horizontal_area == VIDEOMODE_HORIZONTAL_CUSTOM)
		fprintf(fp, "VIDEOMODE_HORIZONTAL_AREA=%d\n", VIDEOMODE_custom_horizontal_area);
	else
		fprintf(fp, "VIDEOMODE_HORIZONTAL_AREA=%s\n", horizontal_area_cfg_strings[VIDEOMODE_horizontal_area]);
	if (VIDEOMODE_vertical_area == VIDEOMODE_VERTICAL_CUSTOM)
		fprintf(fp, "VIDEOMODE_VERTICAL_AREA=%d\n", VIDEOMODE_custom_vertical_area);
	else
		fprintf(fp, "VIDEOMODE_VERTICAL_AREA=%s\n", vertical_area_cfg_strings[VIDEOMODE_vertical_area]);
	fprintf(fp, "VIDEOMODE_HORIZONTAL_SHIFT=%d\n", VIDEOMODE_horizontal_offset);
	fprintf(fp, "VIDEOMODE_VERTICAL_SHIFT=%d\n", VIDEOMODE_vertical_offset);
	if (VIDEOMODE_stretch == VIDEOMODE_STRETCH_CUSTOM)
		fprintf(fp, "VIDEOMODE_STRETCH=%g\n", VIDEOMODE_custom_stretch);
	else
		fprintf(fp, "VIDEOMODE_STRETCH=%s\n", stretch_cfg_strings[VIDEOMODE_stretch]);
	fprintf(fp, "VIDEOMODE_FIT=%s\n", fit_cfg_strings[VIDEOMODE_fit]);
	fprintf(fp, "VIDEOMODE_IMAGE_ASPECT=%s\n", keep_aspect_cfg_strings[VIDEOMODE_keep_aspect]);
#if SUPPORTS_ROTATE_VIDEOMODE
	fprintf(fp, "VIDEOMODE_ROTATE90=%d\n", VIDEOMODE_rotate90);
#endif
	fprintf(fp, "VIDEOMODE_HOST_ASPECT_RATIO=%g:%g\n", VIDEOMODE_host_aspect_ratio_w, VIDEOMODE_host_aspect_ratio_h);
#if COLUMN_80
	fprintf(fp, "VIDEOMODE_80_COLUMN=%d\n", VIDEOMODE_80_column);
#endif
}

int VIDEOMODE_Initialise(int *argc, char *argv[])
{
	int i, j;
	for (i = j = 1; i < *argc; i++) {
		int i_a = (i + 1 < *argc); /* is argument available? */
		int a_m = FALSE; /* error, argument missing! */
		int a_i = FALSE; /* error, argument invalid! */

		if (strcmp(argv[i], "-win-width") == 0) {
			if (i_a)
				a_i = (window_resolution.width = Util_sscandec(argv[++i])) == -1;
			else a_m = TRUE;
		}
		else if (strcmp(argv[i], "-win-height") == 0) {
			if (i_a)
				a_i = (window_resolution.height = Util_sscandec(argv[++i])) == -1;
			else a_m = TRUE;
		}
		else if (strcmp(argv[i], "-fs-width") == 0) {
			if (i_a)
				a_i = (init_fs_resolution.width = Util_sscandec(argv[++i])) == -1;
			else a_m = TRUE;
		}
		else if (strcmp(argv[i], "-fs-height") == 0) {
			if (i_a)
				a_i = (init_fs_resolution.height = Util_sscandec(argv[++i])) == -1;
			else a_m = TRUE;
		}
		else if (strcmp(argv[i], "-fullscreen") == 0)
			VIDEOMODE_windowed = FALSE;
		else if (strcmp(argv[i], "-windowed") == 0)
			VIDEOMODE_windowed = TRUE;
		else if (strcmp(argv[i], "-horiz-area") == 0) {
			if (i_a) {
				int idx = CFG_MatchTextParameter(argv[++i], horizontal_area_cfg_strings, VIDEOMODE_HORIZONTAL_SIZE);
				if (idx < 0) {
					VIDEOMODE_horizontal_area = VIDEOMODE_HORIZONTAL_CUSTOM;
					a_i = (VIDEOMODE_custom_horizontal_area = Util_sscandec(argv[i])) == -1
					      || VIDEOMODE_custom_horizontal_area < VIDEOMODE_MIN_HORIZONTAL_AREA
					      || VIDEOMODE_custom_horizontal_area > VIDEOMODE_MAX_HORIZONTAL_AREA;
				} else
					VIDEOMODE_horizontal_area = idx;
			}
			else a_m = TRUE;
		}
		else if (strcmp(argv[i], "-vert-area") == 0) {
			if (i_a) {
				int idx = CFG_MatchTextParameter(argv[++i], vertical_area_cfg_strings, VIDEOMODE_VERTICAL_SIZE);
				if (idx < 0) {
					VIDEOMODE_vertical_area = VIDEOMODE_VERTICAL_CUSTOM;
					a_i = (VIDEOMODE_custom_vertical_area = Util_sscandec(argv[i])) == -1
					      || VIDEOMODE_custom_vertical_area < VIDEOMODE_MIN_VERTICAL_AREA
					      || VIDEOMODE_custom_vertical_area > VIDEOMODE_MAX_VERTICAL_AREA;
				} else
					VIDEOMODE_vertical_area = idx;
			}
			else a_m = TRUE;
		}
		else if (strcmp(argv[i], "-horiz-shift") == 0) {
			if (i_a)
				a_i = !Util_sscansdec(argv[++i], &VIDEOMODE_horizontal_offset);
			else a_m = TRUE;
		}
		else if (strcmp(argv[i], "-vert-shift") == 0) {
			if (i_a)
				a_i = !Util_sscansdec(argv[++i], &VIDEOMODE_vertical_offset);
			else a_m = TRUE;
		}
		else if (strcmp(argv[i], "-stretch") == 0) {
			if (i_a) {
				int idx = CFG_MatchTextParameter(argv[++i], stretch_cfg_strings, VIDEOMODE_STRETCH_SIZE);
				if (idx < 0) {
					VIDEOMODE_stretch = VIDEOMODE_STRETCH_CUSTOM;
					a_i = !Util_sscandouble(argv[i], &VIDEOMODE_custom_stretch)
					      || VIDEOMODE_custom_stretch < 1.0;
				} else
					VIDEOMODE_stretch = idx;
			}
			else a_m = TRUE;
		}
		else if (strcmp(argv[i], "-fit-screen") == 0) {
			if (i_a) {
				if ((VIDEOMODE_fit = CFG_MatchTextParameter(argv[++i], fit_cfg_strings, VIDEOMODE_FIT_SIZE)) < 0)
					a_i = TRUE;
			}
			else a_m = TRUE;
		}
		else if (strcmp(argv[i], "-image-aspect") == 0) {
			if (i_a) {
				if ((VIDEOMODE_keep_aspect = CFG_MatchTextParameter(argv[++i], keep_aspect_cfg_strings, VIDEOMODE_KEEP_ASPECT_SIZE)) < 0)
					a_i = TRUE;
			}
			else a_m = TRUE;
		}
#if SUPPORTS_ROTATE_VIDEOMODE
		else if (strcmp(argv[i], "-rotate90") == 0)
			VIDEOMODE_rotate90 = TRUE;
		else if (strcmp(argv[i], "-no-rotate90") == 0)
			VIDEOMODE_rotate90 = FALSE;
#endif /* SUPPORTS_ROTATE_VIDEOMODE */
		else if (strcmp(argv[i], "-host-aspect-ratio") == 0) {
			if (i_a) {
				if (strcmp(argv[++i], "auto") == 0)
					VIDEOMODE_host_aspect_ratio_w = VIDEOMODE_host_aspect_ratio_h = 0.0;
				else
					a_i = !ParseAspectRatio(argv[i], &VIDEOMODE_host_aspect_ratio_w, &VIDEOMODE_host_aspect_ratio_h);
			}
			else a_m = TRUE;
		}
#if COLUMN_80
		else if (strcmp(argv[i], "-80column") == 0)
			VIDEOMODE_80_column = TRUE;
		else if (strcmp(argv[i], "-no-80column") == 0)
			VIDEOMODE_80_column = FALSE;
#endif /* COLUMN_80 */
		else {
			if (strcmp(argv[i], "-help") == 0) {
				Log_print("\t-win-width <num>            Host window width");
				Log_print("\t-win-height <num>           Host window height");
				Log_print("\t-fs-width <num>             Host fullscreen width");
				Log_print("\t-fs-height <num>            Host fullscreen height");
				Log_print("\t-fullscreen                 Run fullscreen");
				Log_print("\t-windowed                   Run in window");
				Log_print("\t-horiz-area narrow|tv|full|<number>");
				Log_print("\t                            Set horizontal view area");
				Log_print("\t-vert-area short|tv|full|<number>");
				Log_print("\t                            Set vertical view area");
				Log_print("\t-horiz-shift <num>          Set horizontal shift of the visible area (-%i..%i)", VIDEOMODE_MAX_HORIZONTAL_AREA, VIDEOMODE_MAX_HORIZONTAL_AREA);
				Log_print("\t-vert-shift <num>           Set vertical shift of the visible area (-%i..%i)", VIDEOMODE_MAX_VERTICAL_AREA, VIDEOMODE_MAX_VERTICAL_AREA);
				Log_print("\t-stretch none|integral|full|<number>");
				Log_print("\t                            Set method of image stretching");
				Log_print("\t-fit-screen width|height|both");
				Log_print("\t                            Set method of image fitting the screen");
				Log_print("\t-image-aspect none|square-pixels|real");
				Log_print("\t                            Set image aspect ratio");
#if SUPPORTS_ROTATE_VIDEOMODE
				Log_print("\t-rotate90                   Rotate the screen sideways");
				Log_print("\t-no-rotate90                Don't rotate the screen");
#endif /* SUPPORTS_ROTATE_VIDEOMODE */
				Log_print("\t-host-aspect-ratio auto|<w>:<h>");
				Log_print("\t                            Set host display aspect ratio");
#if COLUMN_80
				Log_print("\t-80column                   Show output of an 80 column card, if present");
				Log_print("\t-no-80column                Show standard screen output");
#endif
			}
			argv[j++] = argv[i];
		}

		if (a_m) {
			Log_print("Missing argument for '%s'", argv[i]);
			return FALSE;
		} else if (a_i) {
			Log_print("Invalid argument for '%s'", argv[--i]);
			return FALSE;
		}
	}
	*argc = j;

#ifdef XEP80_EMULATION
	display_modes[VIDEOMODE_MODE_XEP80].asp_ratio = xep80_aspect_ratio_ntsc;
#endif
	return TRUE;
}

static int CompareResolutions(const void *res1, const void *res2)
{
	unsigned int w = ((const VIDEOMODE_resolution_t *)res1)->width - ((const VIDEOMODE_resolution_t *)res2)->width;
	if (w != 0)
		return w;
	return ((const VIDEOMODE_resolution_t *)res1)->height - ((const VIDEOMODE_resolution_t *)res2)->height;
}

/* Removes duplicate resolutions from the list. */
static void RemoveDuplicateResolutions(void)
{
	unsigned int i, j;

	for (i = 0, j = 1; j < resolutions_size; ++j) {
		if (CompareResolutions(&resolutions[i], &resolutions[j]) != 0) {
			++i;
			if (i != j)
				resolutions[i] = resolutions[j];
		}
	}

	resolutions_size -= j - i - 1;
	/* Beware: unused end of RESOLUTIONS is still malloced! */
}

int VIDEOMODE_InitialiseDisplay(void)
{
	/* PLATFORM_Initialise must be called earlier! */
	resolutions = PLATFORM_AvailableResolutions(&resolutions_size);
	if (resolutions == NULL) {
		Log_print("Fatal error: System reports no display resolutions available");
		return FALSE;
	}

	qsort(resolutions, resolutions_size, sizeof(VIDEOMODE_resolution_t), &CompareResolutions);
	RemoveDuplicateResolutions();
	if (resolutions_size == 0) {
		Log_print("Fatal error: System reports no resolution higher than minimal %ux%u available",
		          display_modes[0].min_w, display_modes[0].min_h);
		return FALSE;
	}

	/* Find the resolution from config file/command line in RESOLUTIONS. */
	for (current_resolution = 0; current_resolution < resolutions_size; current_resolution ++) {
		if (resolutions[current_resolution].width >= init_fs_resolution.width &&
		    resolutions[current_resolution].height >= init_fs_resolution.height)
			break;
	}
	if (current_resolution >= resolutions_size) {
		/* No resolution found, using the biggest one. */
		current_resolution = resolutions_size - 1;
		Log_print("Requested resolution %ux%u is too big, using %ux%u instead.",
		          init_fs_resolution.width, init_fs_resolution.height,
		          resolutions[current_resolution].width, resolutions[current_resolution].height);
	} else if (resolutions[current_resolution].width != init_fs_resolution.width ||
	           resolutions[current_resolution].height != init_fs_resolution.height)
		Log_print("Requested resolution %ux%u is not available, using %ux%u instead.",
		          init_fs_resolution.width, init_fs_resolution.height,
		          resolutions[current_resolution].width, resolutions[current_resolution].height);
	/* Autodetect host display aspect ratio if requested. */
	if (VIDEOMODE_host_aspect_ratio_w == 0.0 || VIDEOMODE_host_aspect_ratio_h == 0.0)
		AutodetectHostAspect(&VIDEOMODE_host_aspect_ratio_w, &VIDEOMODE_host_aspect_ratio_h);

	UpdateTvSystemSettings();
	if (!VIDEOMODE_Update()) {
		Log_print("Fatal error: Cannot initialise video");
		return FALSE;
	}
#if SUPPORTS_PLATFORM_PALETTEUPDATE
	PLATFORM_PaletteUpdate();
#endif
	return TRUE;
}

void VIDEOMODE_Exit(void)
{
	if (resolutions != NULL)
		free(resolutions);
}
