/* This file implements kanji 'radical' search widgets.
 * At one point, I intended to make my own radical list, with
 * my own definition of radicals
 * But I have wimped out and decided to go with the "standard" ones.
 *
 * This stuff takes advantage of the 'radkfile' file, included with
 * Jim Breen's xjdic program, and copied here with permission.
 * 
 */


#include <stdio.h>

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

#include <Shell.h>

#include <Xaw/Command.h>
#include <Xaw/Label.h>
#include <Xaw/Form.h>
#include <Xaw/Box.h>
#include <Xaw/AsciiText.h>
#include <cursorfont.h>

#include "defs.h"
#include "externs.h"
#include "game.h"    /* for GUESS_XXX defines */
#include "search.h"
#include "multikanji.h"
#include "searchwidgets.h"
#include "utils.h"
#include "init.h" /* for setup_deletewindow() */

Cursor pointercursor, busycursor;

Widget radical_popup;
Widget radicalinputform;

Widget radsearch_help; /* like a status bar */

int num_radicals=0; /* number of radicals read from file */

/*MAXRADICALS is defined in defs.h now */


XChar2b radicals[MAXRADICALS]; /* char for each radical */
XChar2b radical_array[MAXRADICALS][MAXKRADPOST]; /* kanji containing each rad*/
Widget radbuttons[MAXRADICALS];
CARD8 radtoggle[MAXRADICALS];   /* array to show if radical is selected*/

int numradsactive; /* number of radicals indicated by current select */
XChar2b activelist[MAXKRADPOST];
 /* activelist[] is the intersection of the kanji list from each
  * highlighted radical (eg: if its entry in radtoggle[] is set)
  */

static void setradstatus(char *s)
{
	XtVaSetValues(radsearch_help,XtNlabel,s,NULL);
}


/* Callback for 'clear' button
 * Note that we have to duplicate PART of functionality in here,
 * in subtractRadical()
 */
void ClearRadicals(Widget w, XtPointer data, XtPointer call_data)
{
	int rcount;
	for(rcount=0; rcount<num_radicals; rcount++){
		UnhighlightButton(radbuttons[rcount]);
		XtSetSensitive(radbuttons[rcount],True);
		radtoggle[rcount]=0;
	}
	numradsactive=0;
	printf("DEBUG: ClearRadicals clearing byte at %p\n",
	       &activelist[0].byte1);

	activelist[0].byte1=0;
	setradstatus("No radicals selected");
	ClearAllMulti();
	
}

/* given a radical indexnumber, 
 * take all kanji referenced by that radical and
 * intersect them with the 'active' list.
 *
 * If do_merge==1, make that intersection, the NEW 'active' list.
 *
 * return 1 if meaningful merge possible, or 0 if intersection set ==empty set
 */
static int mergeRadical(int radnum, int do_merge){
	XChar2b newactivelist[MAXKRADPOST];
	XChar2b searchstr[2];
	XChar2b *kradptr;
	int newcount=0;

	if(radnum>=num_radicals){
		puts("ERROR: mergeRadical passed invalid radnum");
		return 0;
	}

	kradptr=radical_array[radnum];
	/* kradptr now points to list of all kanji that use this radical*/


	while(kradptr->byte1!=0){
		if(kstrchr(activelist,*kradptr) != NULL){
			newactivelist[newcount++]=*kradptr;
		}
		kradptr++;
	}

	if(newcount>0){
		if(do_merge==0){
			/* we were just checking to see if it was POSSIBLE*/
			return 1;
		}
		newactivelist[newcount].byte1=0;
		newactivelist[newcount].byte2=0;
#ifdef DEBUG
		{
			XChar2b *tmpptr=activelist;
			int kcount=0;
			printf("kanji in old active list:\n  ");
			while(tmpptr->byte1!=0){
				printf("0x%2x%2x ",
				       tmpptr->byte1,tmpptr->byte2);
				tmpptr++;
				kcount++;
			}
			printf(" (kcount=%d)\n",kcount);
			
			tmpptr=&radical_array[radnum][0];
			kcount=0;
			printf("kanji in radical%d list:\n  ",radnum);
			while(tmpptr->byte1!=0){
				printf("0x%2x%2x ",
				       tmpptr->byte1,tmpptr->byte2);
				tmpptr++;
				kcount++;
			}
			printf(" (kcount=%d)\n",kcount);
		}
#endif

		printf("DEBUG: copying %d bytes from %p to %p\n",
		       (newcount+1)*sizeof(XChar2b),newactivelist,activelist);

		bcopy(newactivelist,activelist,(newcount+1)*sizeof(XChar2b));
#ifdef DEBUG
		{
			XChar2b *tmpptr=activelist;
			int kcount=0;
			printf("kanji in MERGED active list:\n  ");
			while(tmpptr->byte1!=0){
				printf("0x%2x%2x ",
				       tmpptr->byte1,tmpptr->byte2);
				tmpptr++;
				kcount++;
			}
			printf(" (kcount=%d)\n",kcount);
		}
#endif

		return 1;
	} else {
		return 0;
	}
}

