/* 

The lookup*.c files contain the interface to the database. Apart from a few calls
in printmovie.c and printname.c, all accesses to the movie-db package
are via these files.  This file does lookups on a person's name.

*/

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>


#include <stdio.h>
#include <stdlib.h>

#include "moviedb.h"
#include "dbutils.h"
#include "filmography.h"
#include "aka.h"
#include "ratings.h"
#include "years.h"
#include "biographies.h"
#include "filmbuf.h"


#include "LinkView.h"
#include "searchname.h"
#include "printname.h"
#include "history.h"
#include "popup.h"
#include "PcBar.h"
#include "LinkText.h"
#include "lookupname.h"
#include "xregal.h"
#include "utility.h"

/* DATA TYPES */
/* ---------- */

typedef struct SubstringData {
	char *substring;
	int *lists;
	} SubstringData;

typedef struct NameRec {
	char             *name;
	struct NameRec  *next;
	} NameRec;


/* FUNCTION PROTOTYPES */
/* ------------------- */

/* in filmography.c */
extern struct listRec *lookupFilmography ( FILE *dbFp, FILE *indexFp, 
	struct nameSearchRec *nrec, int listId);
extern void filmographyResultsKeyLookup (struct nameSearchRec *chain);
extern void filmographySearchKeyLookup (struct nameSearchRec *chain);
extern void straightFilmographySearchKeyLookup (struct nameSearchRec *chain);



/* GLOBAL VARIABLES */
/* ---------------- */

struct nameSearchRec *nchain=NULL;
static NameRec* namelist=NULL;

/* this variable needs to be global so that a substring search can
   be cancelled from the popup dialog box */
int name_substring_stage=0;

/* -----------------------------------------------------------------------*/
/*                              Diagnostic functions                      */
/* -----------------------------------------------------------------------*/

#ifdef CRASHME
void crashme_name(void) {
	char line[MXLINELEN];
	char *ptr;
	static int count=0;
	static FILE *nameKeyFp=NULL;
	static int lists[NO_OF_FILMOGRAPHY_LISTS];
	int i;

	if (nameKeyFp==NULL) {
		nameKeyFp = openFile(NAMEKEY);
		for (i=0; i<NO_OF_FILMOGRAPHY_LISTS; i++)
			lists[i]=1;
		}

	while (count<(-app_data.crashme)-1) {
		count++;
		if (fgets ( line, MXLINELEN, nameKeyFp ) == NULL) 
			exit(0);
		}

	count++;
	if (fgets ( line, MXLINELEN, nameKeyFp ) == NULL) 
		exit(0);
	ptr = strchr(line, FSEP);
	*ptr = '\0';
	printf("%d. %s\n", count, line);
	if (!LookupName(line, lists, True, SEARCH_EXACT))
		XtError("Name not found!");
  
	}
#endif

/* compare 2 names, ignoring any appended roman numerals */

int nameCompare(char *name1, char *name2) {
	char shortname1[255];
	char shortname2[255];
	char *cp;

	/* if either name doesn't have roman numerals do a normal compare */
	if (name1[strlen(name1)-1] != ')')
		return caseCompare(name1, name2);
	if (name2[strlen(name2)-1] != ')')
		return caseCompare(name1, name2);

	/* strip out the roman numerals */
	strcpy(shortname1, name1);
	strcpy(shortname2, name2);

	cp = strrchr(shortname1, '(');
	if (cp != NULL)
		*cp = '\0';
	cp = strrchr(shortname2, '(');
	if (cp != NULL)
		*cp = '\0';
	return caseCompare(shortname1, shortname2);
	}

/* -----------------------------------------------------------------------*/
/*                   Sorted List Manipulation Functions                   */
/* -----------------------------------------------------------------------*/


void AddName(NameRec **namelist, char *name) {
	NameRec *nrec, *prec;

	/* insert into empty list */
	if (*namelist==NULL) {
		*namelist = (NameRec*) XtMalloc(sizeof(NameRec));
		(*namelist)->name = duplicateString(name);
		(*namelist)->next=NULL;
		return;
		}

	/* insert into start of list */
	if (nameCompare(name, (*namelist)->name)<0) {
		nrec=*namelist;
		*namelist = (NameRec*) XtMalloc(sizeof(NameRec));
		(*namelist)->name = duplicateString(name);
		(*namelist)->next=nrec;
		return;
		}

	/* search the list to see where it goes */
	prec=*namelist;
	nrec=(*namelist)->next;
	while (nrec!=NULL) {
		/* ignore duplicates */
		if (caseCompare(name, nrec->name)==0) 
			return;
		if (nameCompare(name, nrec->name)<0) 
			break;
		prec=nrec;
		nrec=nrec->next;
		}

	prec->next=(NameRec*) XtMalloc(sizeof(NameRec));
	prec=prec->next;
	prec->name = duplicateString(name);
	prec->next=nrec;
	}


