// based in part on hello-world.c (c) 1996, 1997 D. Jeff Dionne
// This is free software, under the GNU public license v2
register void *reg_a4 asm("%a4");
#include <Common.h>
#include <System/SysAll.h>
#include <System/SysEvtMgr.h>
#include <UI/UIAll.h>
#include <UI/CharAttr.h>
#include "kanjitest.h"
#include "shared.h"
#define MyAppID 'jKJT'
#define MyDBType 'Data'
#define MyDBName "KanjiTestDB"
#define KanAppID 'jKJt'
#define KanDBType 'Data'
#define KanDBName "KanjiTableDB"

#define FAIL_PENALTY 20L
#define MIN_KANJI_SET 50L 
#define MAXSTRINGSIZE 255L
#define ADVANCE_MERIT 12L
//point at which non-correct kanji are from entire database
#define COUNTDOWN_ESCALATION 50L
//point at which definitions are no longer shown
#define DEFINITION_ESCALATION 150L
//below this number, "hard" kanji are preferentially chosen
#define HARD_ESCALATION 200L
//above this number, "easy" kanji are preferentially chosen.  Give the poor
//guy a real break
#define RANDOM_ESCALATION 300L

#define KANJI_MODE 0
#define ENGLISH_MODE 1
//room for 2 more modes...

#define DEFWAIT 40L

typedef struct {
   Word kanjiUsed[NumKanjiButtons]; // row of each kanji being displayed
   Word correctKanji;   //number of correct Kanji 
   Word englishUsed[NumEnglishButtons];
   Word correctEnglish;
   Word gameMode :2;
   Word displayMode :2;
   Word choosing :1;
} State_t;

typedef struct {   
   DmOpenRef KanDB, MyDB;
   
   char**   indp;   //array of pointers into string resources
   Word *sortp;   //pointer to a sorted list of indices
   KanjiData_t* kandp;
   BitmapData_t* bitp[MAXSTRINGRESOURCES];
   UserTrack_t *statp;
   State_t *statep;
   char*    kanjiDefp;
   char*    englishDefp[4]; //fix this ***********
   StringResource_t* strp[MAXSTRINGRESOURCES];

   Word numBitmaps;      //number of bitmaps available
   Word numIndices;      //number of lines in current indexing scheme 
   BitmapType bmheader; // bmdata must immediately follow bmheader
   BitmapData_t bmdata; // We assume a 16x16 bitmap
   char numKanji[5];
   char numGuessed[7];
   char prcRight[4];
   char countDown[6];
   char merit[7];
} GlobalData_t;

static GlobalData_t g;
#include "shared.c"

