/**************************************************************

   modeline.cpp - Modeline generation and scoring routines

   ---------------------------------------------------------

   Switchres   Modeline generation engine for emulation

   License     GPL-2.0+
   Copyright   2010-2021 Chris Kennedy, Antonio Giner,
                         Alexandre Wodarczyk, Gil Delescluse

 **************************************************************/

#include <stdio.h>
#include <string.h>
#include <cstddef>
#include "modeline.h"
#include "log.h"

#define max(a,b)({ __typeof__ (a) _a = (a);__typeof__ (b) _b = (b);_a > _b ? _a : _b; })
#define min(a,b)({ __typeof__ (a) _a = (a);__typeof__ (b) _b = (b);_a < _b ? _a : _b; })


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

int get_line_params(modeline *mode, monitor_range *range, int char_size);
int scale_into_range (int value, int lower_limit, int higher_limit);
int scale_into_range (double value, double lower_limit, double higher_limit);
int scale_into_aspect (int source_res, int tot_res, double original_monitor_aspect, double users_monitor_aspect, double *best_diff);
int stretch_into_range(double vfreq, monitor_range *range, double borders, bool interlace_allowed, double *interlace);
int total_lines_for_yres(int yres, double vfreq, monitor_range *range, double borders, double interlace);
double max_vfreq_for_yres (int yres, monitor_range *range, double borders, double interlace);

//============================================================
//  modeline_create
//============================================================

