/* game.c:
 *	This file should contail all the internal game mechanisms;
 *	things that deal with the way things play, not how the
 *	windows look.
 */
 

#include <stdio.h>
#include <stdlib.h>	/* This SHOULD define lrand48..
			 *	but doesn't for solaris. go figure
			 */
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <Intrinsic.h>
#include <StringDefs.h>
#include <Xaw/Command.h>

#include "defs.h"
#include "externs.h"
#include "widgets.h"
#include "grades.h"
#include "init.h"
#include "game.h"
#include "convert.h"
#include "badguess.h"
#include "utils.h"
#include "log.h"

#include "timeout.h"

TRANSLATION lastpicked=NULL;/* we can't "really" auto-initialize this...
		  * set to zero so we can set it
		  * properly later
		  */
TRANSLATION values[NUMBEROFCHOICES];
int truevalue=0;
int numberincorrect=0;
int totalguessed=0, totalmissed=0;
/* "numberincorrect" is total number of UNIQUE kanji missed.
 * "totalmissed" is number of times player clicked wrong.
 * "totalguessed" is total number of guesses, right or wrong.
 * The "total" values are only set in the function guessvalue()
 */

int choicesmode=GUESS_ENGLISH;
int questionmode=GUESS_KANJI;
int romajiswitch=0;	/* fake kana with romaji? */

Boolean doBell,showinorder;
Boolean useUsefile;
Boolean switchKanaEnglish;


int englishwidth=7;	/* unused? */
int englishheight=11;	/* unused? */

void SetupGuess();

/*
*UseThisKanji()
*	Returns Boolean value on whether this is a "valid" kanji for us,
*	based on usefile markings, current grades allowed,
*	and whether it has english/kana translation available
*/
Boolean UseThisKanji(int kanjinum)
{
	TRANSLATION kanji = translations[kanjinum];

	if(useUsefile){
		if(InUsefile(kanjinum) == False){
			return False;
		}
	}
	
	/* nonexistant kanji? */
	if(kanji == NULL) return False;


	/* check against frequency limits */
	if(kanji->frequency==0) {
		/* No frequency means REALLY LOW frequency,
		 * therefore if there's ANY limit, dont use
		 */
		if(lowfrequency != 0)
			return False;
	} else {
		/* "1" is highest frequency!! */
		if(kanji->frequency < highfrequency) return False;
	}

	if(lowfrequency != 0){
		if(kanji->frequency >lowfrequency)
			return False;
	}

	/* does the appropriate form to display exist? */
	if( switchKanaEnglish || (choicesmode != GUESS_KANA)){
		if(kanji->english == NULL)
			return False;
		if(kanji->english[0] == '\0')
			return False;
	}
	if( switchKanaEnglish || (choicesmode == GUESS_KANA)) {
		/* we're supposed to be showing kana.. are there any? */
		if(kanji->pronunciation == NULL)
			return False;
	}

	/* only thing left is to check grade level */
	switch(kanji->grade_level){
		case 1: case 2:
		case 3: case 4:
		case 5: case 6:
			if(gradelevelflags & (1 <<kanji->grade_level) ){
				return True;
			} else {
				return False;				
			}
		default:
			if(gradelevelflags & (1 <<7) ){
				return True;
			} else {
				return False;
			}
	}
}


/* CountKanji
 *This routine gets called a hell of a lot:
 *	When we change grade level, and
 *	when we change kana/english display.
 *	 (the secnd being because kanjidic does not always have
 *	 english and/or kana listings
 *
 *   This counts the number of usable (enabled) kanji.
 *
 *  DO NOT update "numberincorrect" here. It is independant of
 *  whether or not a kanji is active this second or not.

 */
void CountKanji()
{
	TRANSLATION ktrans;
	int knum;
	numberofkanji=0;

	for(knum=lowestkanji; knum<=highestkanji;knum++){
		ktrans = translations[knum];
		if(ktrans == NULL)
			continue;
		if(UseThisKanji(knum)){
			numberofkanji++;
		}
	}
}

/* the "missed" window now has an interesting format:
 *   (unique kanji missed)/(total missed)/(total guessed)
 *
 * So make a nice wrapper routine to encapsulate it
 */