static Boolean StartApplication(void) {
  Word resourceNum, 
       currentString,
       numStrings;

  void* GetResourcePtr(ULong type, Int index) {
    VoidHand handle = DmGetResource(type, index);
    if (handle) return MemHandleLock(handle);
    return 0;
  }
  void* GetResourcePtrOrDie(ULong type, Int index) {
    VoidHand handle = DmGetResource(type, index);
    if (handle) return MemHandleLock(handle);
    ErrDisplay("no resource"); return 0;
  }
  void* GetResourcePtrOrCreate(ULong type, Int index, DmOpenRef db, 
      void* data, long size, long copy_size) {
    VoidHand handle = DmGetResource(type, index);
    VoidPtr returnVal;
    if (handle && MemHandleSize(handle) == size) 
	return MemHandleLock(handle);
    if (handle) 
	handle = DmResizeResource(handle, size); 
    else
    	handle = DmNewResource(db, type, index, size);
    ErrFatalDisplayIf(!handle, "no resource");
    returnVal = MemHandleLock(handle);
    DmSet(returnVal, 0, size, 0);
    DmWrite(returnVal, 0, data, copy_size); return returnVal;
  }
  
  void* AllocatePtrOrDie(ULong size) {
    VoidHand handle = DmNewHandle(g.KanDB, size);
    char* no_mem = "no mem";
    void* returnVal;
    ErrFatalDisplayIf(!handle, no_mem);
    returnVal = MemHandleLock(handle);
    ErrFatalDisplayIf(!handle, no_mem);
    return returnVal;
  }
  
  MemSetDebugMode( memDebugModeFillFree );
  MemSet (&g, sizeof(GlobalData_t), 0);
  g.KanDB =DmOpenDatabaseByTypeCreator(KanDBType,KanAppID,dmModeReadOnly);
  ErrFatalDisplayIf(!g.KanDB, "No DB");

  g.numBitmaps = 0;
  for (resourceNum = 0; resourceNum < MAXSTRINGRESOURCES; resourceNum++) {
    g.bitp[resourceNum] = GetResourcePtr('BITM', resourceNum);
    if (!g.bitp[resourceNum]) break;
    g.numBitmaps += MemPtrSize(g.bitp[resourceNum])/sizeof(BitmapData_t);
  }

  numStrings = 0;
  for (resourceNum = 0; resourceNum < MAXSTRINGRESOURCES; resourceNum++) {
    g.strp[resourceNum] = GetResourcePtr('STRN', resourceNum ); 
    if (!g.strp[resourceNum]) break;
    numStrings += g.strp[resourceNum]->numEntries;
  }

  g.kandp = GetResourcePtrOrDie('KAND',0);
  g.sortp = AllocatePtrOrDie((numStrings+1)* sizeof(Word));

  newSort(&g, SORT_FREQ);
  g.indp = AllocatePtrOrDie((numStrings+1)* sizeof(char *));
  g.kanjiDefp = AllocatePtrOrDie( MAXSTRINGSIZE);

  currentString = 0;
  for (resourceNum = 0; resourceNum < MAXSTRINGRESOURCES; resourceNum++) { 
    Word num;
    VoidHand stringH;
    char *stringP;
    char buff[10];
    if (!g.strp[resourceNum]) break;
    num = g.strp[resourceNum]->numEntries;
    stringH = SysFormPointerArrayToStrings(g.strp[resourceNum]->data, num);
    stringP = MemHandleLock(stringH);
       StrIToA(buff, num) ;
       ErrFatalDisplayIf(currentString + num > numStrings+1, buff);
       DmWrite(g.indp, currentString*(sizeof(char*)),
	 stringP,MemPtrSize(stringP)   ); 
       currentString += num;
    MemHandleUnlock(stringH);
    MemHandleFree(stringH);
  }

  //initialize statistics structure
  g.MyDB =DmOpenDatabaseByTypeCreator(MyDBType,MyAppID,dmModeReadWrite);
  if (!g.MyDB) {
    ErrFatalDisplayIf (DmCreateDatabase(0, MyDBName, MyAppID, MyDBType, true), "No log");
    g.MyDB =DmOpenDatabaseByTypeCreator(MyDBType,MyAppID,dmModeReadWrite);
    ErrFatalDisplayIf(!g.MyDB, "No Log");
  }

  { 
    UserTrack_t track = {g.numBitmaps, MIN_KANJI_SET, 0, 0, {} };
    g.statp=GetResourcePtrOrCreate('STAT',0,g.MyDB, (void*)&track, 
	sizeof(track)+g.numBitmaps, sizeof(track) );
  }

  { State_t state;
    state.correctKanji=-1;
    state.choosing = 1;
    g.statep =GetResourcePtrOrCreate('STAT',1, g.MyDB, (void*)&state, 
	sizeof(state), sizeof(state) );
  }
  g.bmheader = (BitmapType) {16,16,2,
		{0,0},
                {0,0,0,0}};
  g.englishDefp[0] = AllocatePtrOrDie( MAXSTRINGSIZE);
  g.englishDefp[1] = AllocatePtrOrDie( MAXSTRINGSIZE);
  g.englishDefp[2] = AllocatePtrOrDie( MAXSTRINGSIZE);
  g.englishDefp[3] = AllocatePtrOrDie( MAXSTRINGSIZE);
  return 0;
}