int modeline_create(modeline *s_mode, modeline *t_mode, monitor_range *range, generator_settings *cs)
{
	double vfreq_real = 0;
	double interlace = 1;
	double doublescan = 1;
	double scan_factor = 1;
	int x_scale = 0;
	int y_scale = 0;
	int v_scale = 0;
	double x_fscale = 0;
	double y_fscale = 0;
	double v_fscale = 0;
	double x_diff = 0;
	double y_diff = 0;
	double v_diff = 0;
	double y_ratio = 0;
	double borders = 0;
	int rotation = s_mode->type & MODE_ROTATED;
	double source_aspect = rotation? 1.0 / (STANDARD_CRT_ASPECT) : (STANDARD_CRT_ASPECT);
	t_mode->result.weight = 0;

	// ≈≈≈ Vertical refresh ≈≈≈
	// try to fit vertical frequency into current range
	v_scale = scale_into_range(t_mode->vfreq, range->vfreq_min, range->vfreq_max);

	if (!v_scale && (t_mode->type & V_FREQ_EDITABLE))
	{
		t_mode->vfreq = t_mode->vfreq < range->vfreq_min? range->vfreq_min : range->vfreq_max;
		v_scale = 1;
	}
	else if (v_scale != 1 && !(t_mode->type & V_FREQ_EDITABLE))
	{
		t_mode->result.weight |= R_OUT_OF_RANGE;
		return -1;
	}

	// ≈≈≈ Vertical resolution ≈≈≈
	// try to fit active lines in the progressive range first
	if (range->progressive_lines_min && (!t_mode->interlace || (t_mode->type & SCAN_EDITABLE)))
		y_scale = scale_into_range(t_mode->vactive, range->progressive_lines_min, range->progressive_lines_max);

	// if not possible, try to fit in the interlaced range, if any
	if (!y_scale && range->interlaced_lines_min && cs->interlace && (t_mode->interlace || (t_mode->type & SCAN_EDITABLE)))
	{
		y_scale = scale_into_range(t_mode->vactive, range->interlaced_lines_min, range->interlaced_lines_max);
		interlace = 2;
	}

	// if we succeeded, let's see if we can apply integer scaling
	if (y_scale == 1 || (y_scale > 1 && (t_mode->type & Y_RES_EDITABLE)))
	{
		// check if we should apply doublescan
		if (cs->doublescan && y_scale % 2 == 0)
		{
			y_scale /= 2;
			doublescan = 0.5;
		}
		scan_factor = interlace * doublescan;

		// Calculate top border in case of multi-standard consumer TVs
		if (cs->v_shift_correct)
			borders = (range->progressive_lines_max - t_mode->vactive * y_scale / interlace) * (1.0 / range->hfreq_min) / 2;

		// calculate expected achievable refresh for this height
		vfreq_real = min(t_mode->vfreq * v_scale, max_vfreq_for_yres(t_mode->vactive * y_scale, range, borders, scan_factor));
		if (vfreq_real != t_mode->vfreq * v_scale && !(t_mode->type & V_FREQ_EDITABLE))
		{
			t_mode->result.weight |= R_OUT_OF_RANGE;
			return -1;
		}

		// calculate the ratio that our scaled yres represents with respect to the original height
		y_ratio = double(t_mode->vactive) * y_scale / s_mode->vactive;
		int y_source_scaled = s_mode->vactive * floor(y_ratio);

		// if our original height doesn't fit the target height, we're forced to stretch
		if (!y_source_scaled)
			t_mode->result.weight |= R_RES_STRETCH;

		// otherwise we try to perform integer scaling
		else
		{
			// exclude lcd ranges from raw border computation
			if (t_mode->type & V_FREQ_EDITABLE && range->progressive_lines_max - range->progressive_lines_min > 0)
			{
				// calculate y borders considering physical lines (instead of logical resolution)
				int tot_yres = total_lines_for_yres(t_mode->vactive * y_scale, vfreq_real, range, borders, scan_factor);
				int tot_source = total_lines_for_yres(y_source_scaled, t_mode->vfreq * v_scale, range, borders, scan_factor);
				y_diff = tot_yres > tot_source?double(tot_yres % tot_source) / tot_yres * 100:0;

				// we penalize for the logical lines we need to add in order to meet the user's lower active lines limit
				int y_min = interlace == 2?range->interlaced_lines_min:range->progressive_lines_min;
				int tot_rest = (y_min >= y_source_scaled / doublescan)? y_min % int(y_source_scaled / doublescan):0;
				y_diff += double(tot_rest) / tot_yres * 100;
			}
			else
				y_diff = double((t_mode->vactive * y_scale) % y_source_scaled) / (t_mode->vactive * y_scale) * 100;

			// we save the integer ratio between source and target resolutions, this will be used for prescaling
			y_scale = floor(y_ratio);

			// now if the borders obtained are low enough (< 10%) we'll finally apply integer scaling
			// otherwise we'll stretch the original resolution over the target one
			if (!(y_ratio >= 1.0 && y_ratio < 16.0 && y_diff < 10.0))
				t_mode->result.weight |= R_RES_STRETCH;
		}
	}

	// otherwise, check if we're allowed to apply fractional scaling
	else if (t_mode->type & Y_RES_EDITABLE)
		t_mode->result.weight |= R_RES_STRETCH;

	// if there's nothing we can do, we're out of range
	else
	{
		t_mode->result.weight |= R_OUT_OF_RANGE;
		return -1;
	}

	// ≈≈≈ Horizontal resolution ≈≈≈
	// make the best possible adjustment of xres depending on what happened in the previous steps
	// let's start with the SCALED case
	if (!(t_mode->result.weight & R_RES_STRETCH))
	{
		// apply integer scaling to yres
		if (t_mode->type & Y_RES_EDITABLE) t_mode->vactive *= y_scale;

		// if we can, let's apply the same scaling to both directions
		if (t_mode->type & X_RES_EDITABLE)
		{
			x_scale = cs->scale_proportional? y_scale : 1;
			double aspect_corrector = max(1.0f, cs->monitor_aspect / source_aspect);
			t_mode->hactive = normalize(double(t_mode->hactive) * double(x_scale) * aspect_corrector, cs->pixel_precision? 1 : 8);
		}

		// otherwise, try to get the best out of our current xres
		else
		{
			x_scale = t_mode->hactive / s_mode->hactive;
			// if the source width fits our xres, try applying integer scaling
			if (x_scale)
			{
				x_scale = scale_into_aspect(s_mode->hactive, t_mode->hactive, source_aspect, cs->monitor_aspect, &x_diff);
				if (x_diff > 15.0 && t_mode->hactive < cs->super_width)
						t_mode->result.weight |= R_RES_STRETCH;
			}
			// otherwise apply fractional scaling
			else
				t_mode->result.weight |= R_RES_STRETCH;
		}
	}

	// if the result was fractional scaling in any of the previous steps, deal with it
	if (t_mode->result.weight & R_RES_STRETCH)
	{
		if (t_mode->type & Y_RES_EDITABLE)
		{
			// always try to use the interlaced range first if it exists, for better resolution
			t_mode->vactive = stretch_into_range(t_mode->vfreq * v_scale, range, borders, cs->interlace, &interlace);

			// check in case we couldn't achieve the desired refresh
			vfreq_real = min(t_mode->vfreq * v_scale, max_vfreq_for_yres(t_mode->vactive, range, borders, interlace));
		}

		// check if we can create a normal aspect resolution
		if (t_mode->type & X_RES_EDITABLE)
			t_mode->hactive = max(t_mode->hactive, normalize(STANDARD_CRT_ASPECT * t_mode->vactive, cs->pixel_precision? 1 : 8));

		// calculate integer scale for prescaling
		x_scale = max(1, scale_into_aspect(s_mode->hactive, t_mode->hactive, source_aspect, cs->monitor_aspect, &x_diff));
		y_scale = max(1, floor(double(t_mode->vactive) / s_mode->vactive));

		scan_factor = interlace;
		doublescan = 1;
	}

	x_fscale = double(t_mode->hactive) / s_mode->hactive * source_aspect / cs->monitor_aspect;
	y_fscale = double(t_mode->vactive) / s_mode->vactive;
	v_fscale = vfreq_real / s_mode->vfreq;
	v_diff = (vfreq_real / v_scale) -  s_mode->vfreq;
	if (fabs(v_diff) > cs->refresh_tolerance)
		t_mode->result.weight |= R_V_FREQ_OFF;

	// ≈≈≈ Modeline generation ≈≈≈
	// compute new modeline if we are allowed to
	if (t_mode->type & V_FREQ_EDITABLE)
	{
		double margin = 0;
		double vblank_lines = 0;
		double vvt_ini = 0;
		double interlace_incr = !cs->interlace_force_even && interlace == 2? 0.5 : 0;

		// Get resulting refresh
		t_mode->vfreq = vfreq_real;

		// Get total vertical lines
		vvt_ini = total_lines_for_yres(t_mode->vactive, t_mode->vfreq, range, borders, scan_factor) + interlace_incr;

		// Calculate horizontal frequency
		t_mode->hfreq = t_mode->vfreq * vvt_ini;

		horizontal_values:

		// Fill horizontal part of modeline
		get_line_params(t_mode, range, cs->pixel_precision? 1 : 8);

		// Calculate pixel clock
		t_mode->pclock = t_mode->htotal * t_mode->hfreq;
		if (t_mode->pclock <= cs->pclock_min)
		{
			if (t_mode->type & X_RES_EDITABLE)
			{
				x_scale *= 2;
				x_fscale *= 2;
				t_mode->hactive *= 2;
				goto horizontal_values;
			}
			else
			{
				t_mode->result.weight |= R_OUT_OF_RANGE;
				return -1;
			}
		}

		// Vertical blanking
		t_mode->vtotal = vvt_ini * scan_factor;
		vblank_lines = round_near(t_mode->hfreq * (range->vertical_blank + borders)) + interlace_incr;
		margin = (t_mode->vtotal - t_mode->vactive - vblank_lines * scan_factor) / (cs->v_shift_correct? 1 : 2);

		double v_front_porch = margin + t_mode->hfreq * range->vfront_porch * scan_factor + interlace_incr;
		int (*pf_round)(double) = interlace == 2? (cs->interlace_force_even? round_near_even : round_near_odd) : round_near;

		t_mode->vbegin = t_mode->vactive + max(pf_round(v_front_porch), 1);
		t_mode->vend = t_mode->vbegin + max(round_near(t_mode->hfreq * range->vsync_pulse * scan_factor), 1);

		// Recalculate final vfreq
		t_mode->vfreq = (t_mode->hfreq / t_mode->vtotal) * scan_factor;

		t_mode->hsync = range->hsync_polarity;
		t_mode->vsync = range->vsync_polarity;
		t_mode->interlace = interlace == 2? 1 : 0;
		t_mode->doublescan = doublescan == 1? 0 : 1;
	}

	// finally, store result
	t_mode->result.scan_penalty = (s_mode->interlace != t_mode->interlace? 1 : 0) + (s_mode->doublescan != t_mode->doublescan? 1 : 0);
	t_mode->result.x_scale = t_mode->result.weight & R_RES_STRETCH || t_mode->hactive >= cs->super_width ? x_fscale : (double)x_scale;
	t_mode->result.y_scale = t_mode->result.weight & R_RES_STRETCH? y_fscale : (double)y_scale;
	t_mode->result.v_scale = v_fscale;
	t_mode->result.x_diff = x_diff;
	t_mode->result.y_diff = y_diff;
	t_mode->result.v_diff = v_diff;

	return 0;
}

