/* I'm changing this so that only the actual SEARCH routines are in here. 
 * widget creation has been moved to "searchwidgets.c"
 */

#include <stdio.h>

#include <Xos.h>
#include <Intrinsic.h>
#include <StringDefs.h>

#include <Shell.h>



#include "defs.h"
#include "externs.h"
#include "game.h"
#include "widgets.h"
#include "search.h"
#include "convert.h"
#include "multikanji.h"
#include "strokesearch.h"
#include "readfile.h"
#include "utils.h"
#include "log.h"

#include "searchwidgets.h"


TRANSLATION lastsearch=NULL;	/* holds index of last kanji searched */

/* called from DoStrokeSearch()
 * Go look for all kanji with this strokecount.
 * 
 */
void dostrokefind(BYTE strokecount, BITS16 skipval)
{
	short skipmask=skipfromthree(3,0,0);
	TRANSLATION searchtarget = NULL;
	TRANSLATION firstmatch = NULL;
	int searchndx=lowestkanji;

	if(strokecount==0)
		return;

#ifdef DEBUG
	printf("searching for strokecount %d\n",strokecount);
#endif

	setstatus("Searching...");

	ClearAllMulti();

	for(searchndx=lowestkanji; searchndx <=highestkanji; searchndx++){
		searchtarget=translations[searchndx];
		if(searchtarget==NULL) continue;

		if(searchtarget->Strokecount != strokecount) {
			continue;
		}

		if(skipval!=0)
			if((searchtarget->Sindex & skipmask) != skipval){
				continue;
			}

		if(match_onlyactive){
			if(!UseThisKanji(searchndx)) {
				continue;
			}
		}
		
		AddMultiTranslation(searchtarget);
		if(firstmatch==NULL)
			firstmatch=searchtarget;

	}

	if(firstmatch!=NULL){
		setstatus("Search complete");
		if(getMultiCount() >1){
			ShowMulti();
		}
	} else{
		setstatus("No match found");
		Beep();
	}
	
}

/* called by doenglishfind, if we need to restrict search to
 * "beginning of word only"
 * return 1 on match, 0 on not.
 */
int matchengfirst(char *full, char *frag){
	int len=strlen(frag);
	while(1){
		if(strncmp(full,frag,len) ==0){
			return 1;
		}
		full=strchr(full,':'); /* actually, " : " */
		if(full==NULL){
			return 0;
		}
		full+=1;
	}
	return 0;
}

/* doenglishfind:
 *	Assume what we want to look for is in the search ENGLISH INPUT widget.
 *	First, read in that value from the widget
 *	(okay, this is not modular :-)
 *
 *	Then go through the whole dictionary looking for matches,
 *	and adding any matches to the multi window.
 *	We show the multiwindow, if appropriate
 *	We return the first match, if any.
 *	But we do not set the main search window, for some reason.
 */
TRANSLATION doenglishfind()
{
	String str;
	TRANSLATION searchtarget = NULL;
	TRANSLATION firstmatch = NULL;
	int searchndx=lowestkanji;

	setstatus("Searching...");

	XtVaGetValues(searchwidgets[SEARCH_ENG_W],XtNstring,&str,NULL);

	if((str[0] == '\0') || (str[0] == '\n')){
		return NULL;
	}

	ClearAllMulti();
	searchtarget=translations[lowestkanji];



	for(searchndx=lowestkanji;searchndx<=highestkanji;searchndx++){
		searchtarget=translations[searchndx];
		if(searchtarget==NULL) continue;

		if(match_onlyatstart){
			if(matchengfirst(searchtarget->english,str) != 1){
				continue;
			}
		} else {
			if(strstr(searchtarget->english,str) == NULL){
				continue;
			}
		}

		if(match_onlyactive){
			if(!UseThisKanji(searchndx)){
				continue;
			}
		}
			
		AddMultiTranslation(searchtarget);
		if(firstmatch==NULL){
			firstmatch=searchtarget;
		}
	}



	if(firstmatch!=NULL){
		setstatus("Search complete");
		if(getMultiCount() >1){
			ShowMulti();
		}
	} else{
		setstatus("No match found");
		Beep();
	}

	return firstmatch;
	
}

/********************************************************************
 * suite of really stupid-looking routines, so I can do INTELLIGENT *
 * generic-comparisons in DoFind, and posibly elsewhere             *
 ********************************************************************/