void StopApplication (void) {
  Word i;
//  char* heap_error = "heap_error";

  void DiscardResource(void* res) {
    MemPtrUnlock(res);
    //should also release here but ResetRecordStates may take care of that
  }
  void DiscardMemory(void* mem) {
    MemPtrUnlock(mem); MemPtrFree(mem);
  }
 
  for(i=0; g.strp[i]; i++) DiscardResource(g.strp[i]); 
  for(i=0; g.bitp[i]; i++) DiscardResource(g.bitp[i]); 
  DiscardResource(g.kandp);
  DiscardResource(g.statp);
  DiscardResource(g.statep);

  DiscardMemory(g.sortp);
  DiscardMemory(g.indp);
  DiscardMemory(g.kanjiDefp);
  DiscardMemory(g.englishDefp[0]);
  DiscardMemory(g.englishDefp[1]);
  DiscardMemory(g.englishDefp[2]);
  DiscardMemory(g.englishDefp[3]);

  DmResetRecordStates(g.MyDB);
  DmResetRecordStates(g.KanDB);
  DmCloseDatabase(g.MyDB);
  DmCloseDatabase(g.KanDB);
}
 
static long CountDown(void) {
  return (g.statp->numGuessed - g.statp->numRight) * FAIL_PENALTY 
	-g.statp->numGuessed;
}

//Figure of merit, based on current record.
static long Merit(void) {
  long i, a;

  for (i=a=0; i< g.statp->numKanji; i++) 
    a += g.statp->track[i].record;
  return (a*100)/255;
}

#define NO_READING 1
#define NO_DEFINITION 2
static void SetTempString(char* orig, char* destination, Word Skip) {
  char temp[MAXSTRINGSIZE];
  Word src, dst;
  for(src = dst = 0; orig[src] && dst<MAXSTRINGSIZE-1; src++) {
    //leave definition for later
    if (orig[src] == '-' && orig[src+1] == '-') {
      if (Skip & NO_READING) src +=2; 
      break; 
    }

    if (!(Skip & NO_READING)) {
      temp[dst] = orig[src];
      if (temp[dst] == ':' || temp[dst] == ',')  temp[++dst] = ' ' ;
      dst++;
    }
  }

  for(; orig[src] && dst<MAXSTRINGSIZE-1; src++) {
    if (!(Skip & NO_DEFINITION)) {
      temp[dst] = orig[src];
      if (temp[dst] == ':' || temp[dst] == ',')  temp[++dst] = ' ' ;
      dst++;
    }
  }
  temp[dst] = 0;
  DmWrite(destination, 0, temp, MAXSTRINGSIZE);
}

static inline Boolean Advance(void) {
    if (CountDown() > 0) return false;
    if ( (long)g.statp->numKanji*100L <= ADVANCE_MERIT*100L+Merit() )
      return true;
    return false;
}

static void DisplayStats(void) {
  long i;
  FormType *pfrm = FrmGetActiveForm();

  static void SetField(Word index, char* value) { 
    FieldType *pfld;
    pfld = FrmGetObjectPtr(pfrm, FrmGetObjectIndex(pfrm, index ) );
    FldSetTextPtr(pfld, value);
    FldDrawField(pfld);
  }
  //Update display of statistics
  if (g.statp->numGuessed != 0) {
    StrIToA(g.prcRight, g.statp->numRight * 100L / g.statp->numGuessed );
    StrIToA(g.numGuessed, g.statp->numGuessed );
    StrIToA(g.numKanji, g.statp->numKanji );
    StrIToA(g.merit, Merit() );

    SetField(NumKanjiField, g.numKanji);
    SetField(PrcRightField, g.prcRight);
    SetField(NumGuessedField, g.numGuessed);  

    i = CountDown();
    StrIToA(g.countDown, CountDown() );
    if (i>=0)
      SetField(CountDownField , g.countDown );
    else if (  Advance() )
      SetField(CountDownField , "Now!");
    else
      SetField(CountDownField , "Soon");

    SetField(MeritField, g.merit);
  }
}