//============================================================
//  get_line_params
//============================================================

int get_line_params(modeline *mode, monitor_range *range, int char_size)
{
	int hhi, hhf, hht;
	int hh, hs, he, ht;
	double line_time, char_time, new_char_time;
	double hfront_porch_min, hsync_pulse_min, hback_porch_min;

	hfront_porch_min = range->hfront_porch * .90;
	hsync_pulse_min  = range->hsync_pulse  * .90;
	hback_porch_min  = range->hback_porch  * .90;

	line_time = 1 / mode->hfreq * 1000000;

	hh = round(mode->hactive / char_size);
	hs = he = ht = 1;

	do {
		char_time = line_time / (hh + hs + he + ht);
		if (hs * char_time < hfront_porch_min ||
			fabs((hs + 1) * char_time - range->hfront_porch) < fabs(hs * char_time - range->hfront_porch))
			hs++;

		if (he * char_time < hsync_pulse_min ||
			fabs((he + 1) * char_time - range->hsync_pulse) < fabs(he * char_time - range->hsync_pulse))
			he++;

		if (ht * char_time < hback_porch_min ||
			fabs((ht + 1) * char_time - range->hback_porch) < fabs(ht * char_time - range->hback_porch))
			ht++;

		new_char_time = line_time / (hh + hs + he + ht);
	} while (new_char_time != char_time);

	hhi = (hh + hs) * char_size;
	hhf = (hh + hs + he) * char_size;
	hht = (hh + hs + he + ht) * char_size;

	mode->hbegin  = hhi;
	mode->hend    = hhf;
	mode->htotal  = hht;

	return 0;
}