Boolean compareUnicode(TRANSLATION entry, int compvalue){
	if(entry->Uindex==compvalue) return True;
	return False;
}
Boolean compareHalpern(TRANSLATION entry, int compvalue){
	if(entry->Hindex==compvalue) return True;
	return False;
}
Boolean compareNelson(TRANSLATION entry, int compvalue){
	if(entry->Nindex==compvalue) return True;
	return False;
}
Boolean compareFreq(TRANSLATION entry, int compvalue){
	if(entry->frequency==compvalue) return True;
	return False;
}

/************************************************************/


/* DoFind
 *   (do-find)accelerator hook
 *   This handles callbacks for multiple widgets.
 *   Basically, any inputfield on the Search window, that uses
 *   [ENTER] as a trigger for a search.
 *   Except for the kanasearch
 */
void 
DoFind(Widget w,XEvent *event,String *params,Cardinal *num_parags){
	TRANSLATION findtarget = NULL;
	int targetval=0, retval=0, ndx=0;
	Boolean (* comparefunc)(TRANSLATION,int);

	setstatus("Searching...");

	if(w == searchwidgets[SEARCH_ENG_W]){
		findtarget = doenglishfind();

		if(findtarget == NULL){
			setstatus("No match found");
			Beep();
			printsearch(lastsearch);
		} else {
			printsearch(findtarget);
		}
		return;
	}

	/* Special case: not a "find this index", but a
	 * "find me this index or greater"
	 */
	if (w == searchnumbers[POUND_INPUT]){
		int number;
		/* want closest index match */
		number = GetWidgetHexval(w);

		if((number <lowestkanji) || (number> highestkanji)){
			setstatus("input out of dict. range");
			Beep();
			printsearch(lastsearch);
			return;
		} 

		if(number > highestkanji){
			number = highestkanji;
		} else if (number <lowestkanji){
			number = lowestkanji;
		} else {
			/* find closest non-blank */
			while(translations[number] == NULL){
				number++;
				/* this should never be triggered, but... */
				if(number >highestkanji)
					number = lowestkanji;
			}
		}

		findtarget = translations[number];

		printsearch(findtarget);

		setstatus("Search complete");
		return;
	}


	if(w==searchnumbers[U_INPUT]) {
		targetval=GetWidgetHexval(w);
	} else {
		targetval=GetWidgetNumberval(w);
	}

	if(w==searchnumbers[U_INPUT]){
		comparefunc=compareUnicode;
	} else if (w == searchnumbers[F_INPUT]) {
		comparefunc=compareFreq;
	} else if (w == searchnumbers[H_INPUT]) {
		comparefunc=compareHalpern;
	} else if (w == searchnumbers[N_INPUT]) {
		comparefunc=compareNelson;
	} else {
		puts("DEBUG: ERROR: unrecognized compare type in DoFind");
		return ;
	}

	for(ndx=lowestkanji;ndx<highestkanji;ndx++){
		if(translations[ndx]==NULL)
			continue;
		if(comparefunc(translations[ndx],targetval)){
			retval=ndx;
			break;
		}
	}


	if(retval!=0){
		findtarget = translations[retval];
		printsearch(findtarget);
		setstatus("Displaying match");
	} else {
		setstatus("No match found");
		Beep();
	}

	

}



/* Finds a match for single UNICODE-encoded widechar.
 * Returns pointer to translation struct, or NULL if no match.
 */
TRANSLATION dounicodefind(XChar2b *target)
{
	/* The guts of this are modeled after the special-case for xU,
	 * in DoFind()
	 */

	int ndx, retval=0;
	int targetval=(target->byte1 <<8) | target->byte2;

	for(ndx=lowestkanji;ndx<highestkanji;ndx++){
		if(translations[ndx]==NULL)
			continue;
		if(compareUnicode(translations[ndx],targetval)){
			retval=ndx;
			break;
		}
	}
	if(retval!=0){
		return translations[retval];
	} else {
		return NULL;
	}
}


/* pass in unicode string.
 * look for match. display if found.
 * Fancy stuff;
 *   If single-char, just looks it up in kanjidic.
 *
 *   If multi-char, then attempts to look up each char in kanjidic,
 *   to convert unicode to JIS, THEN does search on JIS string. Wheee!
 */
