/*
 * monitors: read the monitors database
 */
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <ctype.h>
#include <assert.h>
#include <stdlib.h>

#include "util.h"
#include "Monitor.h"

class Monitor *monitor = 0;
static int sz_array = 0;
int nr_monitor = 0;
static char string_buf[1024];

#define prefix(a,b)	(strncasecmp(a, b, sizeof(b)-1) == 0)


/*
 * string() picks out the contents of a possibly-quote-delimited field after
 * some keyword that we don't care about, returning it to the caller
 * in a static buffer.
 */
char *
string(char **inp)
{
    char *p = *inp, *q, *b;

    b = string_buf;
    while (*p && !isspace(*p))
	++p;
    while (isspace(*p))
	++p;
    if (*p == '"')
	for (q = ++p; *q && *q != '"';)
	    *b++ = *q++;
    else
	for (q = p; *q && !isspace(*q); )
	    *b++ = *q++;

    *inp = q;
    *b = 0;
    return string_buf;
} /* string */

/*
 * value() picks out the rest of the line, minus any trailing whitespace,
 * after some insignificant keyword.  (the rest of the line comes back in
 * a static buffer.)
 */
char *
value(char *p)
{
    char *q;

    while (*p && !isspace(*p))
	++p;
    while (isspace(*p))
	++p;
    for (q = p; *q; ++q)
	;
    while (isspace(q[-1]) && q > p)
	--q;
    if (q == p)
	return 0;

    memcpy(string_buf, p, (int)(q-p));
    string_buf[(int)(q-p)] = 0;
    return string_buf;
} /* value */


/*
 * sort a monitor database on the vendor name, then the product()
 */
static int
monitor_cmp(const void *a, const void *b)
{
     Monitor *ma = (Monitor *)a,
	     *mb = (Monitor *)b;
    int rc;
    int va, vb;

    va = strcmp(ma->vendor(), "VESA");
    vb = strcmp(mb->vendor(), "VESA");

    if (va == 0)
	rc = (vb == 0) ? 0 : -1;
    else if (vb == 0)
	rc = 1;
    else
	rc = strcasecmp(ma->vendor(), mb->vendor());

    return (rc == 0) ? strcasecmp(ma->product(), mb->product()) : rc;
} /* monitor_cmp */


/*
 * get_monitordb() reads the monitors database out of MONITOR_FILE
 */
int
Monitor::parse_database(messagefunc msg)
{
    char line[200];
    int gathering=0;
    int size;
    struct Monitor *current;
    int x;
    FILE *f;
    char *p;
    char *q;

    if (monitor)
	return 0;

    sz_array = 100;
    nr_monitor = 1;
    monitor = (class Monitor*)calloc(sz_array, sizeof monitor[0]);
    if (monitor == (class Monitor*)0) {
	msg("Out of memory initializing monitor database");
	return EOF;
    }

    monitor[0].vendor("(Customise)");
    monitor[0].product("hand-configure your monitor");
    monitor[0]._custom = 1;

    if ((f = fopen(MONITOR_FILE, "r")) == 0)
	return 0;

    size = 0;
    while (fgets(line, sizeof line, f)) {
	//putchar('>'); putchar(' '); fputs(line, stdout);
	for (p=line; isspace(*p); ++p)
	    ;
	if (*p == 0 || *p == '#')
	    continue;
	if (prefix(p, "Section") && strieq(string(&p), "Monitor")
				 && monitor[nr_monitor].scan(f)
				 && monitor[nr_monitor].hsync()
				 && monitor[nr_monitor].vsync()
				 && monitor[nr_monitor].vendor()
				 && monitor[nr_monitor].product()) {
	    if (nr_monitor >= sz_array-1) {
		sz_array *= 2;
		monitor = (class Monitor*)realloc(monitor,
				sz_array * sizeof monitor[0]);
	    }
	    nr_monitor ++;
	}
    }
    qsort(&monitor[1],
	  nr_monitor-1,
	  sizeof *monitor,
	  monitor_cmp);
    return 0;
} /* parse_database */


int
Monitor::print(FILE *f)
{
    if (_vendor && _model)
	fprintf(f, "  Identifier \"%s %s\"\n", _vendor, _model);
    if (_vendor)
	fprintf(f, "  VendorName \"%s\"\n", _vendor);
    if (_model)
	fprintf(f, "  ModelName \"%s\"\n", _model);
    if (_bandwidth)
	fprintf(f, "  BandWidth %s\n", _bandwidth);
    if (_hsync)
	fprintf(f, "  HorizSync %s\n", _hsync);
    if (_vref)
	fprintf(f, "  VertRefresh %s\n", _vref);

    for (int i = 0; i < _nrmodes; i++) {
	_modes[i].print(f);
	putc('\n', f);
    }
    return feof(f) || ferror(f);
}