static void DisplayKanji(State_t *statep) {
  long i;
  FieldType *pfld;
  FormType *pfrm = FrmGetActiveForm();

  for(i= KanjiButton0; i<= KanjiButtonLast ; i++) {                     
    ControlType *pctl;
    pctl = FrmGetObjectPtr(pfrm, FrmGetObjectIndex(pfrm, i));
    WinEraseRectangle(&pctl->bounds, 0);
    CtlDrawControl(pctl);
    if (i!= KanjiButtonLast)
      MyDrawBitmap(statep->kanjiUsed[i-KanjiButton0]
      ,pctl->bounds.topLeft.x+7, pctl->bounds.topLeft.y+5 );
  }
  
  pfld = FrmGetObjectPtr(pfrm, FrmGetObjectIndex(pfrm, DefField ) );
  WinEraseRectangle(&pfld->rect, 0);
  FldSetTextPtr(pfld, g.kanjiDefp );
  FldRecalculateField(pfld, true);

  // Draw "bells and whistles" countdown rectangle
  { long val;
    RectangleType r = {{129, 152}, {30,5} };
    val = g.statp->numKanji*100L -ADVANCE_MERIT*100L - Merit(); val /= 33L;
    WinEraseRectangle(&r,0);
    if (val < r.extent.x) r.extent.x = val;
    if (val > 0) WinDrawRectangle(&r,0);
  }
}

static void DisplayEnglish(State_t *statep) {
  long i;
  FormType *pfrm = FrmGetActiveForm();
  MyDrawBitmap(statep->correctEnglish, 72, 0 );
  for(i= 0; i<= 3; i++) {                     
    FieldType *pfld;
    pfld = FrmGetObjectPtr(pfrm, FrmGetObjectIndex(pfrm, i+EnglishField0));
    WinEraseRectangle(&pfld->rect, 0);
    SetTempString( STRP(statep->englishUsed[i]), 
    g.englishDefp[i], statep->displayMode );
    FldSetTextPtr(pfld, g.englishDefp[i] );
    FldRecalculateField(pfld, true);
  }
}

static Boolean GoodChoice(Word buttonNumber, State_t *statep) {
  Word index         = statep->kanjiUsed[buttonNumber]; 
  UserTrack_t track;
  unsigned char newRecord;
  Boolean correctChoice;

  if (buttonNumber <= NumKanjiButtons) {
    correctChoice = (index == statep->correctKanji);
  } else
    correctChoice = false;
  //Update global information
  track.numIndices = g.statp->numIndices;
  track.numGuessed = g.statp->numGuessed +1;
  track.numRight = g.statp->numRight + (correctChoice ? 1 : 0);
  track.numKanji = g.statp->numKanji + (Advance()? 10 : 0);
  if (track.numKanji > track.numIndices) track.numKanji = track.numIndices;
  if (track.numKanji < MIN_KANJI_SET) track.numKanji = MIN_KANJI_SET;
  DmWrite(g.statp, 0, &track, sizeof(track) ); 

  //Update individual kanji info
  newRecord = (correctChoice ? 128 : 0)+g.statp->track[statep->correctKanji].record/2;
  DmWrite(g.statp, (long)&g.statp->track[statep->correctKanji].record-(long) g.statp
  ,&newRecord, 1);
  if (!correctChoice && buttonNumber < NumKanjiButtons-1) {
    newRecord = g.statp->track[index].record/2;
    DmWrite(g.statp, (long)&g.statp->track[index].record-(long) g.statp
    ,&newRecord, 1);
  }
  if (!correctChoice) {
    SetTempString(STRP(statep->correctKanji), g.kanjiDefp, 0);
    DisplayKanji(statep);
    statep->choosing = 0;
    statep->displayMode=0;
  }
  return correctChoice;
}