static void updatemisscount(Widget w)
{
	char tempstr[100];
	sprintf(tempstr,"%d/%d/%d",numberincorrect,totalmissed,totalguessed);
	XtVaSetValues(w,XtNstring,tempstr,NULL);
	return;
	
}

/* kinda like CountKanji, but this ONLY updates 'numberincorrect' 
 * This is a brute force util, to recount when/if we have a leak in
 * our 'numberincorrect' calculations
 */
void TallyWrong()
{
	int wcount=0;
	int kcount=0;
	for(kcount=lowestkanji; kcount<=highestkanji; kcount++){
		if(translations[kcount]==NULL)
			continue;
		if(translations[kcount]->incorrect>0)
			wcount++;
	}
	numberincorrect=wcount;
	updatemisscount(kanjiMissed);
}

/* User wants us to forget our missed kanji count. */
void ClearMissed()
{
	int kcount;
	for(kcount=lowestkanji; kcount<=highestkanji; kcount++)	{
		if(translations[kcount]==NULL)
			continue;
		translations[kcount]->incorrect=0;
	}
	numberincorrect=0;
	updatemisscount(kanjiMissed);
}

/* pickkanji:
 *   picks an acceptably random kanji index.
 *	NOTE THAT THIS DEPENDS ON "numberofkanji" being correct!
 */

TRANSLATION pickkanji()
{
	int rand_kanji,count;
	

	rand_kanji = lrand48()%numberofkanji;

	for(count=lowestkanji;count<=highestkanji;count++){
		if(UseThisKanji(count))	{
		      rand_kanji--;
		      if(rand_kanji <0)
			  return translations[count];
		}
	}
	
	fprintf(stderr,"Internal error: picked kanji out of range\n");
	fprintf(stderr,"random pick: %d\n",rand_kanji);
	fprintf(stderr,"number of kanji: %d\n",numberofkanji);
	exit(0);

}

/* pickincorrect:
 *	This would be more accurately named "Pick one you missed previously"
 * 
 *	Start at first incorect kanji after the last one picked,and look
 *	for another missed one.
 *	This hopefully ensures that we don't replay the same incorrect
 *	one too often.
 *
 *  We are trying to phase this routine out, in favour of
 *    GetRepeat()
 *
 */
TRANSLATION pickincorrect()
{
	TRANSLATION currentpick=lastpicked;
	int count;

	if(numberincorrect==0){
		puts("Internal Error.. pickincorrect called when no incorrect stored");
		return pickkanji();
	}
	count = lowestkanji;
	while(translations[count] != lastpicked)
		count++;

	/* Okay, we are now at current display. Search for first incorrect*/
	do {
		/* search for first USABLE.*/
		do {
			count++;
			if(count > highestkanji)
				count = lowestkanji;
			currentpick=translations[count];

		} while(!UseThisKanji(count));
		if(translations[count] == lastpicked){
			if(numberincorrect>1){
				/* why didnt we hit a second incorrect one? */
				setstatus("Internal error picking repeat");
			}
			return lastpicked;
		}

	} while (currentpick->incorrect == 0);

	return currentpick;
}

/* picktruevalue()
 *	Similar to pickkanji().
 *	This calls pickkanji() to generate random kanji when
 *	desired, or the "next" kanji after 'lastpicked'
 *
 *	Also tries to go back to missed kanji, ONLY when
 *	"random" order is in effect.
 *
 *	Tries to make sure same kanji isn't called in a row,
 *	based on "lastpicked"
 *
 *	"lastpicked is SET somewhere ELSE, presumably just before calling
 *	this routine. It is also used by the "Back" command
 */
