/* 

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 movie title.

*/


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

#include "moviedb.h"
#include "dbutils.h"
#include "filmography.h"
#include "titlesearch.h"
#include "aka.h"
#include "plot.h"
#include "literature.h"
#include "business.h"
#include "laserdisc.h"
#include "movielinks.h"
#include "castcomp.h"
#include "titleinfo.h"
#include "ratings.h"
#include "years.h"
#include "trivia.h"
#include "searchtitle.h"

#include "printmovie.h"
#include "printmovies.h"
#include "history.h"
#include "popup.h"
#include "filmbuf.h"

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

#include "PcBar.h"
#include "LinkText.h"
#include "lookuptitle.h"
#include "titlelist.h"
#include "utility.h"
#include "xregal.h"



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

/* in titlesearch.c */ 
extern void titleResultsAttrKeyLookup(struct titleSearchRec *tchain);
extern void titleResultsNameKeyLookup(struct titleSearchRec *tchain);
extern void titleResultsTitleKeyLookup(struct titleSearchRec *tchain);
extern void lookupTitles( FILE *dbFp, FILE *indexFp, 
		struct titleSearchRec *trec, int listId);
extern void titleSearchKeyLookup(struct titleSearchRec *tchain);

/* in aka.c */
extern struct akaRec *readAkaSearchResults(FILE *dbFp, TitleID titleKey);

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

struct titleSearchRec *tchain=NULL;
struct titleSearchRec *tvchain=NULL;

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

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

#ifdef CRASHME
void crashme(void) {
	char line[MXLINELEN];
	char *ptr;
	static int count=0;
	static FILE *titleKeyFp=NULL;

	if (titleKeyFp==NULL)	
		titleKeyFp = openFile(TITLEKEY);

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

	count++;
	if (fgets ( line, MXLINELEN, titleKeyFp ) == NULL) 
		exit(0);
	ptr = strchr(line, FSEP);
	*ptr = '\0';
	printf("%d. %s\n", count, line);
	if (!LookupTitle(line, SEARCH_EXACT, TRUE))
		XtError("Title not found!");
  
	}
#endif

/* -----------------------------------------------------------------------*/
/*              Functions relating to movie title searches                */
/* -----------------------------------------------------------------------*/


/* 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 particular
   movie, returning after each lookup. The function returns True when it does
   not need to be called again. */

Boolean doTitleSearch(XtPointer client_data) {
	struct titleSearchRec *tchain = (struct titleSearchRec *) client_data;
	FILE  *dbFp = NULL, *indexFp = NULL;
	int   listId;
	struct titleSearchRec *trec;
	char fn[MAXPATHLEN];
	static int invocation=0;
	int invocations=NO_OF_FILMOGRAPHY_LISTS+15;
	char buf[256];
	char urlbuf[256];

	if (invocation==0) {
		sprintf(buf, "Looking up \"%s\"", tchain->title);
		SetTitle(buf);
		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);
		for (trec = tchain; trec!=NULL; trec = trec->next) {
			if (dbFp==NULL) {
				(void) constructFilename(fn, filmographyDefs[listId].stem, DBSEXT ) ;
				dbFp = openFile(fn);
				(void) constructFilename(fn, filmographyDefs[listId].stem, TDXEXT ) ;
				indexFp = openFile(fn);
				}
			lookupTitles(dbFp, indexFp, trec, 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);
		addYearsToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+2) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addRatingsToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+3) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addAkaToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+4) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addTriviaToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+5) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addTitleInfoToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+6) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addPlotToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+7) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addLiteratureToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+8) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addBusinessToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+9) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addLaserDiscToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+10) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addCastCompleteStatusToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+11) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addCrewCompleteStatusToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+12) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		addMovieLinksToTitleSearch(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+13) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		titleResultsAttrKeyLookup(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+14) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		titleResultsNameKeyLookup(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}
	if (invocation==NO_OF_FILMOGRAPHY_LISTS+15) {
		if (app_data.showprogress)
			PcBarSetPercentage(pcbar, (invocation*100)/invocations);
		titleResultsTitleKeyLookup(tchain);
		invocation++;
		if (app_data.showprogress) return False;
		}

	ClearFilmbuf();
	printMovie(tchain);

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

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

	invocation=0;
	search_in_progress=False;
	if (app_data.showprogress) 
		PcBarSetPercentage(pcbar, 0);