int
Monitor::scan(FILE *f)
{
    char buf[1024];
    char *p;

    Erase();
    _modes = (ModeLine*)calloc(1, sizeof _modes[0]);

    while (fgets(buf, sizeof buf, f)) {
	for (p = buf; isspace(*p); ++p)
	    ;
	if (*p == '#')
	    continue;
	if (prefix(p, "EndSection"))
	    return 1;
	if (prefix(p, "VendorName"))
	    vendor(string(&p));
	else if (prefix(p, "ModelName"))
	    product(string(&p));
	else if (prefix(p, "BandWidth"))
	    bandwidth(value(p));
	else if (prefix(p, "HorizSync"))
	    hsync(value(p));
	else if (prefix(p, "VertRefresh"))
	    vsync(value(p));
	else if (prefix(p, "Resolution")) {
	    if (sscanf(p+sizeof "Resolution", " %d %d", &_hrez, &_vrez) != 2)
		_vrez = _hrez = 0;
	}
	else if (prefix(p, "Mode")) {
	    if (_modes[_nrmodes].scan(p, f)) {
		_nrmodes ++;
		_modes = (class ModeLine*)realloc(_modes,
			    (1+_nrmodes) * sizeof _modes[0]);
	    }
	}
    }
    free(_modes);
    _modes = 0;
    return 0;
}

int
ModeLine::print(FILE *f)
{
    if (modedata)
	fprintf(f, "  ModeLine \"%dx%d\" %5.2f %s\n",
		hsize, vsize, clock, modedata);
    return feof(f) || ferror(f);
}


int 
ModeLine::scan(char *p, FILE *f)
{
    float clk;
    int x, y;
    int ok;
    char *q;
    char htimings[80], vtimings[80];
    char flags[80];
    char data[512];

    selected = 0;

    if (prefix(p, "Modeline")) {
	if (sscanf(p+8, " \"%dx%d\" %f %[^\n]", &y, &x, &clk, data) == 4) {
	    hsize = y;
	    vsize = x;
	    clock = clk;
	    modedata = strdup(data);
	    return 1;
	}
    }
    else if (isspace(p[4])) {
	ok = (sscanf(p+4, " \"%dx%d\"", &y, &x) == 2);
	flags[0] = htimings[0] = vtimings[0] = 0;

	while (fgets(data, sizeof data, f)) {
	    //putchar('|'); putchar(' '); fputs(data, stdout);
	    strtok(data, "\n");
	    for (p = data; isspace(*p); ++p)
		;
	    if (*p == '#')
		continue;
	    if (prefix(p, "Flags")) {
		flags[0] = 0;
		do {
		    q = string(&p);
		    if (*q == 0)
			break;
		    strcat(flags, " ");
		    strcat(flags, q);
		} while (*p);
	    }
	    else if (prefix(p, "DotClock"))
		ok = ok && (sscanf(p+8, " %f", &clk) == 1);
	    else if (prefix(p, "HTimings")) {
		p += 8;
		for (q = p; *q && !prefix(q, "VTimings"); ++q)
		    ;
		if (q) {
		    strncpy(htimings, p, (q-p));
		    htimings[q-p] = 0;
		    strcpy(vtimings, q+8);
		}
		else strcpy(htimings, p);
	    }
	    else if (prefix(p, "VTimings"))
		strcpy(vtimings, p+8);
	    else if (prefix(p, "EndMode")) {
		if (! (ok && htimings[0] && vtimings[0]) )
		    return 0;
		hsize = y;
		vsize = x;
		clock = clk;
		sprintf(data, "%s %s%s", htimings, vtimings, flags);
		modedata = strdup(data);
		return 1;
	    }
	}
    }
    return 0;
}


#if TEST_MODULE
void
Error(char *msg, ...)
{
    fprintf(stderr, "%s\n", msg);
}

main()
{
    int rc = Monitor::parse_database(Error);
    int x, y;

    ModeLine B = ModeLine(100,100,10.0,"hello, sailor");

    if (rc == 0) {
	printf("we have a monitor database with %d entr%s\n",
		    nr_monitor, (nr_monitor != 1) ? "ies" : "y");
	for (x=0; x<nr_monitor; x++) {
	    puts("MONITOR:");
	    monitor[x].print(stdout);
	}
    }
    else
	puts("Yes we have no monitor database!");
}
#endif
