#!/usr/local/bin/perl

use Getopt::Long;

#----------------------------------------------------------------------------

$h_minfreq = 30000;			## Mininum scan frequency
$h_maxfreq = 35500;			## Maximum scan frequency
$maxdot    = 70000000;		## Maximum dot clock frequency (Hz)

$h_freq_error  = 0.991;		## Margin for hsync adjustments 
$h_extra_space = 1.27;		## Extra Horizontal space factor
$v_extra_space = 0.93;		## Extra Vertical space factor
$h_sync1_guess = 80;		## Horizontal sync1 guess
$h_sync2_guess = 80;		## Horizontal sync2 guess

#----------------------------------------------------------------------------

### Check command line ###

$Cmd_font = "8x16";			## Default Font specification
$Cmd_vsf  = 60;				## Default Vertical Frequency.
$Cmd_help = 0;
$Cmd_dotclock = 0;
$Cmd_verbose = 0;
$Cmd_trick9  = 0;

$ret = GetOptions("help",      \$Cmd_help,
	              "font:s",    \$Cmd_font,
		          "vsf:i",     \$Cmd_vsf,
                  "dot:f",     \$Cmd_dotclock,
                  "verbose",   \$Cmd_verbose,
                  "trick9",    \$Cmd_trick9,
                  "hmargin:f", \$h_extra_space,
                  "vmargin:f", \$v_extra_space);

if ($ARGV[0] eq "" || $Cmd_help || $ret == 0 || $Cmd_vsf == 0)
{
	print STDERR "Usage: SVGATextCalc <mode> [--font=fontspec] [--trick9] [--verbose]\n";
	print STDERR "                      [--vsf=refresh(Hz)] [--dot=dotclock(MHz)]\n";
	print STDERR "                      [--hmargin=factor] [--vmargin=factor]\n";
	exit 1;
}

($font_width, $font_height)    = split('x', $Cmd_font);
($h_text_lines, $v_text_lines) = split(/x/,$ARGV[0]);
$v_freq                        = $Cmd_vsf;


### Grab allowed ranges from /etc/Textconfig ###
($h_minfreq, $h_maxfreq, $maxdot) = getlimits();

if ($Cmd_verbose) {
	print "Horizontal Frequency: $h_minfreq, $h_maxfreq, Maximum dot=$maxdot\n";
}

#-----------------------------------------------------------------------------

### Check horizontal frequency ###

$v_lines = $font_height * $v_text_lines / $v_extra_space;
$h_freq  = $v_lines * $v_freq;

### I control the vertical... ###

$v_res   = round($font_height, $v_lines * $v_extra_space);   ## Vertical resolution
$v_start = $v_res;											 ## Vertical start
$v_end   = $v_start + $font_height;							 ## Vertical end

### ...And the Horizontal ###

$h_pixels   = round(8, $h_text_lines * $font_width * $h_extra_space);
$h_res      = round(8, $h_text_lines * $font_width);
$pixelclock = $h_pixels * $h_freq;
$h_start    = round(8, $h_res + $h_sync1_guess);
$h_end      = round(8, $h_start + $h_sync2_guess);

##
##	We must make every effort so that the user must make as few 
##	adjustments as possible in the output. If the user didn't 
##	specify a fixed dotclock and our horizontal frequency is out
##	of scale, we then recalculate the dotclock so that the frequency
##	will be as close as possible to the offending bound (either
##	upper or lower).
##
##	The code below only recalculates the new clock and puts it
##	into the variable $Cmd_dotclock. 
##

if (! $Cmd_dotclock)
{
	##
	##	We adjust the frequency to the maximum or minimum
	##	allowed, and then generate the new dotclock to
	##	keep things "balanced".
	##

	if ($h_freq < $h_minfreq || $h_freq > $h_maxfreq)
	{
		if ($h_freq < $h_minfreq) { $newclock = $h_minfreq * (1+(1-$h_freq_error)) * $h_pixels; }
		else 					  { $newclock = $h_maxfreq * $h_freq_error * $h_pixels; }
	}

	$Cmd_dotclock = $newclock / 1000000;
}

##
##	If we have a fixed dot clock request (either by the means of the --dot
##	command line switch or set by the piece of code above), we must recalc
##	the parameters to keep the screen in sync. There's no guarantee that
##	the resolution will be the same (remember, the asked one may not be
##	possible at all), but the program will make its best to make it close.
##