void findunicodestring(XChar2b *target)
{
	XChar2b jisstring[5];
	TRANSLATION umatch;
	bzero(jisstring, sizeof(XChar2b) * 5);

	if(target==NULL) return;

	umatch=dounicodefind(target);

	if(umatch==NULL){
		setstatus("No match found");
		Beep();
		return;
	}

	/* only a single-char long */
	if(target[1].byte1==0){
		printsearch(umatch);
		setstatus("Displaying match");
		return;
	}

	jisstring[0].byte1=umatch->kanji->byte1;
	jisstring[0].byte2=umatch->kanji->byte2;

	umatch=dounicodefind(&target[1]);
	if(umatch==NULL){
		/* allow for a bit of mouse overswipe on a line */
		/* setstatus("Displaying fragment match"); */
		findkanjiall(jisstring);
		return;
	}
	jisstring[1].byte1=umatch->kanji->byte1;
	jisstring[1].byte2=umatch->kanji->byte2;

	if(target[2].byte1){
		umatch=dounicodefind(&target[2]);
		if(umatch!=NULL){
			jisstring[2].byte1=umatch->kanji->byte1;
			jisstring[2].byte2=umatch->kanji->byte2;
		}
	}

	findkanjiall(jisstring);
	
}


/* if fragment matches FIRST chars of fullstring, return 1.
 * Otherwise, return  0
 *   Allow hiragana/kanakana folding.
 */
int matchkanafirst(XChar2b *fullstring, XChar2b *fragment)
{
	while(1){
		if(fragment->byte1==0)
			return 1;

		/* no need to test for fullstring->byte1==0,
		 * it will just fail match :-)
		 * Although if we were nice, we could return a value
		 * saying the thing is too short
		 */

		/* theres almost no point to this. sigh.*/
		if(fullstring->byte1 != fragment->byte1){
			if((fullstring->byte1 != 0x24) &&
			   (fullstring->byte1 != 0x25))
				return 0;
			if((fragment->byte1 != 0x24) &&
			   (fragment->byte1 != 0x25))
				return 0;
		}

		if(fullstring->byte2 != fragment->byte2)
			return 0;

		fragment++;
		fullstring++;
	}
}

/* match XChar2b
 *	Attempt to find occurence of fragment in xchar2b string
 *	return 1 on match, 0 on fail.
 *
 *	Note that we try to allow match of kanagana, by hiragana.
 *	If match_onlyatstart!=0, only match if start of a kana phrase
 *	matches fragment
 */
int matchkana(XChar2b *fullstring, XChar2b *fragment)
{

	if(match_onlyatstart){
		while(fullstring->byte1 != 0){
			if(matchkanafirst(fullstring,fragment))
				return 1;

			/* Oh well.... Find start of next "word".
			 * First skip over "current" word, then
			 * then look for valid kanji char.
			 * Remember, we need to skip space, AND things
			 * like parenthesis
			 */

			/* Look for space, explicitly */
			while(fullstring->byte1 != 0){
				if((fullstring->byte1==0x21) &&
				   (fullstring->byte2==0x21)){
					break;
				}
				fullstring++;
			}
			/* now have either space, or end-of-string */
			/* so look for begining of word */
			while(fullstring->byte1 < 0x24){
				if(fullstring->byte1 ==0)
					break;
				fullstring++;
			}
			
			if(fullstring->byte1 ==0){
				/* oh well, end of string hit */
				break;
			}

			/* We must be in the kana-and-above range now.
			 * just loop and search again!
			 */
		}

		return 0;
	}

	/* else */

	while(fullstring->byte1 != 0){
		if(matchkanafirst(fullstring,fragment))
			return 1;
		fullstring++;

	}
	return 0;

}


/*
 *	Equivalent to matchkana!
 *	return 1 if match, 0 if no match
 *
 *	Note that this takes intoaccount 'match_onlyatstart'
 *	Which will presumably only match the kanji if at beginning
 *	of the phrase. Hmmm.
 */
int matchkanji(XChar2b *fullstring, XChar2b *fragment)
{
	return matchkana(fullstring,fragment);
}

/*
 * a kanji equivalent to strchr()
 * Returns pointer to first place the single widechar kchar is found in
 * kanji string, or NULL if no match
 */
XChar2b * kstrchr(XChar2b *string, XChar2b kchar)
{
	while(string->byte1 !=0){
		if((kchar.byte1==string->byte1) &&
		   (kchar.byte2==string->byte2))
		{
			return string;
		}
		string++;
	}

	return NULL;
}



/* dokanafind()
 *	find match to XChar2b string in kana translations
 */

