/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |     videogen                                          |
 * |                                                       |
 * |     a simple XFree86 Modeline calculator              |
 * |     (c) 1997-2002, Szabolcs Rumi                      |   
 * |                                                       |
 * |     http://www.rtfm.hu/videogen                       |
 * |                                                       |
 * |     the videogen package is distributed under the     |
 * |     GNU General Public License Version 2 (GPLv2)      |
 * |                                                       |
 * +-------------------------------------------------------+   
 */





#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <math.h>

#include "config.h"
#include "videogen.h"





extern FILE *yyin;
extern int yyparse (void);
extern int yydebug;





char *cfgfile = CFG_CFGFILE;
unsigned int verbose = CFG_VERBOSE;
unsigned int fbset = CFG_FBSET;
unsigned int nvidia = CFG_NVIDIA;
unsigned int num_modes = 0;
resolution_t modes[256];
double max_dotclk = 0;
double max_hfreq = 0;
double max_vfreq = 0;
double desired_vfreq = 0;
double hvisible = CFG_HORIZ_VISIBLE;
double vvisible = CFG_VERT_VISIBLE;
double hfporch = CFG_HORIZ_FRONT_PORCH;
double hbporch = CFG_HORIZ_BACK_PORCH;
double hsync = CFG_HORIZ_SYNC_PULSE;
double vfporch = CFG_VERT_FRONT_PORCH;
double vbporch = CFG_VERT_BACK_PORCH;
double vsync = CFG_VERT_SYNC_PULSE;





void
banner (void)
{
	pmsg (VL_VERBOSE, "\n       +--------------------------------------------------------------+\n");
	pmsg (VL_VERBOSE, "       |                                                              |\n");
	pmsg (VL_VERBOSE, "       |     videogen %s     simple XFree86 Modeline calculator     |\n", CFG_VIDEOGEN_VERSION);
	pmsg (VL_VERBOSE, "       |                                                              |\n");
	pmsg (VL_VERBOSE, "       |              by Szabolcs Rumi, (c) 1997 - 2002               |\n");
	pmsg (VL_VERBOSE, "       |        THIS PROGRAM COMES WITH ABSOLUTELY NO WARRANTY        |\n");
	pmsg (VL_VERBOSE, "       |    for details see the GNU General Public License (GPLv2)    |\n");
	pmsg (VL_VERBOSE, "       |                                                              |\n");
	pmsg (VL_VERBOSE, "       +--------------------------------------------------------------+\n\n");
}





void
usage (void)
{
	pmsg (VL_QUIET, "USAGE: videogen [-h] [-v|-q] [-fb|-nfb] [-nv|-nnv] [-f=<file|->] [-m=<mode>]\n");
	pmsg (VL_QUIET, "                [-mdc=<n>] [-mhf=<n>] [-mvf=<n>] [-dvf=<n>] [-hv=<n>] [-vv=<n>]\n");
	pmsg (VL_QUIET, "                [-hfp=<n>] [-hbp=<n>] [-hsp=<n>] [-vfp=<n>] [-vbp=<n>] [-vsp=<n>]\n\n");
}