TRANSLATION picktruevalue()
{
	int currentpick;
	TRANSLATION ret_val = lastpicked;

	if(!showinorder){
		/* Should we repeat a kanji they missed earlier? */
		/* First check if they have missed any */
		if(numberincorrect>0){
			if(lrand48()%REPEATFRACTION == 0){
				/* repeat what user missed  */
				/*ret_val = pickincorrect();*/
				ret_val=GetRepeat();
			}
			else
			do {
				ret_val = pickkanji();
			} while (ret_val == lastpicked);
			
		}
		/* This also catches the case where we repeat one
		 * they missed, and it is the current one showing
		 */
		while (ret_val == lastpicked){
			ret_val = pickkanji();
		}

	} else {
	
		/* This is annoying. We have to search through all
		 * kanji to find the "lastpicked" translation.
		 * We then add one, so to speak.
		 */

		ret_val = lastpicked;

		currentpick =lowestkanji;

		while(lastpicked != translations[currentpick]){
			currentpick++;
		}
		

		/* okay, add one */

		do {
			currentpick++;
			if(currentpick >highestkanji)
				currentpick = lowestkanji;
		

		} while(!UseThisKanji(currentpick));

		ret_val = translations[currentpick];
	}
	return ret_val;
} 

/* If player has mis-recognized a kanji, call this routine to 
 * make a record that that kanji has been missed at least once.
 *
 * Called by guessvalue()
 */
void markasmissed(TRANSLATION kanjiP)
{
	if(kanjiP->incorrect ==0)
		numberincorrect+=1;
	kanjiP->incorrect += REPEATTIMES;
	if(kanjiP->incorrect >MAXREPEAT)
		kanjiP->incorrect = MAXREPEAT;
	AdjustBadCache(kanjiP);
}

/*guessvalue(int)
 *	This is what handles user guesses.
 *	Passed in number by EITHER kanji button or english button,
 *	we don't care.
 *	Set status bar to correct/incorrect. Beeps if incorrect
 *	Also sets "incorrect" counter in the translations[],
 *	for the translation wrongly guessed.
 *	ALSO calls routine to set total wrong.
 *	
*/
/* note that "-1" on guess means "you lose: ran out of time" */

void guessvalue(int guess)
{
	TRANSLATION kanjiP;

	kanjiP = values[guess];/* for setting "incorrect" flag */

	totalguessed+=1;

	if(guess == truevalue){
		ClearCheat();
		setstatus("Correct! Now try THIS one...");
		switch(kanjiP->incorrect){
			case 0:
				break;
			case 1:
				numberincorrect -= 1;
				/* and fall through */
			default:
				kanjiP->incorrect -= 1;
				AdjustBadCache(kanjiP);
				
				/*	TallyWrong();*/
		}

		updatemisscount(kanjiMissed);

		SetupGuess();
		return;
	} 

	totalmissed+=1;

	/* YOU GOT IT **WWRRROONNGGG!!!!** */
	/* We mark two translation entries because of this;
	 *  once for the quiz meaning you missed,
	 *  and once for the incorrect kanji you thought it was
	 */
	Beep();
	setstatus("Incorrect.");

	/* Note that markasmissed also increments numberincorrect,
	 *  *IFF* needed
	 */
	if(guess != -1){
		markasmissed(kanjiP);
	}

	kanjiP = values[truevalue];
	markasmissed(kanjiP);

	updatemisscount(kanjiMissed);

	/*SetWidgetNumberval... */
}


/* setkanjilabel:
 *	Takes widget, and translation struct.
 *	Sets widget label to be kanji string.
 *	Will set font.
 */
void setkanjilabel(Widget w,TRANSLATION trans)
{

	XtVaSetValues(w,	XtNencoding,XawTextEncodingChar2b,
				XtNfont,largekfont,
				NULL);

	XtVaSetValues(w,	XtNlabel,trans->kanji,
				NULL);
		      

}

/* setenglabel
 *	Given widget, and kanji index,
 *	Sets widget label to appropriate english.
 *	Will change fonts to englishfont.
 */
void setenglabel(Widget widget,TRANSLATION trans)
{

	XtVaSetValues(widget,
		     XtNencoding,XawTextEncoding8bit,
		     XtNfont,englishfont,
		     XtNlabel,trans->english,
		     NULL);

}

/* setkanalabel
 *	Given widget, and kanji index,
 *	Sets widget label to kana reading
 *	Will change font to smallkfont
 */