if ($Cmd_dotclock)
{
	### Calculate multiplying factor to the new dotclock ###

	$h_divfactor = ($Cmd_dotclock * 1000000) / $pixelclock;

	## 
	##	First, we check if the new HSF (changed with the new dotclock) still
	##	fits our range. If so, then we have a deal, and keep the new dotclock without
	##  a change on the expected resolution. Otherwhise, we have to hack the
	##	horizontal parameters to make it fit. Of course, that will change the
	##	number of characters on the line, but that's the way it is...
	##
	
	$h_freq_new = $h_freq * $h_divfactor;

	if ($h_freq_new >= $h_minfreq && $h_freq_new <= $h_maxfreq)
	{	
		$h_freq = $h_freq_new;
	}
	else
	{
		$h_res    = round(8, $h_res    * $h_divfactor);
		$h_start  = round(8, $h_start  * $h_divfactor);
		$h_end    = round(8, $h_end    * $h_divfactor);
		$h_pixels = round(8, $h_pixels * $h_divfactor);
	}

	$pixelclock = $Cmd_dotclock * 1000000;
}

##
##	Check the 9 pixels spacing trick
##	This uses a feature on the VGA to add spacing between characters.
##	Everything must be calculated as if we were using 8 pixel fonts, though.
##
##	The reported dotclock may not be the same as set with the --dot command line.
##	This feature is best explained in SVGATextMode documentation.
##

if ($Cmd_trick9 && $font_width == 8)
{
	$pixelclock = $pixelclock * 9 / 8;
	$font_width = 9;
}

if ($Cmd_verbose)
{
	printf STDERR "Resolution: %dx%d chars, ", $h_text_lines, $v_text_lines;
	printf STDERR "font: %dx%d pixels, ", $font_width, $font_height;
	printf STDERR "VSF=%d Hz, HSF=%.2f KHz\n", $h_freq / $v_lines, $h_freq/1000;
}

### Sanity checks ###

if ($h_freq < $h_minfreq || $h_freq > $h_maxfreq)
{
	print STDERR "\n*** WARNING: Horizontal frequency is out of range ***\n\n";
}

if ($pixelclock > $maxdot)
{
	print STDERR "\n*** WARNING: Dotclock is out of range ***\n\n";
}

### Print the results ###

printf "# Generated by SVGATextCalc (paganini\@ism.com.br), %dHz, %.2fKHz\n" ,$h_freq/$v_lines, $h_freq/1000; 
printf "\"T%dx%dx%d\"	      %02.2f  %4.4s %4.4s %4.4s %4.4s  %4.4s %4.4s %4.4s %4.4s  font %dx%d  # %dHz/%.2fKHz\n" ,
	int($h_res / (($Cmd_trick9) ? 8 : $font_width)),
	int($v_res / $font_height),
	$font_width,
	$pixelclock/1000000,
	int($h_res), int($h_start), int($h_end), int($h_pixels),
	int($v_res), int($v_start), int($v_end), int($v_lines),
	$font_width, $font_height,
	$h_freq/$v_lines, $h_freq/1000;

exit(0);

#----------------------------------------------------------------------------------------

sub getlimits
{
	my($lowfreq, $hifreq, $dacspeed);

	if (! open (CFG, "/etc/TextConfig"))
	{
		print "Error opening /etc/TextConfig. Is SVGATextMode installed?\n";
		exit 1;
	}

	while (<CFG>)
	{
		if (m/^HorizSync[ \t]*(.*)/o)
		{
			@freqs       = split(/[,-]/,$1);
			$lowfreq     = $freqs[0] * 1000;
			$hifreq      = $freqs[$#freqs] * 1000;
		}
		elsif (m/^DacSpeed[ \t]*([0-9]+)/o)
		{
			$dacspeed = $1 * 1000000;
		}
	}

	if ($hifreq == 0 || $lowfreq == 0)
	{
		print STDERR "I need the HorizSync line in /etc/TextConfig.\n";
		exit 1;
	}

	if ($dacspeed == 0 || $lowfreq == 0)
	{
		print STDERR "I need the DacSpeed line in /etc/TextConfig.\n";
		exit 1;
	}

	return ($lowfreq, $hifreq, $dacspeed);
}

#----------------------------------------------------------------------------------------
sub round
{
	my($factor, $val) = @_;
	return int($val / $factor) * $factor;
}