#ifdef CRASHME
	if (app_data.crashme>0)
		crashme();
#endif

	return True;
	}



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

void displayMultipleMovies(struct titleSearchRec *tchain, char *movietitle,
		int final_message) {
    struct titleSearchRec *trec ;
	struct akaRec *aka;
	char buf[1024];

	addAkaToTitleSearch(tchain);
	titleResultsTitleKeyLookup(tchain);
	titleResultsAttrKeyLookup(tchain);

	ClearFilmbuf();
  	trec=tchain; 
	while (trec!=NULL) {
		if (trec->titleKey != NOTITLE) {
			AddToFilmbuf2("<l MOV:", trec->title);
			AddToFilmbuf3(">", trec->title, "<e>\n");

			/* display a.k.a.'s */
			for (aka=trec->aka; aka!=NULL; aka=aka->next) {
				AddToFilmbuf2("            ... aka <f2>", aka->akaTitle);
				if (aka->akaAttr != NULL)
					AddToFilmbuf2("  ", aka->akaAttr);
				AddToFilmbuf("<f1>\n");
				}
			}
		if (trec!=NULL)
			trec = trec->next;
		}

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

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

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



/* for each movie in the list, find out about aliases */

void addAka(TitleRec *tchain) {
	FILE  *keyFp, *indexFp, *dbFp ;
	FILE  *attrKeyFp, *attrIndexFp;
	TitleRec *trec ;
	struct akaRec *arec;
	long mid = 0, lower, upper, saveUpper ;
	int found = FALSE ;
	TitleID titleKey, dbKey = NOTITLE ;

	dbFp = openFile ( AKADB ) ;
	indexFp = openFile ( TITLEIDX ) ;
	keyFp = openFile ( TITLEKEY ) ;
	attrIndexFp = openFile (ATTRIDX);
	attrKeyFp = openFile(ATTRKEY);

	(void) fseek(dbFp, 0, SEEK_END);
	saveUpper = ftell(dbFp) / 8;

	for ( trec = tchain ; trec != NULL ; trec = trec -> next ) {
		/* do a binary search to find a matching key */
		titleKey = trec -> titleKey ;
		lower = 0 ;
		upper = saveUpper ;
		found = FALSE ;
		while ( !found && upper >= lower ) {
		mid = ( upper + lower ) / 2 ;
		(void) fseek ( dbFp, mid * 8, SEEK_SET ) ;
		dbKey = getTitle ( dbFp ) ;
		if ( titleKey == dbKey )
			found = TRUE ;
		else if ( dbKey < titleKey )
			lower = mid + 1 ;
		else
			upper = mid - 1 ;
		}

		/* if a matching key was found, backtrack up the list to find the 
		   first matching key */
		if (found) {

			/* backtrack up the list to find the first matching key */
			if (mid == 0)
				(void) fseek ( dbFp, 0, SEEK_SET ) ;
			else {
				while (titleKey == dbKey) {
					if ( --mid >= 0 ) {
						(void) fseek ( dbFp, mid * 8, SEEK_SET ) ;
						dbKey = getTitle ( dbFp ) ;
						}
					else
						break ;
					}
				if ( mid >= 0 ) {
					(void) getTitle ( dbFp ) ;
					(void) getAttr ( dbFp ) ;
					}
				else
					(void) fseek ( dbFp, 0, SEEK_SET ) ;
				}

			/* now read all the matching keys */
			trec -> aka = readAkaSearchResults ( dbFp, titleKey ) ;

			/* read the aka titles */
			for (arec=trec->aka; arec!=NULL; arec=arec->next) {
				arec->akaTitle = mapTitleKeyToText(arec->akaKey, keyFp, indexFp);
				arec->akaAttr = mapAttrKeyToText(arec->akaAttrKey, attrKeyFp, attrIndexFp);
				}
			}
		}

	(void) fclose(dbFp);
	(void) fclose(indexFp);
	(void) fclose(keyFp);
	(void) fclose(attrIndexFp);
	(void) fclose(attrKeyFp);
	}




/* 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 file and returns True when it does
   not need to be called again. */

Boolean substringTitleSearch(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 *titleKeyFp;
	static TitleRec *head;
	static TitleRec *tail;
	static char searchtext[256];

	if (movie_substring_stage==0) {
		strcpy(searchtext, (char*) client_data);
		head=tail=NULL;
		count=0;
		titleKeyFp = openFile(TITLEKEY);
		fseek(titleKeyFp, 0, SEEK_END);
		upper=ftell(titleKeyFp);
		rewind(titleKeyFp);
		movie_substring_stage++;
		}

	if (movie_substring_stage==1) {
		while (fgets ( line, MXLINELEN, titleKeyFp ) != NULL) {

			/* update display */
			pcdone=ftell(titleKeyFp)*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) {
				if (count == 0) {
					head = newTitleRec();
					head->title = duplicateString(line);
					head->titleKey = strtol(++ptr, (char **) NULL, 16);
					count++;
					tail=head;
					}
				else {
					tail->next = newTitleRec();
					tail = tail->next;
					tail->title = duplicateString(line);
					tail->titleKey = strtol( ++ptr, (char **) NULL, 16);
					count++ ;
					}
				}
				if (count>=app_data.substringMax)
					break;
			}
  
		fclose(titleKeyFp);
		movie_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);
		movie_substring_stage=0;
		search_in_progress=False;
		return True;
		}

	if (ts_popped_up) {
		XtPopdown(ts_shell);
		ts_popped_up=False;
		enableSearch();
		}

	if (count>=app_data.substringMax)
		final_message=SEARCH_OVERFLOW;
	else if (movie_substring_stage==-1)
		final_message=SEARCH_ABORTED;
	else
		final_message=SEARCH_OK;
	addAka(head);
	displayListOfMovies(head, searchtext, final_message);
    SetBusy(viewer, False);
	movie_substring_stage=0;
	search_in_progress=False;
	freeTitles(head);
	return True;
	}