static Boolean GoodEnglishChoice(Word buttonNumber, State_t *statep) {
  Word index         = statep->englishUsed[buttonNumber]; 
  UserTrack_t track;
  unsigned char newRecord;
  Boolean correctChoice;

  if (buttonNumber <= NumEnglishButtons) {
    correctChoice = (index == statep->correctEnglish);
  } else
    correctChoice = false;
  //Update global information
  track.numIndices = g.statp->numIndices;
  track.numGuessed = g.statp->numGuessed +1;
  track.numRight = g.statp->numRight + (correctChoice ? 1 : 0);
  track.numKanji = g.statp->numKanji + (Advance()? 10 : 0);
  if (track.numKanji > track.numIndices) track.numKanji = track.numIndices;
  if (track.numKanji < MIN_KANJI_SET) track.numKanji = MIN_KANJI_SET;
  DmWrite(g.statp, 0, &track, sizeof(track) ); 

  //Update individual kanji info
  newRecord = (correctChoice ? 128 : 0)
	+g.statp->track[statep->correctEnglish].record/2;
  DmWrite(g.statp, 
	(long)&g.statp->track[statep->correctEnglish].record-(long) g.statp
  ,&newRecord, 1);
  if (!correctChoice && buttonNumber < NumEnglishButtons-1) {
    newRecord = g.statp->track[index].record/2;
    DmWrite(g.statp, (long)&g.statp->track[index].record-(long) g.statp
    ,&newRecord, 1);
  }
  if (!correctChoice) {
    statep->choosing = 0;
    statep->displayMode=0;
    DisplayEnglish(statep);
  }
  return correctChoice;
}

static Boolean SplashInfoHandleEvent(EventPtr e) {
  FormType *pfrm;
  State_t state = *g.statep;
  
  switch (e->eType) {
    case frmOpenEvent:
      pfrm=FrmGetActiveForm(); 
      FrmDrawForm(pfrm);
      DisplayStats();
      return false;

    case ctlSelectEvent:
      switch (e->data.ctlEnter.controlID) {
	case SplashToggle:
          state.gameMode = ENGLISH_MODE - state.gameMode;
          DmWrite(g.statep, 0, &state, sizeof(state) );
	  break;
	case SplashDone:
	  switch (state.gameMode) {
            case ENGLISH_MODE: 
	      FrmGotoForm(TestEnglish);
	      break;
            case KANJI_MODE: 
	      FrmGotoForm(TestKanji);
	      break;
	  }
      } 
            
      return false;
    default:
  }
  return false;
}