int
main (int argc, char **argv)
{
	char pathname_buf[4096];
	arg_parse_t ap = { 0, 0, 0, NULL, &opts };
	int i;
	double calc_dc, calc_hf, calc_vf;
	unsigned int calc_hfl, calc_vfl, calc_hfp, calc_hsp, calc_hbp, calc_vfp, calc_vsp, calc_vbp;



	/*
	 * reading command line parameters
	 */

	ap.argc = argc;
	ap.argv = argv;

	if (arg_parse (&ap, &arg_action) < 0)
	{
		pmsg (VL_VERBOSE, "command line syntax error\n");
		exit (ERR_CMDLINE);
	}



	/*
	 * reading config file
	 */

	if (strcmp (cfgfile, "-"))
	{
		yyin = fopen (tpathexp (cfgfile, (char *)&pathname_buf), "r");
		if (yyin == NULL)
		{
			pmsg (VL_VERBOSE, "could not open configuration file \"%s\" (errno=%i)\n", cfgfile, errno);
		}
		else
		{
			pmsg (VL_VERBOSE, "reading configuration from file %s\n", cfgfile);
			if (yyparse () != 0)
				exit (ERR_CFGFILE);
		}
	}
	else
	{
		yyin = stdin;
		if (yyin == NULL)
		{
			pmsg (VL_DEBUG, "[main] stdin not open\n");
		}
		else
		{
			pmsg (VL_VERBOSE, "reading configuration from standard input\n");
			if (yyparse () != 0)
				exit (ERR_CFGFILE);
		}
	}



	/*
	 * print out some information about the program itself (in verbose mode only)
	 */

	banner ();



	/*
	 * check if the mandatory parameters are present
	 */

	if (num_modes == 0)
	{
		pmsg (VL_VERBOSE, "error: no modes (resolutions) have been specified\n");
		exit (ERR_RES);
	}

	if (max_dotclk == 0)
	{
		pmsg (VL_VERBOSE, "error: the maximum dot clock rate has not been specified\n");
		exit (ERR_MDC);
	}

	if (max_hfreq == 0)
	{
		pmsg (VL_VERBOSE, "error: the maximum horizontal refresh frequency has not been specified\n");
		exit (ERR_MHF);
	}

	if (max_vfreq == 0)
	{
		pmsg (VL_VERBOSE, "error: the maximum vertical refresh frequency has not been specified\n");
		exit (ERR_MVF);
	}

	if (desired_vfreq == 0)
		desired_vfreq = max_vfreq;



	/*
	 * do our main job
	 */

	for (i = 0; i < num_modes; i++)
	{
		calc_vf = desired_vfreq;
		calc_hfl = (unsigned int)floor (modes[i].hres * 100 / hvisible / 8) * 8;

		if ((nvidia > 0) && (calc_hfl - modes[i].hres > CFG_NV_MAX_HBLANK))
		{
			pmsg (VL_DEBUG, "[main] hblank: %u > %u\n", calc_hfl - modes[i].hres, CFG_NV_MAX_HBLANK);
			hvisible = 100 - ((100 - hvisible) * (CFG_NV_MAX_HBLANK / (calc_hfl - modes[i].hres)));
			calc_hfl = (unsigned int)floor (modes[i].hres * 100 / hvisible);
			calc_hfl = (unsigned int)floor (calc_hfl / 8) * 8;
		}

		calc_hfp = (unsigned int)floor ((calc_hfl - modes[i].hres) / 64) * 8;
		calc_hbp = (unsigned int)floor ((calc_hfl - modes[i].hres - calc_hfp) / 8) * 8; 

		calc_hsp = floor (hsync * max_dotclk / 8) * 8;

		if ((nvidia > 0) && (calc_hsp > CFG_NV_MAX_HSP))
		{
			/* calc_hsp is assumed to be a multiple of 8 because all values it is
			 * calculated from are assumed to be multiples of 8.
			 */
			pmsg (VL_DEBUG, "[main] hsp: %u > %u\n", calc_hsp, CFG_NV_MAX_HSP);
			calc_hsp = CFG_NV_MAX_HSP;
		}

		calc_hbp -= calc_hsp;

		calc_vfl = (unsigned int)floor (modes[i].vres * 100 / vvisible + 0.5);

		if ((nvidia > 0) && (calc_vfl - modes[i].vres > CFG_NV_MAX_VBLANK))
		{
			pmsg (VL_DEBUG, "[main] vblank: %u > %u\n", calc_vfl - modes[i].vres, CFG_NV_MAX_VBLANK);
			vvisible = 100 - ((100 - vvisible) * (CFG_NV_MAX_VBLANK / (calc_vfl - modes[i].vres)));
			calc_vfl = (unsigned int)floor (modes[i].vres * 100 / vvisible);
		}

		calc_dc = calc_vf * calc_hfl * calc_vfl / 1000000;
		calc_hf = (1000 * calc_dc) / calc_hfl;

		calc_vfp = (unsigned int)vfporch;
		calc_vbp = calc_vfl - modes[i].vres - calc_vfp;

		calc_vsp = floor (vsync * calc_hf / 1000);

		if ((nvidia > 0) && (calc_vsp > CFG_NV_MAX_VSP))
		{
			/* framelength must be the same after the correction
			 */
			pmsg (VL_DEBUG, "[main] vsp: %u > %u\n", calc_vsp, CFG_NV_MAX_VSP);
			calc_vsp = CFG_NV_MAX_VSP;
		}

		calc_vbp -= calc_vsp;

		pmsg (VL_DEBUG, "[main] dump 1: visible=%ux%u frame=%ux%u\n", modes[i].hres, modes[i].vres, calc_hfl, calc_vfl);
		pmsg (VL_DEBUG, "[main] dump 1: dc=%f hf=%f vf=%f\n", calc_dc, calc_hf, calc_vf);
		pmsg (VL_DEBUG, "[main] dump 1: hsp=%u vsp=%u\n\n", calc_hsp, calc_vsp);

		if (calc_dc > max_dotclk)
		{
			pmsg (VL_DEBUG, "[main] dc: %f > %f\n", calc_dc, max_dotclk);
			calc_dc = max_dotclk;
			calc_vf = (1000000 * calc_dc) / (calc_hfl * calc_vfl);
		}

		calc_hf = (1000 * calc_dc) / calc_hfl;

		pmsg (VL_DEBUG, "[main] dump 2: visible=%ux%u frame=%ux%u\n", modes[i].hres, modes[i].vres, calc_hfl, calc_vfl);
		pmsg (VL_DEBUG, "[main] dump 2: dc=%f hf=%f vf=%f\n", calc_dc, calc_hf, calc_vf);
		pmsg (VL_DEBUG, "[main] dump 2: hsp=%u vsp=%u\n\n", calc_hsp, calc_vsp);

		if (calc_hf > max_hfreq)
		{
			pmsg (VL_DEBUG, "[main] hf: %f > %f\n", calc_hf, max_hfreq);
			calc_hf = max_hfreq;
			calc_dc = calc_hf * calc_hfl / 1000;
			calc_vf = (1000000 * calc_dc) / (calc_hfl * calc_vfl);
		}

		pmsg (VL_DEBUG, "[main] dump 3: visible=%ux%u frame=%ux%u\n", modes[i].hres, modes[i].vres, calc_hfl, calc_vfl);
		pmsg (VL_DEBUG, "[main] dump 3: dc=%f hf=%f vf=%f\n", calc_dc, calc_hf, calc_vf);
		pmsg (VL_DEBUG, "[main] dump 3: hsp=%u vsp=%u\n\n", calc_hsp, calc_vsp);

		/*
		 * printing out the results
		 */

		if (fbset == 0)
		{
			fprintf (stdout, "Modeline \"%ux%u\" %0.2f %u %u %u %u %u %u %u %u  # %0.0f MHz, %0.1f kHz, %0.1f Hz\n",
				modes[i].hres, modes[i].vres,
				calc_dc,
				modes[i].hres,
				modes[i].hres + calc_hfp,
				modes[i].hres + calc_hfp + calc_hsp,
				modes[i].hres + calc_hfp + calc_hsp + calc_hbp,
				modes[i].vres,
				modes[i].vres + calc_vfp,
				modes[i].vres + calc_vfp + calc_vsp,
				modes[i].vres + calc_vfp + calc_vsp + calc_vbp,
				calc_dc, calc_hf, calc_vf);
		}
		else
		{
			fprintf (stdout, "timings %0.0f %u %u %u %u %u %u  # %0.0f MHz, %0.1f kHz, %0.1f Hz\n",
				1000000 / calc_dc, calc_hbp, calc_hfp, calc_vbp, calc_vfp, calc_hsp, calc_vsp,
				calc_dc, calc_hf, calc_vf);
		}
	}
	
	return (0);
}





/* EOF */