//============================================================
//  scale_into_range
//============================================================

int scale_into_range (int value, int lower_limit, int higher_limit)
{
	int scale = 1;
	while (value * scale < lower_limit) scale ++;
	if (value * scale <= higher_limit)
		return scale;
	else
		return 0;
}

//============================================================
//  scale_into_range
//============================================================

int scale_into_range (double value, double lower_limit, double higher_limit)
{
	int scale = 1;
	while (value * scale < lower_limit) scale ++;
	if (value * scale <= higher_limit)
		return scale;
	else
		return 0;
}

//============================================================
//  scale_into_aspect
//============================================================

int scale_into_aspect (int source_res, int tot_res, double original_monitor_aspect, double users_monitor_aspect, double *best_diff)
{
	int scale = 1, best_scale = 1;
	double diff = 0;
	*best_diff = 0;

	while (source_res * scale <= tot_res)
	{
		diff = fabs(1.0 - (users_monitor_aspect / (double(tot_res) / double(source_res * scale) * original_monitor_aspect))) * 100.0;
		if (diff < *best_diff || *best_diff == 0)
		{
			*best_diff = diff;
			best_scale = scale;
		}
		scale ++;
	}
	return best_scale;
}

//============================================================
//  stretch_into_range
//============================================================

int stretch_into_range(double vfreq, monitor_range *range, double borders, bool interlace_allowed, double *interlace)
{
	int yres, lower_limit;

	if (range->interlaced_lines_min && interlace_allowed)
	{
		yres = range->interlaced_lines_max;
		lower_limit = range->interlaced_lines_min;
		*interlace = 2;
	}
	else
	{
		yres = range->progressive_lines_max;
		lower_limit = range->progressive_lines_min;
	}

	while (yres > lower_limit && max_vfreq_for_yres(yres, range, borders, *interlace) < vfreq)
		yres -= 8;

	return yres;
}


//============================================================
//  total_lines_for_yres
//============================================================

int total_lines_for_yres(int yres, double vfreq, monitor_range *range, double borders, double interlace)
{
	int vvt = max(yres / interlace + round_near(vfreq * yres / (interlace * (1.0 - vfreq * (range->vertical_blank + borders))) * (range->vertical_blank + borders)), 1);
	while ((vfreq * vvt < range->hfreq_min) && (vfreq * (vvt + 1) < range->hfreq_max)) vvt++;
	return vvt;
}

//============================================================
//  max_vfreq_for_yres
//============================================================

