/*
 * Copyright (c) 2000 by Conectiva S.A. (http://www.conectiva.com)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *  
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * CONECTIVA LINUX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of Conectiva Linux shall
 * not be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization from
 * Conectiva Linux.
 *
 * Author: Paulo César Pereira de Andrade <pcpa@conectiva.com.br>
 *
 * $XFree86: xc/programs/Xserver/hw/xfree86/xf86cfg/cards.c,v 1.15 2003/02/15 05:37:58 paulo Exp $
 */

#define CARDS_PRIVATE
#include "cards.h"

#undef SERVER	/* defined in config.h, but of no use here */

/* return values from ReadCardsLine. */
#define ERROR		-3
#define UNKNOWN		-2
#define END		-1
#define	NOTUSEFUL	0
#define	NAME		1
#define	CHIPSET		2
#define	SERVER		3
#define	DRIVER		4
#define	RAMDAC		5
#define	CLOCKCHIP	6
#define	DACSPEED	7
#define	NOCLOCKPROBE	8
#define	UNSUPPORTED	9
#define	SEE		10
#define	LINE		11

/*
 * Prototypes
 */
static int ReadCardsLine(FILE*, char*);	/* must have 256 bytes */
static int CompareCards(_Xconst void *left, _Xconst void *right);
static int BCompareCards(_Xconst void *left, _Xconst void *right);
static void DoReadCardsDatabase(void);
static char **DoFilterCardNames(char *pattern, int *result);

#ifdef USE_MODULES

typedef struct {
    int ivendor;
    unsigned short vendor;
    unsigned short valid_vendor;
    char *chipsets;
    int num_chipsets;
} chipset_check;
#endif

/*
 * Initialization
 */
static int linenum = 0;
static char *Cards = "lib/X11/Cards";
CardsEntry **CardsDB;
int NumCardsEntry;

/*
 * Implementation
 */
#ifdef USE_MODULES
const pciVendorInfo *xf86PCIVendorInfo;
#endif

#ifdef USE_MODULES
void
InitializePciInfo(void)
{
    xf86PCIVendorInfo = pciVendorInfoList;
}

void
CheckChipsets(xf86cfgModuleOptions *opts, int *err)
{
    int i, j, ichk, ivnd = 0, vendor = -1, device;
    const pciDeviceInfo **pDev;
    SymTabPtr chips = opts->chipsets;
    chipset_check *check = NULL;
    int num_check = 0;

    if (!chips) {
	CheckMsg(CHECKER_NO_CHIPSETS, "WARNING No chipsets specified.\n");
	++*err;
	return;
    }

    while (chips->name) {
	device = chips->token & 0xffff;
	vendor = (chips->token & 0xffff0000) >> 16;
	if (vendor == 0)
	    vendor = opts->vendor;

	for (ichk = 0; ichk < num_check; ichk++)
	    if (check[ichk].vendor == vendor)
		break;
	if (ichk >= num_check) {
	    check = (chipset_check*)
		XtRealloc((XtPointer)check,
			  sizeof(chipset_check) * (num_check + 1));
	    check[num_check].vendor = vendor;
	    memset(&check[num_check], 0, sizeof(chipset_check));
	    ++num_check;
	}

	/* Search for vendor in xf86PCIVendorInfo */
	if (xf86PCIVendorInfo) {
	    for (ivnd = 0; xf86PCIVendorInfo[ivnd].VendorID; ivnd++)
		if (vendor == xf86PCIVendorInfo[ivnd].VendorID)
		    break;
	}
	if (xf86PCIVendorInfo && xf86PCIVendorInfo[ivnd].VendorID) {
	    check[ichk].valid_vendor = 1;
	    check[ichk].ivendor = ivnd;
	}
	else {
	    CheckMsg(CHECKER_CANNOT_VERIFY_CHIPSET,
		     "WARNING Cannot verify chipset \"%s\" (0x%x)\n",
		      chips->name, device);
	    ++*err;
	    ++chips;
	    continue;
	}

	if (xf86PCIVendorInfo &&
	    (pDev = xf86PCIVendorInfo[ivnd].Device) != NULL) {
	    if (check[ichk].chipsets == NULL) {
		for (j = 0; pDev[j]; j++)
		    ;
		check[ichk].chipsets = (char*)XtCalloc(1, j);
	    }
	    for (j = 0; pDev[j]; j++) {
		if (device == pDev[j]->DeviceID) {
		    if (strcmp(chips->name, pDev[j]->DeviceName)) {
			CheckMsg(CHECKER_NOMATCH_CHIPSET_STRINGS,
			     "WARNING chipset strings don't match: \"%s\" \"%s\" (0x%x)\n",
			     chips->name, xf86PCIVendorInfo[ivnd].Device[j]->DeviceName,
			     device);
			++*err;
		    }
		    break;
		}
	    }
	    if (!pDev[j]) {
		CheckMsg(CHECKER_CHIPSET_NOT_LISTED,
		     "WARNING chipset \"%s\" (0x%x) not in list.\n", chips->name, device);
		++*err;
	    }
	    else
		check[ichk].chipsets[j] = 1;
	}
	++chips;
    }

    for (i = 0; i < num_check; i++) {
	if (!check[i].valid_vendor) {
	    CheckMsg(CHECKER_CHIPSET_NO_VENDOR,
		     "WARNING No such vendor 0x%x\n", vendor);
	    ++*err;
	}
	for (j = 0; j < check[i].num_chipsets; j++) {
	    if (xf86PCIVendorInfo && !check[i].chipsets[j]) {
		CheckMsg(CHECKER_CHIPSET_NOT_SUPPORTED,
			 "NOTICE chipset \"%s\" (0x%x) not listed as supported.\n",
			 xf86PCIVendorInfo[check[i].ivendor].Device[j]->DeviceName,
			 xf86PCIVendorInfo[check[i].ivendor].Device[j]->DeviceID);
	    }
	}
	XtFree(check[i].chipsets);
    }

    XtFree((XtPointer)check);
}
#endif