void FreeNameList(NameRec **namelistptr) {
	NameRec *nrec = *namelistptr;
	NameRec *next;

	while (nrec != NULL) {
		next = nrec->next;
		free(nrec->name);
		XtFree((char*)nrec);
		nrec=next;
		}
	*namelistptr=NULL;
	}


/* -----------------------------------------------------------------------*/
/*                 Functions relating to person searches                  */
/* -----------------------------------------------------------------------*/


/* Remove leading and trailing spaces, and compact multiple spaces to a
   single space */

void compact_spaces(char *dest, char *src) {
	/* remove leadingspaces */
	while (*src==' ')
		src++;

	/* remove duplicate spaces */
	while (*src!='\0') {
		if (*src==' ') {
			*dest = *src;
			dest++;
			src++;
			while (*src==' ') src++;
			}
		else {
			*dest = *src;
			dest++;
			src++;
			}
		}
	if (*(dest-1)==' ')
		dest--;
	*dest='\0';
	}

int count_spaces(char *str) {
	char *cp=str;
	int spaces=0;

	while (*cp!='\0') {
		if (*cp==' ') spaces++;
		cp++;
		}
	return spaces;
	}



/* Add a persons name to the name search chain taking care to accept names
   in both "Firstname Lastname" and "Lastname, Firstname" format. */ 

Boolean NameExists(char *name) {
	int i;
	char namebuf1[256];
	char namebuf2[256];
	char *cp, *extension;
	int spaces;
	struct nameSearchRec temp;

	compact_spaces(namebuf1, name);

	/* see if the name matches as it is */
	temp.name=namebuf1;
	temp.next=NULL;
	temp.nameKey = NONAME;
	straightFilmographySearchKeyLookup(&temp);
	if (temp.nameKey != NONAME) {
		strcpy(name, temp.name);
		return True;
		}

	/* if nothing was found, try rearranging the words from "Firstname Lastname"
	   format to "Lastname, Firstname" format */


	temp.next=NULL;
	temp.nameKey = NONAME;

	extension=NULL;
	cp=strstr(namebuf1, " (");
	if (cp!=NULL) {
		*cp='\0';
		extension=cp+1;
		}

	spaces=count_spaces(namebuf1);
	if (spaces==0)
		return False;

	cp=namebuf1;
	for (i=0; i<spaces; i++) {
		cp=strchr(cp+1, ' ');
		*cp='\0';
		if (extension!=NULL)
			sprintf(namebuf2, "%s, %s %s", cp+1, namebuf1, extension);
		else
			sprintf(namebuf2, "%s, %s", cp+1, namebuf1);
		temp.name=namebuf2;
		straightFilmographySearchKeyLookup(&temp);
		if (temp.nameKey != NONAME) {
			strcpy(name, namebuf2);
			return True;
			}
		*cp=' ';
		}

	return False;
	}



/* Displays a list of names from which the user must choose */

void displayMultipleNames(NameRec *namelist, char *name, int final_message) {
    NameRec *nrec;
	char buf[1024];

	ClearFilmbuf();
  	for (nrec=namelist; nrec!=NULL; nrec=nrec->next) {
		sprintf(buf, "<l -1:%s>%s<e>\n", nrec->name, nrec->name);
		AddToFilmbuf(buf);
		}

	switch (final_message) {
		case SEARCH_OVERFLOW:
			sprintf(buf, "\n<f2>Maximum of %d names reached.\n", 
						app_data.substringMax);
			AddToFilmbuf(buf);
			break;
		case SEARCH_ABORTED:
			AddToFilmbuf("\n<f2>Search aborted.\n");
			break;
		default:
			/* do nothing */
			;
		}

	AddToFilmbuf3("\n<m1><f2>", COPYRIGHT, "<f1><m0>");

	historySavePosition();
	SetTitle(name);
	XtVaSetValues(viewer, XtNhypertext, Filmbuf(), NULL);
	LinkViewReload(vport, 0, 0);
	XtVaSetValues(url_text, XtNstring, "", NULL);
	historyAdd(Filmbuf(), name, NULL);
	}



/* This is an Xt work procedure which gets called when the Xt main loop
   is idle.  It looks up each of the relevant data files for a person,
   returning after each lookup. The function returns True when it does
   not need to be called again. 

   This function expects just one name on the chain. */