double max_vfreq_for_yres (int yres, monitor_range *range, double borders, double interlace)
{
	return range->hfreq_max / (yres / interlace + round_near(range->hfreq_max * (range->vertical_blank + borders)));
}

//============================================================
//  modeline_print
//============================================================

char * modeline_print(modeline *mode, char *modeline, int flags)
{
	char label[48]={'\x00'};
	char params[192]={'\x00'};

	if (flags & MS_LABEL)
		sprintf(label, "\"%dx%d_%d%s %.6fKHz %.6fHz\"", mode->hactive, mode->vactive, mode->refresh, mode->interlace?"i":"", mode->hfreq/1000, mode->vfreq);

	if (flags & MS_LABEL_SDL)
		sprintf(label, "\"%dx%d_%.6f\"", mode->hactive, mode->vactive, mode->vfreq);

	if (flags & MS_PARAMS)
		sprintf(params, " %.6f %d %d %d %d %d %d %d %d %s %s %s %s", double(mode->pclock)/1000000.0, mode->hactive, mode->hbegin, mode->hend, mode->htotal, mode->vactive, mode->vbegin, mode->vend, mode->vtotal,
			mode->interlace?"interlace":"", mode->doublescan?"doublescan":"", mode->hsync?"+hsync":"-hsync", mode->vsync?"+vsync":"-vsync");

	sprintf(modeline, "%s%s", label, params);

	return modeline;
}

//============================================================
//  modeline_result
//============================================================

char * modeline_result(modeline *mode, char *result)
{
	log_verbose("   rng(%d): ", mode->range);

	if (mode->result.weight & R_OUT_OF_RANGE)
		sprintf(result, " out of range");

	else
		sprintf(result, "%4d x%4d_%3.6f%s%s %3.6f [%s] scale(%.3f, %.3f, %.3f) diff(%.3f, %.3f, %.3f)",
			mode->hactive, mode->vactive, mode->vfreq, mode->interlace?"i":"p", mode->doublescan?"d":"", mode->hfreq/1000, mode->result.weight & R_RES_STRETCH?"fract":"integ",
			mode->result.x_scale, mode->result.y_scale, mode->result.v_scale, mode->result.x_diff, mode->result.y_diff, mode->result.v_diff);
	return result;
}

//============================================================
//  modeline_compare
//============================================================

int modeline_compare(modeline *t, modeline *best)
{
	bool vector = (t->hactive == (int)t->result.x_scale);

	if (t->result.weight < best->result.weight)
		return 1;

	else if (t->result.weight <= best->result.weight)
	{
		double t_v_diff = fabs(t->result.v_diff);
		double b_v_diff = fabs(best->result.v_diff);

		if (t->result.weight & R_RES_STRETCH || vector)
		{
			double t_y_score = t->result.y_scale * (t->interlace?(2.0/3.0):1.0);
			double b_y_score = best->result.y_scale * (best->interlace?(2.0/3.0):1.0);

			if  ((t_v_diff <  b_v_diff) ||
				((t_v_diff == b_v_diff) && (t_y_score > b_y_score)) ||
				((t_v_diff == b_v_diff) && (t_y_score == b_y_score) && (t->result.x_scale > best->result.x_scale)))
					return 1;
		}
		else
		{
			int t_y_score = t->result.y_scale + t->result.scan_penalty;
			int b_y_score = best->result.y_scale + best->result.scan_penalty;
			double xy_diff = roundf((t->result.x_diff + t->result.y_diff) * 100) / 100;
			double best_xy_diff = roundf((best->result.x_diff + best->result.y_diff) * 100) / 100;

			if  ((t_y_score < b_y_score) ||
				((t_y_score == b_y_score) && (xy_diff < best_xy_diff)) ||
				((t_y_score == b_y_score) && (xy_diff == best_xy_diff) && (t->result.x_scale < best->result.x_scale)) ||
				((t_y_score == b_y_score) && (xy_diff == best_xy_diff) && (t->result.x_scale == best->result.x_scale) && (t_v_diff <  b_v_diff)))
					return 1;
		}
	}
	return 0;
}

//============================================================
//  modeline_vesa_gtf
//  Based on the VESA GTF spreadsheet by Andy Morrish 1/5/97
//============================================================