void
ReadCardsDatabase(void)
{
#ifdef USE_MODULES
    if (!nomodules) {
	int i, j, ivendor, idevice;
	char name[256];
	_Xconst char *vendor, *device;
	CardsEntry *entry = NULL, *tmp;
	xf86cfgModuleOptions *opts = module_options;
	const pciDeviceInfo **pDev;

	/* Only list cards that have a driver installed */
	while (opts) {
	    if (opts->chipsets) {
		SymTabPtr chips = opts->chipsets;

		while (chips->name) {
		    vendor = opts->name;
		    device = chips->name;
		    ivendor = (chips->token & 0xffff0000) >> 16;
		    idevice = chips->token & 0xffff0;
		    if (ivendor == 0)
			ivendor = opts->vendor;

		    if (xf86PCIVendorInfo) {
			for (i = 0; xf86PCIVendorInfo[i].VendorName; i++)
			    if (ivendor == xf86PCIVendorInfo[i].VendorID) {
				vendor = xf86PCIVendorInfo[i].VendorName;
				break;
			    }
			if (xf86PCIVendorInfo[i].VendorName) {
			    if ((pDev = xf86PCIVendorInfo[i].Device)) {
				for (j = 0; pDev[j]; j++)
				    if (idevice == pDev[j]->DeviceID) {
					device = pDev[j]->DeviceName;
					break;
				    }
			    }
			}
		    }

		    /* Since frequently there is more than one driver for a
		     * single vendor, it is required to avoid duplicates.
		     */
		    XmuSnprintf(name, sizeof(name), "%s %s", vendor, device);
		    tmp = LookupCard(name);

		    if (tmp == NULL || strcmp(tmp->chipset, chips->name) ||
			strcmp(tmp->driver, opts->name)) {
			entry = (CardsEntry*)XtCalloc(1, sizeof(CardsEntry));
			if (NumCardsEntry % 16 == 0) {
			    CardsDB = (CardsEntry**)XtRealloc((XtPointer)CardsDB,
				    sizeof(CardsEntry*) * (NumCardsEntry + 16));
			}
			CardsDB[NumCardsEntry++] = entry;
			entry->name = XtNewString(name);

			/* XXX no private copy of strings */
			entry->chipset = (char*)chips->name;
			entry->driver = opts->name;

			/* better than linear searchs to find duplicates */
			qsort(CardsDB, NumCardsEntry, sizeof(CardsEntry*),
			      CompareCards);
		    }
		    ++chips;
		}
	    }
	    opts = opts->next;
	}

	/* fix entries with the same name */
	for (i = 0; i < NumCardsEntry - 2;) {
	    for (j = i + 1; j < NumCardsEntry - 1 &&
		 strcmp(CardsDB[i]->name, CardsDB[j]->name) == 0; j++)
		    ;

	    if (i + 1 != j) {
		while (i < j) {
		    char *str;

		    if (strcmp(CardsDB[i]->chipset, CardsDB[j]->chipset))
			str = CardsDB[i]->chipset;
		    else
			str = CardsDB[i]->driver;

		    XmuSnprintf(name, sizeof(name), "%s (%s)",
				CardsDB[i]->name, str);
		    XtFree(CardsDB[i]->name);
		    CardsDB[i]->name = XtNewString(name);

		    ++i;
		}
	    }
	    else
		++i;
	}

	/* make sure data is valid to bsearch in */
	qsort(CardsDB, NumCardsEntry, sizeof(CardsEntry*), CompareCards);
    }
    else
#endif
	DoReadCardsDatabase();
}