void dokanafind(XChar2b *target)
{
	TRANSLATION firstmatch = NULL;
	TRANSLATION searchtarget = NULL;
	int searchndx=lowestkanji;

	setstatus("Searching...");

	searchtarget=translations[lowestkanji];
	ClearAllMulti();

	for(searchndx=lowestkanji; searchndx <=highestkanji; searchndx++){
	
		searchtarget=translations[searchndx];
		if(searchtarget==NULL) continue;
		if(match_onlyactive){
			if(!UseThisKanji(searchndx)) {
				continue;
			}
		}

		if(matchkana(searchtarget->pronunciation, target)){
			AddMultiTranslation(searchtarget);
			if(firstmatch==NULL)
				firstmatch=searchtarget;
		}
	}

	if(firstmatch!=NULL){
		printsearch(firstmatch);
		setstatus("Search complete");
		if(getMultiCount() >1){
			ShowMulti();
		}
	} else{
		setstatus("No match found");
		Beep();
	}

	return;
}


/* This is here, because I want to preserve all "search" type
 * routines together in this file.
 * But it is called by Handle_showusefile
 *
 * The purpose is to let the user easily see which chars are in the
 * usefile, and easily remove them. They already have an easy way
 * to ADD them, via the search window :-)
 */
void dousefilefind()
{
	/*int kcount=getMultiMax();*/
	int kindex=lowestkanji;

	ClearAllMulti();
	for(kindex=lowestkanji; kindex<=highestkanji;kindex++){
		if(InUsefile(kindex)){
			AddMultiTranslation(translations[kindex]);

			/*
			 * let the multi-window complain about
			 * truncation, if appropriate
			if(--kcount <=0){
				return;
			}
			*/
		}
	}

	ShowMulti();
}

/* dokanjifind()
 *	find match to "Four Corner" encoding in kanjidic translations
 */

void dokanjifind(int target)
{
	TRANSLATION firstmatch = NULL;
	TRANSLATION searchtarget = NULL;
	int searchndx=lowestkanji;

	setstatus("Searching...");

	ClearAllMulti();

	for(searchndx=lowestkanji; searchndx <=highestkanji; searchndx++){
		searchtarget=translations[searchndx];
		if(searchtarget==NULL) continue;

		if(searchtarget->Qindex == target){
			if(match_onlyactive){
				if(!UseThisKanji(searchndx)) {
					continue;
				}
			}

			AddMultiTranslation(searchtarget);
			if(firstmatch==NULL)
				firstmatch=searchtarget;
		}

	}

	if(firstmatch!=NULL){
		printsearch(firstmatch);
		setstatus("Search complete");
		if(getMultiCount() >1){
			ShowMulti();
		}
	} else{
		setstatus("No match found");
		Beep();
	}

	return;
}

/* doskipfind()
 *	find match to "SKIP" encoding in kanjidic translations
 *	Indentical to dokanjifind, except look at Qindex instead of Sindex
 */

void doskipfind(int target)
{
	TRANSLATION firstmatch = NULL;
	TRANSLATION searchtarget = NULL;
	int searchndx=lowestkanji;

	setstatus("Searching...");

	ClearAllMulti();

	for(searchndx=lowestkanji; searchndx <=highestkanji; searchndx++){
		searchtarget=translations[searchndx];
		if(searchtarget==NULL) continue;

		if(searchtarget->Sindex == target){
			if(match_onlyactive){
				if(!UseThisKanji(searchndx)) {
					continue;
				}
			}

			AddMultiTranslation(searchtarget);
			if(firstmatch==NULL)
				firstmatch=searchtarget;
		}

	}

	if(firstmatch!=NULL){
		setstatus("Search complete");
		printsearch(firstmatch);
		if(getMultiCount() >1){
			ShowMulti();
		}
	} else{
		setstatus("No match found");
		Beep();
	}

	return;
}


/* process_kinput
 *	Accepts a single 2-byte char as input.
 *	We assume this is hiragana, katakana, or special-directive char.
 *
 *	Adjusts the SEARCH_KANA_W widget labelstring appropriately
 *	(allowing backspacing)
 *	Also handles the mirrored labelwidget on the popup window itself
 *
 *	We call the convert routine "romajitokana" on our internal
 *	string, to handle romaji-to-kana stuffs.
 *	Note that we get called by both the point-n-click-kana window,
 *	AND by the kepress-event handler for the window.
 */