// choose random english for each button.  
// unknown kanji are favoured once you get too good
static void GenerateEnglish(State_t *statep) {
  int i;
  long countDown = CountDown();
  typedef enum{
        easy,    //easy kanji from the working set
        random, //any element of the working set
        hard,    //hard kanji from the working set
    	fullset  //any kanji in the database
  } diff_t;
  
  //returns a unique kanji of a given difficulty, corelated to the correct one
  static Word RandomEnglish(diff_t difficulty, Word position) {
    Word returnVal=0;
    Word corelation=0;
    KanjiData_t *kp = g.kandp;
    int i;
    REROLL:
    switch (difficulty) {
      case fullset: returnVal = SysRandom(0) % g.statp->numIndices; break;
      case easy ... hard: returnVal = SysRandom(0) % g.statp->numKanji;
    }
    //make sure that kanji is unique
    for(i=0; i<position; i++) if (returnVal == statep->englishUsed[i]) goto REROLL;
    
    if (position > 0) {
      if (SORTVAL_BUSHU(g.sortp[statep->englishUsed[0]]) 
    	== SORTVAL_BUSHU(g.sortp[returnVal])) corelation+=2;
      if (SORTVAL_SPAHN(g.sortp[statep->englishUsed[0]])/30 
    	== SORTVAL_SPAHN(g.sortp[returnVal])/30) corelation++;
      if (SORTVAL_SKIP(g.sortp[statep->englishUsed[0]])/1024 
    	== SORTVAL_SKIP(g.sortp[returnVal])/1024) corelation++;
      if (SORTVAL_COUNT(g.sortp[statep->englishUsed[0]]) 
    	== SORTVAL_COUNT(g.sortp[returnVal])) corelation++;
    } 
     
    switch(difficulty) {
      case fullset:
      case hard:
       if ( (Word) g.statp->track[returnVal].record > (SysRandom(0)&0xff) + corelation) 
    	  goto REROLL; 
        break;
      case easy:
       if ( (Word) g.statp->track[returnVal].record < (SysRandom(0)&0xff) ) 
    	  goto REROLL; 
        break;
      case random:
    } 
    return returnVal;
  } 
  
  //The correct english goes in slot 0 for a while.  Make sure that it is
  //hard if going is good, and very easy if going is bad
  if (countDown < HARD_ESCALATION) 
	statep->englishUsed[0] = RandomEnglish(hard,0);
  else if (countDown < RANDOM_ESCALATION) 
	statep->englishUsed[0] = RandomEnglish(random,0);
  else  statep->englishUsed[0] = RandomEnglish(easy,0);
  statep->correctEnglish = statep->englishUsed[0];

  for(i=1; i<NumEnglishButtons; i++) {
    if (countDown < COUNTDOWN_ESCALATION) 
	statep->englishUsed[i] = RandomEnglish(fullset, i);
    else if (countDown < HARD_ESCALATION) 
	statep->englishUsed[i] = RandomEnglish(hard, i);
    else if (countDown < RANDOM_ESCALATION) 
	statep->englishUsed[i] = RandomEnglish(random, i);
    else 
	statep->englishUsed[i] = RandomEnglish(easy, i);
  }

  //swap things around.  Well known may end up in the "?" button
  if ( (countDown < DEFINITION_ESCALATION) 
	&& (g.statp->track[statep->correctEnglish].record > 240 ) )
    i = SysRandom(0) % NumEnglishButtons;
  else 
    i = SysRandom(0) % (NumEnglishButtons-1);

  statep->englishUsed[0] = statep->englishUsed[i];
  statep->englishUsed[i] = statep->correctEnglish;

  if   (countDown < DEFINITION_ESCALATION 
	&& g.statp->track[statep->correctKanji].record > 240 )
    statep->displayMode =  (SysRandom(0)&1)+1;
  else
    statep->displayMode = 0;

  statep->choosing = 1; 
} //GenerateEnglish