/* Lookup a particlar movie title, and search for TV shows with the same name.
   Returns the number of matches found. */

int lookup_title_keys(char *movietitle, Boolean exact) {
	char titlebuf[256];
	int j;
	int matched;
	int trivopts[NO_OF_TRIV_LISTS];
	int titleInfoOpts[NO_OF_TITLE_INFO_LISTS];
	struct titleSearchOptRec options;
    struct titleSearchRec *trec;

	for (j=0; j<NO_OF_TRIV_LISTS; j++)
		trivopts[j] = TRUE;

    for (j=0; j<NO_OF_TITLE_INFO_LISTS; j++)
        titleInfoOpts[j] = TRUE;

	options.casesen = FALSE;
    options.akaopt = TRUE;		/* include any a.k.a. names */
	options.businessopt = TRUE; /* include business info */
	options.ldopt = app_data.laserDisc;		/* include laser disc info */
	options.chopt = FALSE;
    options.mrropt = TRUE;
	options.yropt = TRUE;		/* includes awards info */
	options.plotopt = TRUE;		/* include any plot summaries */
	options.substring = FALSE;
	options.yearMatch = TRUE;	/* match any year */
	options.litopt = TRUE;		/* include literature references */
	options . linkopt = TRUE ;	/* include any links with other movies */

	freeTitleChain (tchain);
	tchain=NULL;
	tvchain=NULL;
	tchain = addTitleSearchRec(tchain, movietitle);
	tchain->titleKey=NOTITLE;
    addTitleChainOpts(tchain, options);
    addTitleChainTrivOpts(tchain, trivopts);
    addTitleChainTitleInfoOpts(tchain, titleInfoOpts);

	titleSearchKeyLookup(tchain);

	/* search for TV shows of the same name */
	if ((*movietitle!='"') && !exact) {
		sprintf(titlebuf, "\"%s\"", movietitle);
		tvchain = addTitleSearchRec (tvchain, titlebuf);
		tvchain->titleKey=NOTITLE;
	    addTitleChainOpts(tvchain, options);
    	addTitleChainTrivOpts(tvchain, trivopts);
	    addTitleChainTitleInfoOpts(tvchain, titleInfoOpts);
		titleSearchKeyLookup(tvchain);
		
		/* link two chains together */
		if (tchain->titleKey==NOTITLE) {
			freeTitleChain(tchain);
			tchain=tvchain;
			}
		else {
			trec=tchain;
			while (trec->next != NULL)
				trec = trec->next;
			trec->next = tvchain;
			}
		}

	/* check for multiple matches */
	matched=0;
  	for (trec=tchain; trec!=NULL; trec=trec->next) {
		if (trec->titleKey != NOTITLE)
			matched++;
		}
	return matched;
	}



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

   If the search type is exact, the return value is True if a match
   was found, False otherwise.  If the search type is substring, the
   return value has no meaning as the actual searching will be done
   by a separate work procedure. */