static void
DoReadCardsDatabase(void)
{
    char buffer[256];
    FILE *fp = fopen(Cards, "r");
    int i, result;
    CardsEntry *entry = NULL;
    static char *CardsError = "Error reading Cards database, at line %d (%s).\n";

    if (fp == NULL) {
	fprintf(stderr, "Cannot open Cards database.\n");
	exit(1);
    }

    while ((result = ReadCardsLine(fp, buffer)) != END) {
	switch (result) {
	    case ERROR:
		fprintf(stderr, CardsError, linenum, buffer);
		break;
	    case UNKNOWN:
		fprintf(stderr,
			"Unknown field type in Cards database, at line %d (%s).\n",
			linenum, buffer);
		break;
	    case NAME:
		entry = calloc(1, sizeof(CardsEntry));
		if (NumCardsEntry % 16 == 0) {
		    CardsDB = realloc(CardsDB, sizeof(CardsEntry*) *
				      (NumCardsEntry + 16));
		    if (CardsDB == NULL) {
			fprintf(stderr, "Out of memory reading Cards database.\n");
			exit(1);
		    }
		}
		CardsDB[NumCardsEntry++] = entry;
		entry->name = strdup(buffer);
		break;
	    case CHIPSET:
		if (entry == NULL || entry->chipset != NULL) {
		    fprintf(stderr, CardsError, linenum, buffer);
		}
#if 0
		else
		    entry->chipset = strdup(buffer);
#endif
		break;
	    case SERVER:
		if (entry == NULL || entry->server != NULL) {
		    fprintf(stderr, CardsError, linenum, buffer);
		}
		else
		    entry->server = strdup(buffer);
		break;
	    case DRIVER:
		if (entry == NULL || entry->driver != NULL) {
		    fprintf(stderr, CardsError, linenum, buffer);
		}
		else
		    entry->driver = strdup(buffer);
		break;
	    case RAMDAC:
		if (entry == NULL || entry->ramdac != NULL) {
		    fprintf(stderr, CardsError, linenum, buffer);
		}
		else
		    entry->ramdac = strdup(buffer);
		break;
	    case CLOCKCHIP:
		if (entry == NULL || entry->clockchip != NULL) {
		    fprintf(stderr, CardsError, linenum, buffer);
		}
		else
		    entry->clockchip = strdup(buffer);
		break;
	    case DACSPEED:
		if (entry == NULL || entry->dacspeed != NULL) {
		    fprintf(stderr, CardsError, linenum, buffer);
		}
		else
		    entry->dacspeed = strdup(buffer);
		break;
	    case NOCLOCKPROBE:
		if (entry == NULL) {
		    fprintf(stderr, CardsError, linenum, buffer);
		}
		else
		    entry->flags |= F_NOCLOCKPROBE;
		break;
	    case UNSUPPORTED:
		if (entry == NULL) {
		    fprintf(stderr, CardsError, linenum, buffer);
		}
		else
		    entry->flags |= F_UNSUPPORTED;
		break;
	    case SEE:
		if (entry == NULL || entry->see != NULL) {
		    fprintf(stderr, CardsError, linenum, buffer);
		}
		else
		    entry->see = strdup(buffer);
		break;
	    case LINE:
		if (entry == NULL) {
		    fprintf(stderr, CardsError, linenum, buffer);
		}
		else if (entry->lines == NULL)
		    entry->lines = strdup(buffer);
		else {
		    char *str = malloc(strlen(entry->lines) + strlen(buffer) + 2);

		    sprintf(str, "%s\n%s", entry->lines, buffer);
		    free(entry->lines);
		    entry->lines = str;
		}
		break;
	}
    }

    fclose(fp);

    qsort(CardsDB, NumCardsEntry, sizeof(CardsEntry*), CompareCards);

#ifdef DEBUG
    for (i = 0; i < NumCardsEntry - 1; i++) {
	if (strcmp(CardsDB[i]->name, CardsDB[i+1]->name) == 0)
	    fprintf(stderr, "Duplicate entry in Cards database: (%s).\n",
		    CardsDB[i]->name);
    }
#endif

    for (i = 0; i < NumCardsEntry - 1; i++) {
	if (CardsDB[i]->see != NULL) {
	    if ((entry = LookupCard(CardsDB[i]->see)) == NULL) {
		fprintf(stderr, "Cannot find card '%s' for filling defaults.\n",
			CardsDB[i]->see);
		continue;
	    }
	    if (CardsDB[i]->chipset == NULL && entry->chipset != NULL)
		CardsDB[i]->chipset = strdup(entry->chipset);
	    if (CardsDB[i]->server == NULL && entry->server != NULL)
		CardsDB[i]->server = strdup(entry->server);
	    if (CardsDB[i]->driver == NULL && entry->driver != NULL)
		CardsDB[i]->driver = strdup(entry->driver);
	    if (CardsDB[i]->ramdac == NULL && entry->ramdac != NULL)
		CardsDB[i]->ramdac = strdup(entry->ramdac);
	    if (CardsDB[i]->clockchip == NULL && entry->clockchip != NULL)
		CardsDB[i]->clockchip = strdup(entry->clockchip);
	    if (CardsDB[i]->dacspeed == NULL && entry->dacspeed != NULL)
		CardsDB[i]->dacspeed = strdup(entry->dacspeed);
	    if (CardsDB[i]->flags & F_NOCLOCKPROBE)
		CardsDB[i]->flags |= F_NOCLOCKPROBE;
	    if (CardsDB[i]->flags & F_UNSUPPORTED)
		CardsDB[i]->flags |= F_UNSUPPORTED;
	    if (entry->lines != NULL) {
		if (CardsDB[i]->lines == NULL)
		    CardsDB[i]->lines = strdup(entry->lines);
		else {
		    char *str = malloc(strlen(entry->lines) +
					      strlen(CardsDB[i]->lines) + 2);

		    sprintf(str, "%s\n%s", CardsDB[i]->lines, entry->lines);
		    free(CardsDB[i]->lines);
		    CardsDB[i]->lines = str;
		}
	    }
	    if (entry->see != NULL) {
#ifdef DEBUG
		fprintf(stderr, "Nested SEE entry: %s -> %s -> %s\n",
			CardsDB[i]->name, CardsDB[i]->see, entry->see);
#endif
		CardsDB[i]->see = strdup(entry->see);
		--i;
		continue;
	    }
	    free(CardsDB[i]->see);
	    CardsDB[i]->see = NULL;
	}
    }
}