int modeline_vesa_gtf(modeline *m)
{
	int C, M;
	int v_sync_lines, v_porch_lines_min, v_front_porch_lines, v_back_porch_lines, v_sync_v_back_porch_lines, v_total_lines;
	int h_sync_width_percent, h_sync_width_pixels, h_blanking_pixels, h_front_porch_pixels, h_total_pixels;
	double v_freq, v_freq_est, v_freq_real, v_sync_v_back_porch;
	double h_freq, h_period, h_period_real, h_ideal_blanking;
	double pixel_freq, interlace;

	// Check if there's a value defined for vfreq. We're assuming input vfreq is the total field vfreq regardless interlace
	v_freq = m->vfreq? m->vfreq:double(m->refresh);

	// These values are GTF defined defaults
	v_sync_lines = 3;
	v_porch_lines_min = 1;
	v_front_porch_lines = v_porch_lines_min;
	v_sync_v_back_porch = 550;
	h_sync_width_percent = 8;
	M = 128.0 / 256 * 600;
	C = ((40 - 20) * 128.0 / 256) + 20;

	// GTF calculation
	interlace = m->interlace?0.5:0;
	h_period = ((1.0 / v_freq) - (v_sync_v_back_porch / 1000000)) / ((double)m->height + v_front_porch_lines + interlace) * 1000000;
	v_sync_v_back_porch_lines = round_near(v_sync_v_back_porch / h_period);
	v_back_porch_lines = v_sync_v_back_porch_lines - v_sync_lines;
	v_total_lines = m->height + v_front_porch_lines + v_sync_lines + v_back_porch_lines;
	v_freq_est = (1.0 / h_period) / v_total_lines * 1000000;
	h_period_real = h_period / (v_freq / v_freq_est);
	v_freq_real = (1.0 / h_period_real) / v_total_lines * 1000000;
	h_ideal_blanking = double(C - (M * h_period_real / 1000));
	h_blanking_pixels = round_near(m->width * h_ideal_blanking /(100 - h_ideal_blanking) / (2 * 8)) * (2 * 8);
	h_total_pixels = m->width + h_blanking_pixels;
	pixel_freq = h_total_pixels / h_period_real * 1000000;
	h_freq = 1000000 / h_period_real;
	h_sync_width_pixels = round_near(h_sync_width_percent * h_total_pixels / 100 / 8) * 8;
	h_front_porch_pixels = (h_blanking_pixels / 2) - h_sync_width_pixels;

	// Results
	m->hactive = m->width;
	m->hbegin = m->hactive + h_front_porch_pixels;
	m->hend = m->hbegin + h_sync_width_pixels;
	m->htotal = h_total_pixels;
	m->vactive = m->height;
	m->vbegin = m->vactive + v_front_porch_lines;
	m->vend = m->vbegin + v_sync_lines;
	m->vtotal = v_total_lines;
	m->hfreq = h_freq;
	m->vfreq = v_freq_real;
	m->pclock = pixel_freq;
	m->hsync = 0;
	m->vsync = 1;

	return true;
}

//============================================================
//  modeline_parse
//============================================================

int modeline_parse(const char *user_modeline, modeline *mode)
{
	char modeline_txt[256]={'\x00'};

	if (!strcmp(user_modeline, "auto"))
		return false;

	// Remove quotes
	char *quote_start, *quote_end;
	quote_start = strstr((char*)user_modeline, "\"");
	if (quote_start)
	{
		quote_start++;
		quote_end = strstr(quote_start, "\"");
		if (!quote_end || *quote_end++ == 0)
			return false;
		user_modeline = quote_end;
	}

	// Get timing flags
	mode->interlace = strstr(user_modeline, "interlace")?1:0;
	mode->doublescan = strstr(user_modeline, "doublescan")?1:0;
	mode->hsync = strstr(user_modeline, "+hsync")?1:0;
	mode->vsync = strstr(user_modeline, "+vsync")?1:0;

	// Get timing values
	double pclock;
	int e = sscanf(user_modeline, " %lf %d %d %d %d %d %d %d %d",
		&pclock,
		&mode->hactive, &mode->hbegin, &mode->hend, &mode->htotal,
		&mode->vactive, &mode->vbegin, &mode->vend, &mode->vtotal);

	if (e != 9)
	{
		log_error("Switchres: missing parameter in user modeline\n  %s\n", user_modeline);
		memset(mode, 0, sizeof(struct modeline));
		return false;
	}

	// Calculate timings
	mode->pclock = pclock * 1000000.0;
	mode->hfreq = mode->pclock / mode->htotal;
	mode->vfreq = mode->hfreq / mode->vtotal * (mode->interlace?2:1);
	mode->refresh = mode->vfreq;
	mode->width = mode->hactive;
	mode->height = mode->vactive;
	log_verbose("Switchres: user modeline %s\n", modeline_print(mode, modeline_txt, MS_FULL));

	return true;
}