void process_kinput(XChar2b in_char)
{
	/* 'kanastring' is a GLOBAL BUFFER!! */
	XChar2b *kparse = kanastring;
	int kanalength;

	for(kanalength=0; kparse->byte1 != 0; kanalength++){
		kparse++;
	}

	/********************************************/
	/* Handle special embedded directives first */
	/********************************************/
	/* backspace */
	if((in_char.byte1 == 0x22) && (in_char.byte2 == 0x2b) ) {
		if(kanalength==0)
			return;
		kanalength--;
		kanastring[kanalength].byte1 = 0;
		kanastring[kanalength].byte2 = 0;

		XtVaSetValues(searchwidgets[SEARCH_KANA_W],
		      XtNlabel, kanastring, NULL);
		XtVaSetValues(romajiinput,
		      XtNlabel, kanastring, NULL);

		return;
	
	}
	/* 'paragraph' means "do search now" */
	if((in_char.byte1 == paragraphglyph[0].byte1) &&
	   (in_char.byte2 == paragraphglyph[0].byte2))
	{
		if(kanalength == 0)
			return;
		/* accept.. search! */
		dokanafind(kanastring);
		return;
	}

	/* Done with special directives. Anything else is meant
	 * to be displayed
	 */

	if(kanalength<MAXKANALENGTH-1) {
		kanastring[kanalength].byte1 = in_char.byte1;
		kanastring[kanalength].byte2 = in_char.byte2;

		kanalength++;
		kanastring[kanalength].byte1 = 0;
		kanastring[kanalength].byte1 = 0;
	}

	/* See if we have enough romaji to convert to kana, before display */
	romajitokana(kanastring);

	XtVaSetValues(searchwidgets[SEARCH_KANA_W],
		      XtNlabel, kanastring, NULL);
	XtVaSetValues(romajiinput,
		      XtNlabel, kanastring, NULL);
	
}


void debugkprint(XChar2b* kstr){
	XChar2b *ptr=kstr;
	while(ptr->byte1 != 0){
		printf("%x%x",ptr[0].byte1,ptr[0].byte2);
		ptr++;
	}
	printf("\n");
}

/* Take the "current search" string, and split it up into separate
 * kanji, if it is longer than 1 kanji
 */
void split_kanji_search()
{
	XChar2b *kanjistring;
	if(lastsearch==NULL){
		return;
	}
	kanjistring=lastsearch->kanji;
	if(kanjistring[1].byte1==0){
		return;
	}
	/* Okay, we now have non-null string, of at least 2 chars in length*/
	ClearAllMulti();
	while(kanjistring->byte1!=0){
		/* kanji start at 0x3021 */
		if(kanjistring->byte1>=0x30){
			int knum=(kanjistring->byte1 <<8) | kanjistring->byte2;
			TRANSLATION tptr;
			if(knum >highestkanji){
#ifdef DEBUG
				puts("ERR: cant find kanji 0x%d\n",knum);
#endif
				continue; /* error */
			}
			tptr=translations[knum];
			AddMultiTranslation(translations[knum]);
		}
		kanjistring++;
	}

	ShowMulti();
}


/****************************************************************\
 * Find matches for a kanji 'string'                            *
 *   (used for cut-n-paste, and "match kanji" button)           *
 * If match found, will take care of calling ShowMulti()	*
\****************************************************************/
void findkanjiall(XChar2b *Schars)
{
	int foundone=0;
	TRANSLATION searchparse=NULL;
	int searchndx=0;
	int min_ndx=lowestkanji,max_ndx=highestkanji;

	if(Schars==NULL){
		puts("Paranoid coding is A Good Thing: findkanjiall passed NULL");
		return;
	}
	if(Schars->byte1==0){
		puts("Paranoid coding is A Good Thing: findkanjiall passed zerolen string");
		return;
	}

	/* if match_active on, and high levels not enabled, dont
	 * bother searching through edict lines
	 */
	if(match_onlyactive &&  ((gradelevelflags & ABOVESIXGRADE)==0)) {
		max_ndx=MAXKANJIALLOWED;
	}

	if(Schars[1].byte1!=0){
		/* We have multi-kanji string.
		 * It is now impossible to match single-kanji range,
		 * so start at edict range
		 */
		min_ndx=MAXKANJIALLOWED;
	}

	for(searchndx=min_ndx; searchndx <=max_ndx; searchndx++){
		searchparse=translations[searchndx];
		if(searchparse==NULL) continue;

		if(match_onlyactive){
			if(!UseThisKanji(searchndx)) {
				continue;
			}
		}
		
		if(matchkanji(searchparse->kanji,Schars)){
			if(foundone==0) {
				ClearAllMulti();
				foundone=1;
				printsearch(searchparse);
			}
			AddMultiTranslation(searchparse);
		}
		searchparse=searchparse->nextk;
	}

	if(foundone>0) {
		setstatus("Displaying search result");
		/* Search window already updated. So only show "multi"
		 * window, if there are actual multiple matches
		 */
		if(getMultiCount() >1){
			ShowMulti();
		}
	} else {
		setstatus("no match found");
	}
	
}