/* Given a SINGLE kanji, look up all radicals that we know of, that
 * are used in it.
 * Length of radlist array should be MAXRADICALS+1,
 * although in reality, there probably wont be more than 5 radicals involved.
 */
void FindRadicals(XChar2b kanji, XChar2b *radlist){
	int rcount;
	XChar2b *radscan;
	for(rcount=0; rcount<MAXRADICALS;rcount++){
		radscan=radical_array[rcount];
		while(radscan->byte1 !=0){
			if((radscan->byte1==kanji.byte1) &&
			   (radscan->byte2==kanji.byte2)) {
				radlist->byte1=radicals[rcount].byte1;
				radlist->byte2=radicals[rcount].byte2;
				radlist++;
			}
			radscan++;
		}
	}
}


/* given a radical indexnumber, 
 * take all kanji referenced by that radical and add to 'active' list.
 * return 1 if okay, 0 if we tried to merge, and failed.
 */
static int addRadical(int radnum)
{
	int radcount;
	XChar2b *radptr;
	if((radnum>num_radicals)||(radnum<0)){
		puts("ERROR: addRadical passed invalid number");
		return 0;
	}

	if(numradsactive==0){
		bcopy(radical_array[radnum], activelist,
		       MAXKRADPOST*sizeof(XChar2b));
	} else {
		if(!mergeRadical(radnum, 1)){
			setradstatus("no combination possible");
			return 0;
		}
	}


	/* mask out buttons that no longer can be used */
	/* skip one that was just pressed, and any that are highlighted*/
	for(radcount=0; radcount <num_radicals; radcount++){
		if((radcount!=radnum)&&(radtoggle[radcount]==0)){
			if(!mergeRadical(radcount,0)){
				XtSetSensitive(radbuttons[radcount],False);
			} else {
				XtSetSensitive(radbuttons[radcount],True);
			}
		}
	}

	/* Now just tally up active list, and we're done */
	radptr=activelist; radcount=0;
	while(radptr->byte1!=0){
		radcount++;
		radptr++;
	}
	
	{
		char countbuf[50];
		sprintf(countbuf,"%d kanji possible",radcount);
		setradstatus(countbuf);
		numradsactive=radcount;
	}

	return 1;
}


/* Need to just recalculate intersection of all currently active
 * radicals, except the one passed in.
 * Make sure to update numradsactive
 * Logic in here must match ClearRadicals()
 */
static void subtractRadical(int radnum)
{
	int rcount,totalactive;

	radtoggle[radnum]=0;
	numradsactive=0;
	activelist[0].byte1=0;

	/* Kinda brute-force, but cant think of "elegant" way to do this */

	for(rcount=0,totalactive=0; rcount<num_radicals; rcount++){
		if(radtoggle[rcount]){
			addRadical(rcount);
			totalactive++;
		}
	}

	/*
	 * Note: addRadical() will have updated numradsactive for us
	 */

	if(totalactive==0){
		/* Hmm. we must have UNclicked the only used radical.*/
		/* This will ensure all buttons are set to active again */
		ClearRadicals(NULL,NULL,NULL);
	}

}