//============================================================
//  modeline_to_monitor_range
//============================================================

int modeline_to_monitor_range(monitor_range *range, modeline *mode)
{
	// If Vfreq range is empty, create it around the provided vfreq
	if (range->vfreq_min == 0.0f) range->vfreq_min = mode->vfreq - 0.2;
	if (range->vfreq_max == 0.0f) range->vfreq_max = mode->vfreq + 0.2;

	// Make sure the range includes the target vfreq
	if (mode->vfreq < range->vfreq_min || mode->vfreq > range->vfreq_max)
		return 0;

	double line_time = 1 / mode->hfreq;
	double pixel_time = line_time / mode->htotal * 1000000;
	double interlace_factor = mode->interlace? 0.5 : 1.0;

	range->hfront_porch = pixel_time * (mode->hbegin - mode->hactive);
	range->hsync_pulse = pixel_time * (mode->hend - mode->hbegin);
	range->hback_porch = pixel_time * (mode->htotal - mode->hend);

	// We floor the vertical fields to remove the half line from interlaced modes, because
	// the modeline generator will add it automatically. Otherwise it would be added twice.
	range->vfront_porch = line_time * floor((mode->vbegin - mode->vactive) * interlace_factor);
	range->vsync_pulse = line_time * floor((mode->vend - mode->vbegin) * interlace_factor);
	range->vback_porch = line_time * floor((mode->vtotal - mode->vend) * interlace_factor);
	range->vertical_blank = range->vfront_porch + range->vsync_pulse + range->vback_porch;

	range->hsync_polarity = mode->hsync;
	range->vsync_polarity = mode->vsync;

	range->progressive_lines_min = mode->interlace? 0 : mode->vactive;
	range->progressive_lines_max = mode->interlace? 0 : mode->vactive;
	range->interlaced_lines_min = mode->interlace? mode->vactive : 0;
	range->interlaced_lines_max= mode->interlace? mode->vactive : 0;

	range->hfreq_min = range->vfreq_min * mode->vtotal * interlace_factor;
	range->hfreq_max = range->vfreq_max * mode->vtotal * interlace_factor;

	return 1;
}

//============================================================
//  modeline_adjust
//============================================================

int modeline_adjust(modeline *mode, double hfreq_max, generator_settings *cs)
{
	// If input values are out of range, they are fixed within range and returned in the cs struct.

	// H size ajdustment, valid values 0.5-2.0
	if (cs->h_size != 1.0f)
	{
		if (cs->h_size > 2.0f)
			cs->h_size = 2.0f;
		else if (cs->h_size < 0.5f)
			cs->h_size = 0.5f;

		monitor_range range;
		memset(&range, 0, sizeof(monitor_range));

		modeline_to_monitor_range(&range, mode);

		range.hfront_porch /= cs->h_size;
		range.hback_porch /= cs->h_size;

		modeline_create(mode, mode, &range, cs);
	}

	// H shift adjustment, positive or negative value
	if (cs->h_shift != 0)
	{
		if (cs->h_shift >= mode->hbegin - mode->hactive)
			cs->h_shift = mode->hbegin - mode->hactive - 1;

		else if (cs->h_shift <= mode->hend - mode->htotal)
			cs->h_shift = mode->hend - mode->htotal + 1;

		mode->hbegin -= cs->h_shift;
		mode->hend -= cs->h_shift;
	}

	// V shift adjustment, positive or negative value
	if (cs->v_shift != 0)
	{
		int vactive = mode->vactive;
		int vbegin = mode->vbegin;
		int vend = mode->vend;
		int vtotal = mode->vtotal;

		if (mode->interlace)
		{
			vactive >>= 1;
			vbegin  >>= 1;
			vend    >>= 1;
			vtotal  >>= 1;
		}

		int v_front_porch = vbegin - vactive;
		int v_back_porch =  vend - vtotal;
		int max_vtotal = hfreq_max / mode->vfreq;
		int border = max_vtotal - vtotal;
		int padding = 0;

		// v_shift positive
		if (cs->v_shift >= v_front_porch)
		{
			int v_front_porch_ex = v_front_porch + border;
			if (cs->v_shift >= v_front_porch_ex)
				cs->v_shift = v_front_porch_ex - 1;

			padding = cs->v_shift - v_front_porch + 1;
			vbegin += padding;
			vend += padding;
			vtotal += padding;
		}

		// v_shift negative
		else if (cs->v_shift <= v_back_porch + 1)
		{
			int v_back_porch_ex = v_back_porch - border;
			if (cs->v_shift <= v_back_porch_ex + 1)
				cs->v_shift = v_back_porch_ex + 2;

			padding = -(cs->v_shift - v_back_porch - 2);
			vtotal += padding;
		}

		vbegin -= cs->v_shift;
		vend -= cs->v_shift;

		if (mode->interlace)
		{
			vbegin =  (vbegin << 1)  | (mode->vbegin & 1);
			vend =    (vend << 1)    | (mode->vend & 1);
			vtotal =  (vtotal << 1)  | (mode->vtotal & 1);
		}

		mode->vbegin = vbegin;
		mode->vend = vend;
		mode->vtotal = vtotal;

		if (padding != 0)
		{
			mode->hfreq = mode->vfreq * mode->vtotal / (mode->interlace? 2.0 : 1.0);

			monitor_range range;
			memset(&range, 0, sizeof(monitor_range));
			modeline_to_monitor_range(&range, mode);
			monitor_show_range(&range);
			modeline_create(mode, mode, &range, cs);
		}
	}

	return 0;
}