Boolean LookupTitle(char *searchname, int searchtype, Boolean exact) {
	int len;
	int matched;
	char *cp;
	static char movietitle[256];
	char buf[256];
	char buf1[256];
	int tvseries;
	char *src, *dest;
	Boolean yeargiven;

	/* remove leading and trailing space, compress spaces, 
		change newlines to spaces */

	trim_text(movietitle, searchname);

	if (exact)
		yeargiven=True;
	else {
		len=strlen(movietitle);
		yeargiven = ((movietitle[len-1]==')') && (movietitle[len-6]=='(') && 
			(movietitle[len-7]==' '));
		}

	/* set the watch cursor so the user knows we're doing something */
    SetBusy(viewer, True);

	if (searchtype==SEARCH_SUBSTRING) {
		search_in_progress=True;
		XtAppAddWorkProc(app_context, substringTitleSearch, movietitle);
		return True;
		}

	matched = lookup_title_keys(movietitle, exact);

	/* if no matches were found, try putting the first word last - this will
	   enable searches for titles such as "The Client" to succeed */

	if (matched==0) {

		/* if we're searching for a TV series, strip out the double quotes 
		   first then reinsert them later */

		if (movietitle[0]=='"') {
			tvseries=1;
			dest=buf1;
			src=movietitle+1;
			while (*src != '\0') {
				if (*src != '"') 
					*dest++ = *src;
				src++;
				}
			*dest = '\0';
			}
		else {
			tvseries=0;
			strcpy(buf1, movietitle);
			}
		cp=strchr(buf1, ' ');
		if (cp!=NULL) {
			if (tvseries)
				strcpy(buf, "\"");
			else
				buf[0]='\0';
			if (yeargiven) {
				strncat(buf, cp+1, strlen(cp+1)-7);
				buf[strlen(cp+1)-7]='\0';
				strcat(buf, ", ");
				strncat(buf, buf1, cp-buf1);
				if (tvseries)
					strcat(buf, "\"");
				strcat(buf, cp+strlen(cp)-7);
				}
			else {
				strcat(buf, cp+1);
				strcat(buf, ", ");
				strncat(buf, buf1, cp-buf1);
				if (tvseries)
					strcat(buf, "\"");
				}
			matched = lookup_title_keys(buf, exact);
			}
		}

	/* was anything found? */
	if (matched==0) {
	    SetBusy(viewer, False);
		return False;
		}

	if (ts_popped_up) {
		XtPopdown(ts_shell);
		ts_popped_up=False;
		enableSearch();
		}

	if (matched>1) {
		displayMultipleMovies(tchain, movietitle, SEARCH_OK);
	    SetBusy(viewer, False);
		return True;
		}

	/* now search the database */
	search_in_progress=True;
	XtAppAddWorkProc(app_context, doTitleSearch, tchain);

	return True;
	}