void setkanalabel(Widget widget,TRANSLATION trans)
{
	if(romajiswitch ==1){
		char stringbuff[MAXROMAJI+1];
		/*translate all kana into romaji */
		/* translate to buffer, then set widget string*/
		kanatoromaji(trans->pronunciation, stringbuff);

		/* stupid X bug workaround... */
		XtVaSetValues(widget,
		      XtNlabel,"",
		      NULL);
		
		XtVaSetValues(widget,
		     XtNencoding,XawTextEncoding8bit,
		     XtNfont,englishfont,
		     NULL);
		XtVaSetValues(widget,
		      XtNlabel,stringbuff,
		      NULL);
		
	} else {
	/* This needs to be done separately? */
		XtVaSetValues(widget,
		     XtNencoding,XawTextEncodingChar2b,
		     XtNfont,smallkfont,
		     NULL);
		XtVaSetValues(widget,
		      XtNlabel,trans->pronunciation,
		      NULL);
	}
}



/* printquestion()
 *	prints out question button, in appropriate font.
 */

void printquestion()
{
	switch(questionmode) {
		case GUESS_ENGLISH:
			setenglabel(questionWidget,values[truevalue]);
			break;
		case GUESS_KANA:
			setkanalabel(questionWidget,values[truevalue]);
			break;
		case GUESS_KANJI:
			setkanjilabel(questionWidget,values[truevalue]);
			break;
		default:
			fprintf(stderr,"Internal Error in kdrill: printallchoices\n");
			exit(0);

	}
}



/* printallchoices()
 *	updates all "choices" labels...
 *	have to handle english, kana, or kanji!
 */
void printallchoices()
{
	int i;

	for(i=0;i<NUMBEROFCHOICES;i++) {
		/* Used to call UnhighlightButton() on each, to make sure
		 * 'cheat' effect went away.
		 * Now, guessvalue() calls ClearCheat() if correct
		 * value has been clicked on.
		 */

		switch(choicesmode) {
			case GUESS_ENGLISH:
				setenglabel(choicesWidgets[i],values[i]);
				break;
			case GUESS_KANA:
				setkanalabel(choicesWidgets[i],values[i]);
				break;
			case GUESS_KANJI:
				setkanjilabel(choicesWidgets[i],values[i]);
				break;
			default:
				fprintf(stderr,"Internal Error in kdrill: printallchoices\n");
				exit(0);

		}
	}
}




/* SetupGuess
 *	
 ******************************************************
 *
 * called by guessvalue(), (or init) once user has clicked the CORRECT answer.
 *	sets up new question and triggers button label refresh
 *
 */
void SetupGuess()
{
	Boolean doloop;
	int valuecount;

	StopTimeout();

	lastpicked = values[truevalue];

	/* first determine which of the buttons will be the "true" choice */
	truevalue = lrand48() %NUMBEROFCHOICES;

	/* Now randomly assign characters for each choice button */
	for(valuecount=0;valuecount<NUMBEROFCHOICES;valuecount++){
		if(valuecount == truevalue){
			values[valuecount] = picktruevalue();
		} else {
			values[valuecount] = pickkanji();
		}
	}

	/* now weed out duplicates..
	 * This is messy.
	 *  We compare everything to everything else, and change
	 *   the "current" value if there is a conflict...
	 *    UNLESS current value is the "truevalue".
	 *  We then do a full compare again if there was a conflict
	 */
	do {
		int startcount=0;
		doloop = False;
		do {
			if(startcount == truevalue)
				continue;
			for(valuecount=0;valuecount<NUMBEROFCHOICES;valuecount++){
				if(valuecount == startcount)
					continue;
				if(values[startcount]== values[valuecount] ){
					doloop = True;
					values[startcount] = pickkanji();
				}
			}
		} while(++startcount<NUMBEROFCHOICES);

	} while(doloop== True);

	DescribeCurrent(values[truevalue]);

	printquestion();	/* update labels */
	printallchoices(); /* likewise      */
	printgrades();	/* stupid buttons needs updating manually,
			 * EVERY durn time. arrg */

	if(values[truevalue]->incorrect>0)
		setstatus("You missed this one before");

	StartTimeout();
}



syntax highlighted by Code2HTML, v. 0.9.1