/* callback for when user clicks on an individual radical button */
void SelectRadical(Widget w, XtPointer data, XtPointer call_data)
{
	TRANSLATION kanjiptr;
	int rcount=0;


	while(rcount<num_radicals){
		if(w==radbuttons[rcount]){
			break;
		}
		rcount++;
	}
	if(rcount==num_radicals){
		puts("Error: unknown radicalbutton called SelectRadical?");
		return;
	}
#ifdef DEBUG
	printf("SelectRadical called by radical %d\n",rcount);
#endif

	/* Over slow links, or in debug mode, the operations
	 * after this can be veeerrryyyy sslllloooowwww...
	 */
	XtVaSetValues(radicalinputform, XtNcursor, busycursor,NULL);

	if(radtoggle[rcount]){
		UnhighlightButton(radbuttons[rcount]);
		subtractRadical(rcount);
	} else {
		if(!addRadical(rcount)){
			return;
		}
		HighlightButton(radbuttons[rcount]);
		radtoggle[rcount]=1;
	}


	XtVaSetValues(radicalinputform, XtNcursor, pointercursor,NULL);

	/* potentially, we could have a really looong active list.
	 * dont bother even trying to display, if longer than multilist
	 * would display anyway
	 */
	if(numradsactive>=getMultiMax()){
		return;
	}

	/* Now fill out the "multikanji" popup window, with an entry
	 * for each "active" kanji that is potentially a match with
	 * the radicals the user has selected so far.
	 */
	
	ClearAllMulti();

	for(rcount=0;rcount<numradsactive;rcount++){
		int kindex=(activelist[rcount].byte1 <<8) +
		            activelist[rcount].byte2;

		if((kindex <MINKANJIALLOWED) ||
		   (kindex >MAXKANJIALLOWED)){
			printf("SelectRadical: ERROR: kanji 0x%x in activelist out of range\n",
			       kindex);
			continue;
		} 

		kanjiptr=translations[kindex];
		if(kanjiptr==NULL){
			printf("SelectRadical: ERROR: no entry for kanji 0x%x (position %d)\n",
			       kindex, rcount);
		} else {
			AddMultiTranslation(kanjiptr);
		}
	}
	SetMultiMode(GUESS_KANJI);
	ShowMulti();
}



XChar2b fakelabel[2]={{0x24, 0x22}, {0x0, 0x0}};

/*
 * Internal routine, to create the buttons for the
 * kanji radical search popup.
 * Should have:
 *  -  Buttons for all 250 radicals
 *  -  a 'clear' button
 *  -  a 'search' button
 *  -  a status label.
 *
 */
void makeradicalinput(Widget parent)
{
	Widget radform,bottomform;
	Widget clearbutton;
	int radcount;
	XChar2b buttonlabel[2];
	buttonlabel[1].byte1=0;
	buttonlabel[1].byte2=0;

	radform=XtVaCreateManagedWidget("radinputform", boxWidgetClass,parent,
		XtNwidth,600,
			XtNleft,XawChainLeft,
			XtNright,XawChainRight,
			XtNtop, XawChainTop,
			XtNbottom, XawChainBottom,
		NULL);

	for(radcount=0;radcount<num_radicals;radcount++){
		char radbname[10];
		sprintf(radbname,"radb%d\n",radcount);
		buttonlabel[0].byte1=radicals[radcount].byte1;
		buttonlabel[0].byte2=radicals[radcount].byte2;
		radbuttons[radcount]=XtVaCreateManagedWidget(radbname,
		        commandWidgetClass, radform,
			XtNlabel,buttonlabel,
			XtNencoding, XawTextEncodingChar2b,
			XtNfont, smallkfont,
		        NULL);

		XtAddCallback(radbuttons[radcount], XtNcallback,
				      SelectRadical, NULL);
	}

	bottomform=XtVaCreateManagedWidget("radcommandform", formWidgetClass,
		parent,
		XtNfromVert,radform,
		XtNborderWidth,0,
			XtNleft,XawChainLeft,
			XtNright,XawChainRight,
			XtNtop, XawChainBottom,
			XtNbottom, XawChainBottom,
		NULL);

	clearbutton=XtVaCreateManagedWidget("radclear",
	        commandWidgetClass, bottomform,
		XtNlabel,"clear",
		XtNleft, XawChainLeft,
		XtNright, XawChainLeft,
	        NULL);

	radsearch_help=XtVaCreateManagedWidget("radsearchhelp",
		labelWidgetClass, bottomform,
		XtNlabel,"                 Radical Search                   ",
		XtNfromHoriz,clearbutton,
		XtNleft, XawChainRight,
		XtNright, XawChainRight,
	        NULL);
	
	XtAddCallback(clearbutton, XtNcallback,
				      ClearRadicals, NULL);

}

/* Exported routine */
/* Lets other functions know if radical search is available or not.
 * (just in case the radkfile is not present)
 */ 
int HaveRadicals()
{
	if(num_radicals>0) return 1;
	return 0;
}

/* exported routine */
/* Call HaveRadicals() if you want to know if it
 * succeeded or not
 */