// choose random kanji for each button.  
// unknown kanji are favoured once you get too good
static void GenerateKanji(State_t *statep) {
  int i;
  long countDown = CountDown();
  typedef enum{
        easy,    //easy kanji from the working set
        random, //any element of the working set
        hard,    //hard kanji from the working set
    	fullset  //any kanji in the database
  } diff_t;
  
  //returns a unique kanji of a given difficulty, corelated to the correct one
  static Word RandomKanji(diff_t difficulty, Word position) {
    Word returnVal=0;
    Word corelation=0;
    KanjiData_t *kp = g.kandp;
    int i;
    REROLL:
    switch (difficulty) {
      case fullset: returnVal = SysRandom(0) % g.statp->numIndices; break;
      case easy ... hard: returnVal = SysRandom(0) % g.statp->numKanji;
    }
    //make sure that kanji is unique
    for(i=0; i<position; i++) if (returnVal == statep->kanjiUsed[i]) goto REROLL;
    
    if (position > 0) {
      if (SORTVAL_BUSHU(g.sortp[statep->kanjiUsed[0]]) 
    	== SORTVAL_BUSHU(g.sortp[returnVal])) corelation+=2;
      if (SORTVAL_SPAHN(g.sortp[statep->kanjiUsed[0]])/30 
    	== SORTVAL_SPAHN(g.sortp[returnVal])/30) corelation++;
      if (SORTVAL_SKIP(g.sortp[statep->kanjiUsed[0]])/1024 
    	== SORTVAL_SKIP(g.sortp[returnVal])/1024) corelation++;
      if (SORTVAL_COUNT(g.sortp[statep->kanjiUsed[0]]) 
    	== SORTVAL_COUNT(g.sortp[returnVal])) corelation++;
    } 
     
    switch(difficulty) {
      case fullset:
      case hard:
       if ( (Word) g.statp->track[returnVal].record > (SysRandom(0)&0xff) + corelation) 
    	  goto REROLL; 
        break;
      case easy:
       if ( (Word) g.statp->track[returnVal].record < (SysRandom(0)&0xff) ) 
    	  goto REROLL; 
        break;
      case random:
    } 
    return returnVal;
  } 
  
  //The correct kanji goes in slot 0 for a while.  Make sure that it is
  //hard if going is good, and very easy if going is bad
  if (countDown < HARD_ESCALATION) 
	statep->kanjiUsed[0] = RandomKanji(hard,0);
  else if (countDown < RANDOM_ESCALATION) 
	statep->kanjiUsed[0] = RandomKanji(random,0);
  else  statep->kanjiUsed[0] = RandomKanji(easy,0);
  statep->correctKanji = statep->kanjiUsed[0];

  for(i=1; i<NumKanjiButtons; i++) {
    if (countDown < COUNTDOWN_ESCALATION) 
	statep->kanjiUsed[i] = RandomKanji(fullset, i);
    else if (countDown < HARD_ESCALATION) 
	statep->kanjiUsed[i] = RandomKanji(hard, i);
    else if (countDown < RANDOM_ESCALATION) 
	statep->kanjiUsed[i] = RandomKanji(random, i);
    else 
	statep->kanjiUsed[i] = RandomKanji(easy, i);
  }

  //swap things around.  Well known may end up in the "?" button
  if ( (countDown < DEFINITION_ESCALATION) 
	&& (g.statp->track[statep->correctKanji].record > 240 ) )
    i = SysRandom(0) % NumKanjiButtons;
  else 
    i = SysRandom(0) % (NumKanjiButtons-1);

  statep->kanjiUsed[0] = statep->kanjiUsed[i];
  statep->kanjiUsed[i] = statep->correctKanji;

  if  ( (countDown < DEFINITION_ESCALATION )
      && (g.statp->track[statep->correctKanji].record > 240))
    statep->displayMode =  (SysRandom(0)&1)+1;
  else
    statep->displayMode = 0;
  SetTempString( STRP(statep->correctKanji), g.kanjiDefp, statep->displayMode ) ;

  statep->choosing = 1; 
} //GenerateKanji