//============================================================
//  modeline_is_different
//============================================================

int modeline_is_different(modeline *n, modeline *p)
{
	// Remove on last fields in modeline comparison
	return memcmp(n, p, offsetof(struct modeline, vfreq));
}

//============================================================
//  modeline_copy_timings
//============================================================

void modeline_copy_timings(modeline *n, modeline *p)
{
	// Only copy relevant timing fields
	memcpy(n, p, offsetof(struct modeline, width));
}

//============================================================
//  monitor_fill_vesa_gtf
//============================================================

int monitor_fill_vesa_gtf(monitor_range *range, const char *max_lines)
{
	int lines = 0;
	sscanf(max_lines, "vesa_%d", &lines);

	if (!lines)
		return 0;

	int i = 0;
	if (lines >= 480)
		i += monitor_fill_vesa_range(&range[i], 384, 480);
	if (lines >= 600)
		i += monitor_fill_vesa_range(&range[i], 480, 600);
	if (lines >= 768)
		i += monitor_fill_vesa_range(&range[i], 600, 768);
	if (lines >= 1024)
		i += monitor_fill_vesa_range(&range[i], 768, 1024);

	return i;
}

//============================================================
//  monitor_fill_vesa_range
//============================================================

int monitor_fill_vesa_range(monitor_range *range, int lines_min, int lines_max)
{
	modeline mode;
	memset(&mode, 0, sizeof(modeline));

	mode.width = real_res(STANDARD_CRT_ASPECT * lines_max);
	mode.height = lines_max;
	mode.refresh = 60;
	range->vfreq_min = 50;
	range->vfreq_max = 65;

	modeline_vesa_gtf(&mode);
	modeline_to_monitor_range(range, &mode);

	range->progressive_lines_min = lines_min;
	range->hfreq_min = mode.hfreq - 500;
	range->hfreq_max = mode.hfreq + 500;
	monitor_show_range(range);

	return 1;
}

//============================================================
//  round_near
//============================================================

int round_near(double number)
{
	return number < 0.0 ? ceil(number - 0.5) : floor(number + 0.5);
}

//============================================================
//  round_near_odd
//============================================================

int round_near_odd(double number)
{
	return int(ceil(number)) % 2 == 0? floor(number) : ceil(number);
}

//============================================================
//  round_near_even
//============================================================

int round_near_even(double number)
{
	return int(ceil(number)) % 2 == 1? floor(number) : ceil(number);
}

//============================================================
//  normalize
//============================================================

int normalize(int a, int b)
{
	int c, d;
	c = a % b;
	d = a / b;
	if (c) d++;
	return d * b;
}

//============================================================
//  real_res
//============================================================

int real_res(int x) {return (int) (x / 8) * 8;}