void InitRadicals()
{
#define RADBUFLEN 8192

	/* I'm not particularly proud of the internals of this thing.
	 * Unfortunately, the file format doesnt easily lend itself
	 * to a better approach, that I can think of.
	 * Oh well, memory is a lot cheaper these days. Sigh.
	 */
	char radfile[MAXLINELEN]; /* path to "radkfile" */
	CARD8 linebuf[RADBUFLEN];
	int cur_radnum=-1;
	int kradpos=0;
	FILE *radfp;
	
	GetXtrmString("radkfile","Radkfile",radfile);

	numradsactive=0;
	bzero(radical_array,MAXRADICALS*MAXKRADPOST*sizeof(XChar2b));
	bzero(radtoggle,MAXRADICALS);

	radfp=fopen(radfile,"r");
	if(radfp==NULL){
		num_radicals=0;
		return;
	}
	printf("Opened radfile %s\n",radfile);

	while(cur_radnum<MAXRADICALS){
		int linepos=0;
		if(fgets((void*)linebuf, RADBUFLEN, radfp)==NULL){
			break;
		}
		if(linebuf[0]=='#'){
			continue;
		}
		if(linebuf[0]=='$'){
			cur_radnum++;
			kradpos=0;

			/* save the kanji that we want to display,
			 * for the button representing radical #{cur_radnum}
			 */
			radicals[cur_radnum].byte1=linebuf[2]&0x7f;
			radicals[cur_radnum].byte2=linebuf[3]&0x7f;

			continue;
		}
		/* otherwise, must be radical list line.
		 * A list of kanji that match the current radical.
		 * Except if file is corrupted, of course
		 * Note that linepos gets incremented TWICE.
		 * But, unless file gets corrupted, we should never get
		 * anywhere near RADBUFLEN
		 */

		linepos=0;
		for(; linepos<RADBUFLEN && linebuf[linepos]!='\n'; linepos+=2) {
			radical_array[cur_radnum][kradpos].byte1=
			      linebuf[linepos]&0x7f;
			radical_array[cur_radnum][kradpos].byte2=
			      linebuf[linepos+1]&0x7f;
			kradpos++;
		}
		if(linepos >= RADBUFLEN){
			fprintf(stderr,"ERROR: radkfile %s corrupted!\n",
				radfile);
			printf("linepos=%d,kradpos=%d\n",linepos,kradpos);
			exit(1);
		}
	}

	if(cur_radnum==MAXRADICALS){
		fprintf(stderr,"ERROR:InitRadicals exceeded MAXRADICALS\n");
		/* Need to increase the #define */
		num_radicals=0;
		return;
	}
	num_radicals=cur_radnum;

	printf("%d radicals read from radkfile\n",num_radicals);

}


/* exported routine */
void MakeRadicalinputPopup()
{
	if(num_radicals==0) {
		return;
	}

	pointercursor=XCreateFontCursor(display, XC_X_cursor);
	busycursor=XCreateFontCursor(display, XC_clock);


	radical_popup = XtVaCreatePopupShell("kdrill_radicalsearch ",
		topLevelShellWidgetClass,
		search_popup,
		NULL);

	radicalinputform = XtVaCreateManagedWidget("radicalinputform",
					     formWidgetClass,
					     radical_popup,
			XtNleft,XawChainLeft,
			XtNright,XawChainRight,
			XtNtop, XawChainTop,
			XtNbottom, XawChainBottom,
					     NULL);

	/* make all the substuff */
	makeradicalinput(radicalinputform);

}

/*
 * Exported routine:
 * 
 * It pops up the kanji radical search window
 */
void ShowRadicalinput(Widget w,XtPointer client_data, XtPointer call_data)
{
	static int radicals_up = -1;

	static Position rel_x,rel_y;
	Position x,y;

	if(num_radicals==0){
		setstatus("ERROR: Radical file radkfile not present");
		return;
	}

	if(radicals_up==-1){
		/* first time init.. */
		/*rel_x = GetXtNumber("radical_popupx","Search_popupx");*/
		/*rel_y = GetXtNumber("radical_popupy","Search_popupy");*/
		rel_x = 10;
		rel_y = 10;
		radicals_up=0;
	}
	if(isMapped(radical_popup)==False){
		XtTranslateCoords(search_popup,rel_x,rel_y,&x,&y);
		XtVaSetValues(radical_popup,
		      XtNx,x,
		      XtNy,y,
		      NULL);
		XtPopup(radical_popup,XtGrabNone);
		if(radicals_up==0){
			setup_deletewindow(radical_popup);
			radicals_up=1;
		}
		
		setstatus("Bringing up radical input window...");
	} else {
		XtPopdown(radical_popup);
	}
}