static Boolean TestEnglishHandleEvent(EventPtr e) {
  FormType *pfrm;
  Word buttonNumber;
  static State_t state;
  int i;
 
  switch (e->eType) {
    case frmOpenEvent:
      pfrm=FrmGetActiveForm(); 
      state = *g.statep;
      if (state.correctEnglish == -1) GenerateEnglish(&state);
      for(i= EnglishButton0; i<= LastSquareEnglishButton ; i++) {
        ControlType *pctl = FrmGetObjectPtr(pfrm, FrmGetObjectIndex(pfrm, i));
        pctl->attr.frame = rectangleButtonFrame;
      }

	
      FrmDrawForm(pfrm);
      DisplayEnglish(&state);
      return true;

    case ctlSelectEvent:
      switch (e->data.ctlEnter.controlID) {
        case EnglishButton0 ... EnglishButtonLast: 
	  buttonNumber = e->data.ctlEnter.controlID - EnglishButton0;
	  if (state.choosing) {
	    if (!GoodEnglishChoice (buttonNumber, &state) ) {
              DmWrite(g.statep, 0, &state, sizeof(state) );
	      return false;
            }
	  }           
          GenerateEnglish(&state);
          DisplayEnglish(&state);
          DmWrite(g.statep, 0, &state, sizeof(state) );
          return true;
        case EnglishInfo:
          DmWrite(g.statep, 0, &state, sizeof(state) );
	  FrmGotoForm(SplashInfo);
          return true;
        default: 
          return false;
      } 

    case frmCloseEvent:
      DmWrite(g.statep, 0, &state, sizeof(state) );
      return false;
    case nilEvent:
      if (state.choosing) return false;
      {
        ControlType *pctl;
        int i;
        pfrm=FrmGetActiveForm(); 
	for(i=0; i<NumEnglishButtons;i++) {
            if (state.englishUsed[i] !=state.correctEnglish) continue;
            pctl=FrmGetObjectPtr(pfrm,FrmGetObjectIndex(pfrm,EnglishButton0+i) );
            WinInvertRectangle(&pctl->bounds, 0); 
        }
      }     
    default:
  }
  return false;
}
static Boolean TestKanjiHandleEvent(EventPtr e) {
  FormType *pfrm;
  Word buttonNumber;
  static State_t state;
  Word i;
 
  switch (e->eType) {
    case frmOpenEvent:
      pfrm=FrmGetActiveForm(); 
      state = *g.statep;
      for(i= KanjiButton0; i<= LastSquareButton ; i++) { 
        ControlType *pctl = FrmGetObjectPtr(pfrm, FrmGetObjectIndex(pfrm, i));
        pctl->attr.frame = rectangleButtonFrame;
      }
      FrmDrawForm(pfrm);
      if (state.correctKanji == -1) GenerateKanji(&state);
      else
        SetTempString(STRP(state.correctKanji), g.kanjiDefp, state.displayMode);
	
      DisplayKanji(&state);
      return true;

    case ctlSelectEvent:
      switch (e->data.ctlEnter.controlID) {
        case KanjiButton0 ... KanjiButtonLast: 
	  buttonNumber = e->data.ctlEnter.controlID - KanjiButton0;
	  if (state.choosing) {
	    if (!GoodChoice (buttonNumber, &state) ) {
              DmWrite(g.statep, 0, &state, sizeof(state) );
	      return false;
            }
	  }           
          GenerateKanji(&state);
          DisplayKanji(&state);
          DmWrite(g.statep, 0, &state, sizeof(state) );
          return true;
        case KanjiButtonInfo:
          DmWrite(g.statep, 0, &state, sizeof(state) );
	  FrmGotoForm(SplashInfo);
          return true;
        default: 
          return false;
      } 

    case frmCloseEvent:
      DmWrite(g.statep, 0, &state, sizeof(state) );
      return false;
    case nilEvent:
      if (state.choosing) return false;
      {
        ControlType *pctl;
        int i;
        pfrm=FrmGetActiveForm(); 
	for(i=0; i<NumKanjiButtons;i++) {
            if (state.kanjiUsed[i] !=state.correctKanji) continue;
            pctl=FrmGetObjectPtr(pfrm,FrmGetObjectIndex(pfrm,KanjiButton0+i) );
            WinInvertRectangle(&pctl->bounds, 0); 
        }
      }     
    default:
  }
  return false;
}

void EventLoop(void) {
  EventType e;
  Word error;
  FormPtr pfrm;

  do {
    EvtGetEvent(&e, DEFWAIT);
    if (SysHandleEvent(&e)) continue;
    if (MenuHandleEvent((void *)0, &e, &error)) continue;
    if (e.eType == frmLoadEvent) {
      pfrm = FrmInitForm(e.data.frmLoad.formID);
      FrmSetActiveForm(pfrm);
      switch(e.data.frmLoad.formID) {
       case TestEnglish:
        FrmSetEventHandler(pfrm, TestEnglishHandleEvent);
        break;
       case TestKanji:
        FrmSetEventHandler(pfrm, TestKanjiHandleEvent);
        break;
       case SplashInfo:
	FrmSetEventHandler(pfrm, SplashInfoHandleEvent);
        break;
      }
      continue;
    }
    FrmDispatchEvent(&e);
  } while (e.eType != appStopEvent);
}

DWord PilotMain(Word cmd, Ptr cmdPBP, Word launchFlags)
{
  Word error;
  switch (cmd) {
   case sysAppLaunchCmdNormalLaunch:
      if ( (error = StartApplication() )) return error;
      if (g.statep->gameMode == ENGLISH_MODE )	
	FrmGotoForm(TestEnglish); 
      else
        FrmGotoForm(TestKanji);
      EventLoop();
      StopApplication();	
      break;
    default:
  }
  return 0;
}