CardsEntry *
LookupCard(char *name)
{
    CardsEntry **ptr;

    ptr = (CardsEntry**)bsearch(name, CardsDB, NumCardsEntry,
				sizeof(CardsEntry*), BCompareCards);

    return (ptr != NULL ? *ptr : NULL);
}

char **
GetCardNames(int *result)
{
    char **cards = NULL;
    int ncards;

    for (ncards = 0; ncards < NumCardsEntry; ncards++) {
	if (ncards % 16 == 0) {
	    if ((cards = (char**)realloc(cards, sizeof(char*) *
					 (ncards + 16))) == NULL) {
		fprintf(stderr, "Out of memory.\n");
		exit(1);
	    }
	}
	cards[ncards] = strdup(CardsDB[ncards]->name);
    }

    *result = ncards;

    return (cards);
}

char **
FilterCardNames(char *pattern, int *result)
{
#ifdef USE_MODULES
    if (!nomodules) {
	char **cards = NULL;
	int i, ncards = 0;

	for (i = 0; i < NumCardsEntry; i++) {
	    if (strstr(CardsDB[i]->name, pattern) == NULL)
		continue;
	    if (ncards % 16 == 0) {
		if ((cards = (char**)realloc(cards, sizeof(char*) *
					     (ncards + 16))) == NULL) {
		    fprintf(stderr, "Out of memory.\n");
		    exit(1);
		}
	    }
	    cards[ncards] = strdup(CardsDB[i]->name);
	    ++ncards;
	}

	*result = ncards;

	return (cards);
    }
#endif
    return (DoFilterCardNames(pattern, result));
}