Boolean doNameSearch(XtPointer client_data) {
	struct nameSearchRec *nchain = (struct nameSearchRec *) client_data;
	struct nameSearchRec *c;
	char urlbuf[256];
	FILE  *dbFp = NULL, *indexFp = NULL ;
	int listId ;
	char fn[MAXPATHLEN];
	static int invocation=0;
	int invocations=NO_OF_FILMOGRAPHY_LISTS+7;
	char buf[256];

	if (invocation==0) {
		sprintf(buf, "Looking up \"%s\"", nchain->name);
		SetTitle(buf);
		filmographySearchKeyLookup(nchain);
		invocation++;
		if (app_data.showprogress) return False;
		}

	while (invocation<=NO_OF_FILMOGRAPHY_LISTS) {
		listId = invocation-1;
		if (app_data.showprogress) 
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		if (nchain->searchFlags[listId]) {
			if (dbFp==NULL) {
				(void) constructFilename(fn,filmographyDefs[listId].stem, DBSEXT);
				dbFp = openFile(fn);
				(void) constructFilename(fn, filmographyDefs[listId].stem, NDXEXT);
				indexFp = openFile(fn);
				}
			nchain->lists[listId] = lookupFilmography(dbFp, indexFp, nchain, listId);
			if (nchain->lists[listId]!=NULL && nchain->firstMatch<0) 
				nchain->firstMatch = listId;
			}
		if (dbFp != NULL) {
			(void) fclose(dbFp);
			(void) fclose(indexFp);
			dbFp = NULL;
			}
		invocation++;
		if (app_data.showprogress) return False;
		}

	if (invocation==NO_OF_FILMOGRAPHY_LISTS+1) {
		if (app_data.showprogress) 
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addYearsToNameSearch(nchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+2) {
		if (app_data.showprogress) 
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addRatingsToNameSearch(nchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+3) {
		if (app_data.showprogress) 
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addAkaToNameSearch(nchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+4) {
		if (app_data.showprogress) 
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addBiographiesToNameSearch(nchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+5) {
		if (app_data.showprogress) 
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addNameAkaToNameSearch(nchain);
		invocation++;
		if (app_data.showprogress) return False;
		}

	if (invocation==NO_OF_FILMOGRAPHY_LISTS+6) {
		if (app_data.showprogress) 
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		filmographyResultsKeyLookup(nchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+7) {
		if (app_data.showprogress) 
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
	    sortListResults(nchain);
		invocation++;
		if (app_data.showprogress)  return False;
		}

	ClearFilmbuf();
	c=nchain;
	while (c != NULL) {
		printName(c);
		c = c->next;
		}

	historySavePosition();
	SetTitle(nchain->name);
	XtVaSetValues(viewer, XtNhypertext, Filmbuf(), NULL);
	LinkViewReload(vport, 0, 0);
	urlencode(urlbuf, "/Name?", nchain->name); 
	XtVaSetValues(url_text, XtNstring, urlbuf, NULL);
	historyAdd(Filmbuf(), nchain->name, urlbuf);

	/* set the cursor to normal*/
    SetBusy(viewer, False);

	invocation=0;
	PcBarSetPercentage(pcbar, 0);
	search_in_progress=False;

#ifdef CRASHME
	if (app_data.crashme != 0)
		crashme_name();
#endif

	return True;
	}



/* This is an Xt work procedure which gets called when the Xt main loop
   is idle.  It searches sequentially through the list of al name looking
   for substring matches. The function returns False every time it scans
   an additional 1% of the file and returns True when it does
   not need to be called again. */

Boolean doSubstringNameSearchAll(XtPointer client_data) {
	int final_message;
	char buf[256];
	char line[MXLINELEN];
	char *ptr;
	int pcdone;
	static int count;
	static long upper;
	static int lastpcdone=0;
	static FILE *nameKeyFp;
	static char searchtext[256];

	if (name_substring_stage==0) {
		strcpy(searchtext, (char*) client_data);
		count=0;
		nameKeyFp = openFile(NAMEKEY);
		fseek(nameKeyFp, 0, SEEK_END);
		upper=ftell(nameKeyFp);
		rewind(nameKeyFp);
		name_substring_stage++;
		}

	if (name_substring_stage==1) {
		while (fgets ( line, MXLINELEN, nameKeyFp ) != NULL) {

			/* update display */
			pcdone=ftell(nameKeyFp)*100/upper;
			if (pcdone != lastpcdone) {
				lastpcdone=pcdone;
				if (app_data.showprogress) {
					PcBarSetPercentage(pcbar, pcdone);
					sprintf(buf, "Matches on '%s': %d", searchtext, count);
					SetTitle(buf);
					}
				return False;
				}

			ptr = strchr ( line, FSEP ) ;
			*ptr = '\0' ;

			if (caseStrStr(line, searchtext) != NULL) {
				AddName(&namelist, line);	
				if (++count >= app_data.substringMax)
					break;
				}
			}
  
		fclose(nameKeyFp);
		name_substring_stage++;
		if (app_data.showprogress) return False;
		}

	PcBarSetPercentage(pcbar, 0);

	if (count==0) {
		Alert("No matches found!");
		XBell(XtDisplay(topLevel), 100);
	    SetBusy(viewer, False);
		name_substring_stage=0;
		search_in_progress=False;
		return True;
		}

	if (ns_popped_up) {
		XtPopdown(ns_shell);
		enableSearch();
		ns_popped_up=False;
		}

	if (count>=app_data.substringMax)
		final_message=SEARCH_OVERFLOW;
	else if (name_substring_stage==-1)
		final_message=SEARCH_ABORTED;
	else
		final_message=SEARCH_OK;
	displayMultipleNames(namelist, searchtext, final_message);
	FreeNameList(&namelist);
    SetBusy(viewer, False);
	name_substring_stage=0;
	search_in_progress=False;
	return True;
	}



/* This is an Xt work procedure which gets called when the Xt main loop
   is idle.  It searches sequentially through the list of movies looking
   for substring matches. The function returns False every time it scans
   an additional 1% of the files and returns True when it does
   not need to be called again. */

Boolean doSubstringNameSearch(XtPointer client_data) {
	int final_message;
	SubstringData *sdata = (SubstringData*) client_data;
	char buf[256];
	char *ptr;
	int pcdone;
	char line[MXLINELEN];
	char fn[MAXPATHLEN];
	NameID nameKey;
	long offset;
	int matched;
	static int listId;
	static FILE  *indexFp, *nameIndexFp, *nameKeyFp;
	static bytes_read;
	static int count;
	static long total;
	static int lastpcdone=0;
	static char searchtext[256];
	static int lists[NO_OF_FILMOGRAPHY_LISTS];

	if (name_substring_stage==0) {
		strcpy(searchtext, sdata->substring);
		nameIndexFp = openFile(NAMEIDX);
		nameKeyFp = openFile(NAMEKEY);
		count=0;

		/* find out how many bytes in total */
		total=0;
		for (listId=0; listId < NO_OF_FILMOGRAPHY_LISTS; listId++) {
			lists[listId] = sdata->lists[listId];
			if (lists[listId]) {
				constructFilename(fn, filmographyDefs[listId].stem, NDXEXT);
				indexFp = openFile(fn);
				fseek(indexFp, 0, SEEK_END);
				total += ftell(indexFp);
				fclose(indexFp);
				}
			}

		listId = 0; 
		indexFp=NULL;
		bytes_read=0;
		name_substring_stage++;
		if (app_data.showprogress) return False;
		}

	if (name_substring_stage==1) {
		while (listId < NO_OF_FILMOGRAPHY_LISTS) {
			if (lists[listId]) {
				if (indexFp==NULL) {
					constructFilename(fn, filmographyDefs[listId].stem, NDXEXT);
					indexFp = openFile(fn);
					}
				while (!feof(indexFp)) {

					/* update display of progress */
					pcdone=((ftell(indexFp)+bytes_read)*100)/total;
					if (pcdone != lastpcdone) {
						lastpcdone=pcdone;
						if (app_data.showprogress) {
							PcBarSetPercentage(pcbar, pcdone);
							sprintf(buf, "Matches on '%s': %d", searchtext, count);
							SetTitle(buf);
							}
						return False;
						}

					nameKey = getName(indexFp);
					matched = FALSE;
					getOffset(indexFp);
					fseek ( nameIndexFp, 3 * nameKey, SEEK_SET);
					offset = getOffset ( nameIndexFp );
					fseek ( nameKeyFp, offset, SEEK_SET );
					fgets(line, MXLINELEN, nameKeyFp);
					ptr = strchr(line, FSEP);
					if (ptr!=NULL)
						*ptr = '\0' ;

					if (caseStrStr(line, searchtext) != NULL) {	
						AddName(&namelist, line);
						count++;
						}

					/* have we reached the maximum? */
					if (count>=app_data.substringMax) {
						listId=NO_OF_FILMOGRAPHY_LISTS;
						break;
						}
					}
				bytes_read += ftell(indexFp);
				fclose(indexFp);
				indexFp=NULL;
				}
			listId++;
			}
		name_substring_stage++;
		if (app_data.showprogress) return False;
		}

	PcBarSetPercentage(pcbar, 0);

	if (count==0) {
		Alert("No matches found!");
		XBell(XtDisplay(topLevel), 100);
	    SetBusy(viewer, False);
		name_substring_stage=0;
		search_in_progress=False;
		return True;
		}


	if (ns_popped_up) {
		XtPopdown(ns_shell);
		enableSearch();
		ns_popped_up=False;
		}

	if (count>=app_data.substringMax)
		final_message=SEARCH_OVERFLOW;
	else if (name_substring_stage==-1)
		final_message=SEARCH_ABORTED;
	else
		final_message=SEARCH_OK;
	displayMultipleNames(namelist, searchtext, final_message);
	FreeNameList(&namelist);
    SetBusy(viewer, False);
	name_substring_stage=0;
	search_in_progress=False;
	return True;
	}

/* Returns the Roman numeral for a number in the range 1-99 */

char *IntToRoman(int i) {
	static char romanNumber[10];
	static char *romanUnits[10] = {
		"", "I", "II", "III", "IV", "V", 
		"VI", "VII", "VIII", "IX" };
	static char *romanTens[10] = {
		"", "X", "XX", "XXX", "XL",
		"L", "LX", "LXX", "LXXX", "XC" };

	romanNumber[0] = '\0';
	if ((i>0) && (i<100)) {
		strcat(romanNumber, romanTens[i/10]);
		strcat(romanNumber, romanUnits[i%10]);
		}
	return romanNumber;
	}

/* This is the main entry point when searching for a person's name.
   It is called when:
     a) The user clicks on OK in the name search dialog
     b) The user clicks on a hyperlink pointing to a person.

   The return value is True if a match was found, False otherwise. */

Boolean LookupName(char *searchname, int searchlists[], Boolean exact, int searchtype) {
	NameRec *nrec;
	struct nameSearchOptRec options = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } ;
	char namebuf[256];
	int matches, i;
	static SubstringData sdata;
	static char name[256];
	static int lists[NO_OF_FILMOGRAPHY_LISTS];

	for (i=0; i<NO_OF_FILMOGRAPHY_LISTS; i++)
		lists[i]=searchlists[i];

 	trim_text(name, searchname);

    SetBusy(viewer, True);

	if (searchtype==SEARCH_SUBSTRING) {
		search_in_progress=True;
		XtAppAddWorkProc(app_context, doSubstringNameSearchAll, name);
		return True;
		}
	else if (searchtype==SEARCH_SUBSTRING_SELECTED) {
		sdata.substring=name;
		sdata.lists=lists;
		search_in_progress=True;
		XtAppAddWorkProc(app_context, doSubstringNameSearch, &sdata);
		return True;
		}


	/* find out how many names in the database match the given name */
	if (NameExists(name)) {
		AddName(&namelist, name);
		}

	/* this is a kludge, but it's better than nothing */
	if (!exact) {
		for (i=1; i<40; i++) {
			sprintf(namebuf, "%s (%s)", name, IntToRoman(i));
			if (NameExists(namebuf))
				AddName(&namelist, namebuf);
			}
		}

	/* find out how many match this search */
  	for (matches=0, nrec=namelist; nrec!=NULL; nrec=nrec->next) 
		matches++;

	if (matches==0) {
	    SetBusy(viewer, False);
		return False;
		}

	/* we must have found something, so pop down the dialog box */
	if (ns_popped_up) {
		XtPopdown(ns_shell);
		ns_popped_up=False;
		enableSearch();
		}

	if (matches>1) {
		displayMultipleNames(namelist, name, SEARCH_OK);
	    SetBusy(viewer, False);
		FreeNameList(&namelist);
		return True;
		}

	freeNameSearchChain(nchain);
	nchain=NULL;
	nchain = addNameSearchRec(nchain, namelist->name,  NO_OF_SEARCH_OPTS-1);
	FreeNameList(&namelist);

	/* sort filmographies by year or alphabetically */
	if (app_data.alphaSort)
		options.yropt = NONE;	
	else
		options.yropt = YR;	
	options.biopt = TRUE;			/* include biography */
	options.chopt = TRUE;			/* include character names */
	options.substring = FALSE;
	options.akaopt = TRUE;			/* search under alias names also */

/*	The following movie-db options are not used (yet)...
	approx, mrropt, searchall, casesen, mvsonly */

	nchain->searchparams.searchall = True;
	addNameChainOpts(nchain, options);

	/* now search the database */
	search_in_progress=True;
	XtAppAddWorkProc(app_context, doNameSearch, nchain);
	return True;
	}