static char **
DoFilterCardNames(char *pattern, int *result)
{
    FILE *fp;
    char **cards = NULL;
    int len, ncards = 0;
    char *cmd, *ptr, buffer[256];

    cmd = malloc(32 + (strlen(pattern) * 2) + strlen(Cards));

    strcpy(cmd, "egrep -i '^NAME\\ .*");
    len = strlen(cmd);
    ptr = pattern;
    while (*ptr) {
	if (!isalnum(*ptr)) {
	    cmd[len++] = '\\';
	}
	cmd[len++] = *ptr++;
    }
    cmd[len] = '\0';
    strcat(cmd, ".*$' ");
    strcat(cmd, Cards);
    strcat(cmd, " | sort");
    /*sprintf(cmd, "egrep -i '^NAME\\ .*%s.*$' %s | sort", pattern, Cards);*/

    if ((fp = popen(cmd, "r")) == NULL) {
	fprintf(stderr, "Cannot read Cards database.\n");
	exit(1);
    }
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
	ptr = buffer + strlen(buffer) - 1;
	while (isspace(*ptr) && ptr > buffer)
	    --ptr;
	if (!isspace(*ptr) && ptr > buffer)
	    ptr[1] = '\0';
	ptr = buffer;
	while (!isspace(*ptr) && *ptr)	/* skip NAME */
	    ++ptr;
	while (isspace(*ptr) && *ptr)
	    ++ptr;
	if (ncards % 16 == 0) {
	    if ((cards = (char**)realloc(cards, sizeof(char*) *
					 (ncards + 16))) == NULL) {
		fprintf(stderr, "Out of memory.\n");
		exit(1);
	    }
	}
	cards[ncards++] = strdup(ptr);
    }
    free(cmd);

    *result = ncards;

    return (cards);
}

static int
ReadCardsLine(FILE *fp, char *value)
{
    char name[32], buffer[256], *ptr, *end;
    int result = NOTUSEFUL;

    ++linenum;

    if (fgets(buffer, sizeof(buffer), fp) == NULL)
	return (END);

    ptr = buffer;
    /* skip initial spaces; should'nt bother about this.. */
    while (isspace(*ptr) && *ptr)
	++ptr;

    if (*ptr == '#' || *ptr == '\0')
	return (NOTUSEFUL);

    end = ptr;
    while (!isspace(*end) && *end)
	++end;
    if (end - ptr > sizeof(buffer) - 1) {
	strncpy(value, buffer, 255);
	value[255] = '\0';
	return (ERROR);
    }
    strncpy(name, ptr, end - ptr);
    name[end - ptr] = '\0';

    /* read the optional arguments */
    ptr = end;
    while (isspace(*ptr) && *ptr)
	++ptr;

    end = ptr + strlen(ptr) - 1;
    while (isspace(*end) && end > ptr)
	--end;
    if (!isspace(*end))
	++end;
    *end = '\0';

    if (strcmp(name, "NAME") == 0)
	result = NAME;
    else if (strcmp(name, "CHIPSET") == 0)
	result = CHIPSET;
    else if (strcmp(name, "SERVER") == 0)
	result = SERVER;
    else if (strcmp(name, "DRIVER") == 0)
	result = DRIVER;
    else if (strcmp(name, "RAMDAC") == 0)
	result = RAMDAC;
    else if (strcmp(name, "CLOCKCHIP") == 0)
	result = CLOCKCHIP;
    else if (strcmp(name, "DACSPEED") == 0)
	result = DACSPEED;
    else if (strcmp(name, "NOCLOCKPROBE") == 0)
	result = NOCLOCKPROBE;
    else if (strcmp(name, "UNSUPPORTED") == 0)
	result = UNSUPPORTED;
    else if (strcmp(name, "SEE") == 0)
	result = SEE;
    else if (strcmp(name, "LINE") == 0)
	result = LINE;
    else if (strcmp(name, "END") == 0)
	result = END;
    else {
	strcpy(value, name);
	return (UNKNOWN);
    }

    /* value *must* have at least 256 bytes */
    strcpy(value, ptr);

    return (result);
}

static int
CompareCards(_Xconst void *left, _Xconst void *right)
{
    return strcasecmp((*(CardsEntry**)left)->name, (*(CardsEntry**)right)->name);
}

static int
BCompareCards(_Xconst void *name, _Xconst void *card)
{
  return (strcasecmp((char*)name, (*(CardsEntry**)card)->name));
}
