/* based in part on hello-world.c (c) 1996, 1997 D. Jeff Dionne
 * This is free software, under the GNU public license v2
 */

#pragma pack(2)

register void *reg_a4 asm("%a4");

#include <Common.h>
#include <System/SysAll.h>
#include <System/SysEvtMgr.h>
#include <System/FeatureMgr.h>
#include <UI/UIAll.h>
#include <UI/CharAttr.h>
#include "kanjitable.h"
#include "shared.h"
#define KanAppID 'jKJt'
#define KanDBType 'Data'
#define KanDBName "KanjiTableDB"
#define TestAppID 'jKJT'
#define TestDBType 'Data'
#define TestDBName "KanjiTestDB"

// I'd use a gadget for these, but I had problems and this is simpler
#define DRAWTOPX 0
#define DRAWTOPY 16
#define DRAWEXTENTX   159
#define DRAWEXTENTY 144

#define USERUPDATE 40000
#define ftrTop 1
#define ftrCols 2 

#define DEFWAIT 500
#define CALLBACK_PROLOGUE \
    void *save_a4 = reg_a4; asm("move.l %%a5,%%a4; sub.l #edata,%%a4" : :);
#define CALLBACK_EPILOGUE reg_a4 = save_a4;

typedef struct {   

   struct {
     Word top;
     Byte sort;      //  sort resource to use 
     Byte  visCols;  // Features 
   } state;

   DmOpenRef KanDB, TestDB;
   
   VoidHand sorth;  //handle to a sorted list of indices
   Word *sortp;     //pointer to a sorted list of indices
   VoidHand indh;   //handle to temporary record
   char** indp; //array of pointers into string resources
   VoidHand strh[MAXSTRINGRESOURCES]; // handles to each string resource
   VoidHand bith[MAXSTRINGRESOURCES];
   BitmapData_t* bitp[MAXSTRINGRESOURCES];
   VoidHand kandh;
   KanjiData_t* kandp;
   VoidHand stath;
   UserTrack_t* statp;
   VoidHand stateh;

   Word numBitmaps;      //number of bitmaps available
   Word numIndices;      //number of lines in current indexing scheme 

   char rowStr[6];
   RectangleType r;
   BitmapType bmheader;  // bmdata must immediately follow bmheader
   BitmapData_t bmdata; // We assume a 16x16 bitmap
} GlobalData_t;
static GlobalData_t g;
#include "./shared.c"

static Word inline RowHeight() {
  if (g.state.visCols >=9) return 18;
  else return 20;
}
static Word inline VisRows() {
  if (g.state.visCols >=9) return 8; 
  else return 7;
}
 
static Boolean StartApplication(GlobalData_t *gp) {
  Word resourceNum, 
       currentString,
       numStrings;
  StringResource_t *strp[MAXSTRINGRESOURCES];

  gp->KanDB =DmOpenDatabaseByTypeCreator(KanDBType,KanAppID,dmModeReadWrite);
  ErrFatalDisplayIf(!gp->KanDB, "No DB");

  gp->stateh = DmGetResource('STAT',2);
  if (gp->stateh && MemHandleSize(gp->stateh) != sizeof(gp->state)) {
    DmRemoveResource(gp->KanDB, DmFindResource(gp->KanDB,0,0,gp->stateh));
    gp->stateh=0;        
  }
  if (!gp->stateh) {                                                   
    gp->stateh = DmNewResource(gp->KanDB, 'STAT', 2, sizeof(gp->state));
    ErrFatalDisplayIf(!gp->stateh, "no state"); 
    gp->state.top = 0;
    gp->state.visCols = 9;
    gp->state.sort = SORT_FREQ;
  } else {
    VoidPtr statep = MemHandleLock(gp->stateh);
    MemMove(&gp->state, statep, sizeof(gp->state) );
    MemHandleUnlock(gp->stateh);
  } 

  gp->numBitmaps = 0;
  for (resourceNum = 0; resourceNum < MAXSTRINGRESOURCES; resourceNum++) {
    gp->bith[resourceNum] = DmGetResource('BITM', resourceNum ); 
    if (!gp->bith[resourceNum]) break;
    gp->bitp[resourceNum] = MemHandleLock(gp->bith[resourceNum]);
    gp->numBitmaps += MemPtrSize(gp->bitp[resourceNum])/sizeof(BitmapData_t);
  }

  gp->kandh = DmGetResource('KAND',0);
  ErrFatalDisplayIf(!gp->kandh, "kandh");
  gp->kandp = MemHandleLock(gp->kandh);
  ErrFatalDisplayIf(!gp->kandp, "kandp");

  numStrings = 0;
  for (resourceNum = 0; resourceNum < MAXSTRINGRESOURCES; resourceNum++) {
    gp->strh[resourceNum] = DmGetResource('STRN', resourceNum ); 
    if (!gp->strh[resourceNum]) break;
    strp[resourceNum] = MemHandleLock(gp->strh[resourceNum]);
    numStrings += strp[resourceNum]->numEntries;
  }

  gp->sorth = DmNewHandle(gp->KanDB, (numStrings+1)* sizeof(Word));
  ErrFatalDisplayIf(!gp->kandh, "sorth");
  gp->sortp = MemHandleLock(gp->sorth);
  ErrFatalDisplayIf(!gp->sortp, "sortp");

  gp->indh = DmNewHandle(gp->KanDB, (numStrings+1)* sizeof(char *));
  ErrFatalDisplayIf(!gp->indh, "inddb");
  gp->indp = MemHandleLock(gp->indh);
  ErrFatalDisplayIf(!gp->indp, "indDBp ");

  currentString = 0;
  for (resourceNum = 0; gp->strh[resourceNum] != 0; resourceNum++) { 
    Word numStrings = strp[resourceNum]->numEntries;
    VoidHand stringH;
    char *stringP;
    stringH = SysFormPointerArrayToStrings(strp[resourceNum]->data, numStrings);
    stringP = MemHandleLock(stringH);
    DmWrite(gp->indp, currentString*(sizeof(char*)),
	stringP,MemPtrSize(stringP)   ); 
    currentString += numStrings;
    MemHandleUnlock(stringH);
    MemHandleFree(stringH);
  }
  newSort(gp, 0);

  //initialize statistics structure
  gp->TestDB =DmOpenDatabaseByTypeCreator(TestDBType,TestAppID,dmModeReadOnly);
  gp->stath = DmGetResource('STAT', 0);
  if (gp->stath) 
    gp->statp = MemHandleLock(gp->stath);
  else
    gp->statp = 0;
 
  gp->r.topLeft.x = DRAWTOPX;
  gp->r.topLeft.y = DRAWTOPY;
  gp->r.extent.x = DRAWEXTENTX;
  gp->r.extent.y = DRAWEXTENTY;
  
  gp->bmheader.width = 16;
  gp->bmheader.height= 16;
  gp->bmheader.rowBytes=2;
  gp->bmheader.flags.compressed =  0;
  return 0;
}

static void StopApplication (GlobalData_t *gp) {
  Word i; 

  for(i=0; gp->strh[i]; i++) { 
	MemHandleUnlock(gp->strh[i]); DmReleaseResource(gp->strh[i]); 
  }
  for(i=0; gp->bith[i]; i++) { 
	MemHandleUnlock(gp->bith[i]); DmReleaseResource(gp->bith[i]); 
  }
  if(gp->kandh){ MemHandleUnlock(gp->kandh);DmReleaseResource(gp->kandh); }
  if(gp->indh) { MemHandleUnlock(gp->indh); MemHandleFree(gp->indh); }
  if(gp->sorth){ MemHandleUnlock(gp->sorth);MemHandleFree(gp->sorth); }
  if(gp->stath){ MemHandleUnlock(gp->stath); DmReleaseResource(gp->stath); }
  
  if (gp->stateh) {  
    VoidPtr statep = MemHandleLock(gp->stateh);
    DmWrite(statep, 0, &gp->state, sizeof(gp->state) );
    MemHandleUnlock(gp->stateh);
    DmReleaseResource(gp->stateh);
  }
  DmResetRecordStates(gp->KanDB);
  DmResetRecordStates(gp->TestDB);
  DmCloseDatabase(gp->KanDB);
  DmCloseDatabase(gp->TestDB);
}
 
static short FindRow(Word x, Word y) {
  short row, col, val;
  if (x<DRAWTOPX || (x-DRAWTOPX)>=DRAWEXTENTX
     ||y<DRAWTOPY|| (y-DRAWTOPY)>=DRAWEXTENTY) return -1;
  x -= DRAWTOPX; y -= DRAWTOPY;
  row = y/RowHeight();
  ErrFatalDisplayIf(g.state.visCols==0, "0 cols");
  col = x/(DRAWEXTENTX / g.state.visCols);
  if (row >= VisRows() || col >= g.state.visCols) return -1;
  val = (unsigned short) g.state.top + row + VisRows() * col;
  return (val < g.numIndices) ? val : -1; 
}

static void WriteInRect(RectanglePtr r, char *c) {
  Boolean fits;
  SWord usedLen, width;
  Word *attr;

  attr = GetCharAttr();
  width = r->extent.x;
  while(!IsAlNum(attr,*c) && *c) 
    c++; // skip over non-characters
  usedLen = StrLen(c);
  FntCharsInWidth(c, &width, &usedLen, &fits);
  while (IsAlNum(attr,c[usedLen]) && usedLen >0) 
    usedLen--;  //print whole words 
  WinDrawChars(c, usedLen, r->topLeft.x, r->topLeft.y);

  c = &c[usedLen];
  while(!IsAlNum(attr,*c) && *c) 
    c++; // skip over non-characters
  width = r->extent.x;
  usedLen = StrLen(c);
  FntCharsInWidth(c, &width, &usedLen, &fits);
  while (IsAlNum(attr,c[usedLen]) && usedLen >0) 
    usedLen--;  //print whole words 
  WinDrawChars(c, usedLen, r->topLeft.x, r->topLeft.y + RowHeight()/2);
}

static void DrawKanji(RectanglePtr r, Word rows, Word cols)
{
  KanjiData_t * kp = g.kandp; 
  char* strstart;
  FontID oldFont;
  Word usedLen, rowHeight = RowHeight();
  unsigned short i, j;
  RectangleType bounds;

  void invert(void) {
    bounds.topLeft.y--; bounds.extent.y+=2;
    WinInvertRectangle(&bounds,0);
    bounds.topLeft.y++;
  } //local function invert
  void invertR(void) {
    WinDrawGrayLine(bounds.topLeft.x, bounds.topLeft.y-1, 
      bounds.topLeft.x +bounds.extent.x, bounds.topLeft.y-1); 
  } //local function invert
   
  oldFont = FntSetFont(stdFont);
  WinEraseRectangle(r, 0);
 
  ErrFatalDisplayIf(cols >10, "Too many cols");
  for(i=0; i<=cols; i++) {
    for(j=0; j<=rows; j++) {
      Word row = j + (rows+1) * i + g.state.top; // index to use into file
      if (row >= g.numIndices) continue;
      bounds.topLeft.x = r->topLeft.x + ( i * r->extent.x) /(cols+1);
      bounds.topLeft.y = r->topLeft.y + ( j * rowHeight) +1;
      bounds.extent.x = 16;
      bounds.extent.y = 16;
      MyDrawBitmap( row, bounds.topLeft.x, bounds.topLeft.y );
   
      if (g.statp) switch(g.state.sort) {
        case SORT_FREQ: 
	  if( row < g.statp->numKanji 
          && g.statp->track[row].record <240 ) invert();
          break;
        case SORT_SPAHN:
          if( row == 0 
            ||(SORTVAL_SPAHN(g.sortp[row])/30) 
	      != SORTVAL_SPAHN(g.sortp[row-1])/30)
            invert(); 
          else if (SORTVAL_SPAHN(g.sortp[row])!=SORTVAL_SPAHN(g.sortp[row-1]))
            invertR();
	    break;
        case SORT_GRADE:
          if( row == 0 
            ||(SORTVAL_GRADE(g.sortp[row])!=SORTVAL_GRADE(g.sortp[row-1])))
            invert(); 
          else if (SORTVAL_COUNT(g.sortp[row])!=SORTVAL_COUNT(g.sortp[row-1]))
            invertR();
            break;
        case SORT_SKIP:  
          if( row == 0 
            ||(SORTVAL_SKIP(g.sortp[row])/32!=SORTVAL_SKIP(g.sortp[row-1])/32)) 
            invert(); 
          else if (SORTVAL_SKIP(g.sortp[row])!=SORTVAL_SKIP(g.sortp[row-1]))
            invertR();
            break;
        case SORT_COUNT: 
          if( row == 0 
            ||(SORTVAL_COUNT(g.sortp[row])!=SORTVAL_COUNT(g.sortp[row-1]))) 
            invert(); 
          else if (SORTVAL_BUSHU(g.sortp[row])!=SORTVAL_BUSHU(g.sortp[row-1]))
            invertR();
            break;
        case SORT_BUSHU: 
          if( row == 0 
            ||(SORTVAL_BUSHU(g.sortp[row])!=SORTVAL_BUSHU(g.sortp[row-1]))) 
            invert(); 
          else if (SORTVAL_COUNT(g.sortp[row])!=SORTVAL_COUNT(g.sortp[row-1]))
            invertR();
            break;
	default:
      }
      bounds.topLeft.x += rowHeight; 
      bounds.extent.x =  r->extent.x / (cols+1) - rowHeight;
      bounds.extent.y =  rowHeight * 2;
      if (bounds.extent.x < 10) continue;

      strstart = STRP(row); 
      usedLen = StrLen(strstart);

      WriteInRect(&bounds, strstart);
    }
  }
  FntSetFont(oldFont);
}
 
static void SendUserUpdate(void) {
  EventType event;

  event.eType = USERUPDATE;
  EvtAddEventToQueue (&event);
}

static Boolean defHandleEvent(EventPtr e) {
  FormType *pfrm;
  FieldType *pfld; 
  
  switch (e->eType) {
    case frmOpenEvent:
      pfrm=FrmGetActiveForm(); 
      pfld = FrmGetObjectPtr(pfrm, FrmGetObjectIndex(pfrm, defDef));
      FrmDrawForm(pfrm);
      return true;

    case frmGotoEvent:
      pfrm=FrmGetActiveForm(); 
      pfld = FrmGetObjectPtr(pfrm, FrmGetObjectIndex(pfrm, defDef));
      if (e->data.frmGoto.recordNum < 0 
          || e->data.frmGoto.recordNum >= g.numIndices) 
        return false;
      FldSetTextPtr(pfld, STRP(e->data.frmGoto.recordNum) );
      FldRecalculateField(pfld, true);

      MyDrawBitmap( e->data.frmGoto.recordNum, 72, 10); 
      { char buff[10];
        KanjiData_t* kp = g.kandp;
        int val,val1,val2,val3;
        val= SORTVAL_COUNT(g.sortp[e->data.frmGoto.recordNum]);
	buff[0] = 'c'; StrIToA(&buff[1], val);
        WinDrawChars(buff, StrLen(buff), 0,128);
        val= SORTVAL_GRADE(g.sortp[e->data.frmGoto.recordNum]);
	buff[0]= 'g'; StrIToA(&buff[1], val);
        WinDrawChars(buff, StrLen(buff), 20,128);
        val= SORTVAL_BUSHU(g.sortp[e->data.frmGoto.recordNum]);
	buff[0] ='b'; StrIToA(&buff[1], val);
        WinDrawChars(buff, StrLen(buff), 40,128);

        val= SORTVAL_SKIP(g.sortp[e->data.frmGoto.recordNum]);
        val1= val/1024; StrIToA(buff, val1);
        StrCat(buff,"-");
        val2= (val&1023)/32; StrIToA(&buff[StrLen(buff)], val2);
        StrCat(buff,"-");
        val3 =val&31;   StrIToA(&buff[StrLen(buff)],val3);
        WinDrawChars(buff, StrLen(buff), 65,128);

        val= SORTVAL_SPAHN(g.sortp[e->data.frmGoto.recordNum]);
        val1= val/600; StrIToA(buff, val1);
        val2= (val%600)/30; buff[StrLen(buff)+1] = 0;
        buff[StrLen(buff)] = 'a'+val2;
        val3 =val%30;   StrIToA(&buff[StrLen(buff)],val3);
        WinDrawChars(buff, StrLen(buff), 100,128);

        val= SORTVAL_FREQ(g.sortp[e->data.frmGoto.recordNum]);
	buff[0] ='f'; StrIToA(&buff[1], val);
        WinDrawChars(buff, StrLen(buff), 130,128);
      }
      return true;

    case frmCloseEvent:
      pfrm=FrmGetActiveForm(); 
      pfld = FrmGetObjectPtr(pfrm, FrmGetObjectIndex(pfrm, defDef));
      FldSetTextPtr(pfld, "");
      FldRecalculateField(pfld,false);
      return false;

    case ctlSelectEvent:
      FrmGotoForm(mainForm);
      return false;

    case nilEvent:
      //if (e->penDown) return false;
      //FrmGotoForm(mainForm);
      ////SendUserUpdate();
      return false;
    default:
  }

  return false;
}

static void GoToDefinition(short row) {
  EventType event;

  FrmGotoForm(defForm);

  MemSet (&event, sizeof(EventType), 0);
  event.eType = frmGotoEvent;
  event.data.frmGoto.recordNum = row;
  event.data.frmGoto.formID = defForm;
  EvtAddEventToQueue (&event);
}

static Boolean mainHandleEvent(EventPtr e) {
  static FormType *pfrm;
  FieldType *pfld; 

  short row;
  Boolean result;

  switch (e->eType) {
    case USERUPDATE:
    case frmOpenEvent:
      pfrm=FrmGetActiveForm(); 
      FrmDrawForm(pfrm);
    case frmUpdateEvent:
      pfld = FrmGetObjectPtr(pfrm, FrmGetObjectIndex(pfrm, fieldRecNo));
      StrIToA(g.rowStr, g.state.top);
      FldSetTextPtr(pfld, g.rowStr);
      FldDrawField(pfld);

      // OK, draw everything from here./
      DrawKanji(&g.r, VisRows()-1, g.state.visCols-1); 
      return true;

    case appStopEvent:
    case frmCloseEvent:
      return false; //NOTE--let manager deallocate memory 

    case frmGotoEvent:
      g.state.top   = e->data.frmGoto.recordNum;
      GoToDefinition(e->data.frmGoto.recordNum);
      return true;

    case penDownEvent:
      row = FindRow( e->screenX, e->screenY );     
      if (row < 0) return false;
      GoToDefinition(row);
      return true;

    case menuEvent:
      switch(e->data.menu.itemID) {
        case menuItemNoSort ... menuItemConfig: 
	  g.state.sort = e->data.menu.itemID - menuItemNoSort;
          newSort(&g, g.state.sort);  
          break;
        case menuItem1col: g.state.visCols=1; break;
        case menuItem2col: g.state.visCols=2; break;
        case menuItem8col: g.state.visCols=8; break;
        case menuItem9col: g.state.visCols=9; break;
        case menuItem10col:g.state.visCols=10; break;
        case menuItemAbout:
	  FrmAlert(mainInfo);
	  break;
        default:
          return false;
      }
      SendUserUpdate();
      return true;
    case keyDownEvent:
      switch(e->data.keyDown.chr) {
	  case pageUpChr:
            g.state.top = g.state.top ? g.state.top -VisRows()*g.state.visCols:
		(g.numIndices -1)/(VisRows()*g.state.visCols)*(VisRows()*g.state.visCols);
            result = true;
            break;
	  case pageDownChr:
            g.state.top+=(g.state.visCols*VisRows()); 
	    result = true;
            break;
	  default: 
	    return false; 
	    break;
	}
      if (  g.state.top <0) g.state.top= 0;   
      if (g.state.top >= g.numIndices) g.state.top = 0;
      SendUserUpdate();
      return result;
    default:
  }
 return false;
}

static void FindLaunch(FindParamsPtr findParams, Word launchFlags)
{
  Word done;
  unsigned short i;
  LocalID dbID;
  UInt cardNo = 0;
  RectangleType r; 
  FontID currFont; 
  char* tempstrp;
  char* strToFind;
  UInt width, textLen;
  Boolean fits;
  unsigned short startrecord; 
  GlobalData_t *lg;  //local pointer to "global" data

  if (launchFlags & sysAppLaunchFlagSubCall) {
    CALLBACK_PROLOGUE
    lg = &g;
    CALLBACK_EPILOGUE
  } else {
    return; //BUG: only until locking has been fixed.
    lg = MemPtrNew(sizeof(GlobalData_t));
    if (!lg) { 
      findParams->more = false; 
      return;
    } 
    MemSet (lg, sizeof(GlobalData_t), 0);
    lg->state.sort = SORT_NONE;
    lg->state.top  = 0;
    StartApplication(lg);
  }
  DmOpenDatabaseInfo(lg->KanDB, &dbID, 0, 0, &cardNo, 0);

  strToFind = findParams->strAsTyped;
  done = FindDrawHeader (findParams, "KanjiTable");
  if (done) goto Exit;    // There was no more room to display

  startrecord= findParams->recordNum; 
  for(i=startrecord; i< lg->numIndices && !done; i++) {
    tempstrp = lg->indp[lg->sortp[i]];
    if (!StrStr(tempstrp, strToFind) ) continue;

    done =FindSaveMatch(findParams, i, 0, 0, 0, cardNo, dbID);
    if (done) continue;
    FindGetLineBounds (findParams, &r);
    r.topLeft.x++;
    r.extent.x--;

    currFont = FntSetFont (stdFont);
    // Determine the length of text that will fit within the bounds.
    width = r.extent.x - 2;
    textLen = StrLen(tempstrp);
    FntCharsInWidth(tempstrp, &width, &textLen, &fits);
      
    // Now draw the text from the record.
    WinDrawChars(tempstrp, textLen, r.topLeft.x, r.topLeft.y);
        
    // Restore the font.
    FntSetFont (currFont);
    findParams->lineNumber++;
  } 
  Exit:

  if (~launchFlags & sysAppLaunchFlagSubCall) 
    StopApplication(lg);
}

static void GoToRecord(GoToParamsPtr goToParams) {
  EventType event;

 //Close all forms???

  MemSet (&event, sizeof(EventType), 0);

  event.eType = frmLoadEvent;
  event.data.frmLoad.formID = mainForm;
  EvtAddEventToQueue (&event);
 
  event.eType = frmGotoEvent;
  event.data.frmGoto.recordNum = goToParams->recordNum;
  event.data.frmGoto.matchPos = goToParams->matchPos;
  event.data.frmGoto.matchLen = goToParams->searchStrLen;
  event.data.frmGoto.matchFieldNum = goToParams->matchFieldNum;
  event.data.frmGoto.matchCustom = goToParams->matchCustom;
  event.data.frmGoto.formID = mainForm;
  EvtAddEventToQueue (&event);
}

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 mainForm:
        FrmSetEventHandler(pfrm, mainHandleEvent);
        break;
       case defForm:
        FrmSetEventHandler(pfrm, defHandleEvent);
       break;
      }
      continue;
    }
    FrmDispatchEvent(&e);
  } while (e.eType != appStopEvent);
}
DWord PilotMain(Word cmd, Ptr cmdPBP, Word launchFlags)
{
  Word error;
  switch (cmd) {
   case sysAppLaunchCmdNormalLaunch:
      //MemSetDebugMode(0 /* memDebugModeFillFree */ );
      MemSet (&g, sizeof(GlobalData_t), 0);
      if ( (error = StartApplication(&g) )) return error;
      newSort(&g, g.state.sort);
      FrmGotoForm(mainForm);
      EventLoop();
      StopApplication(&g);	
      break;
    case sysAppLaunchCmdFind: 
      MemSetDebugMode(memDebugModeFillFree );
      FindLaunch( (FindParamsPtr) cmdPBP, launchFlags) ;
      break;
    case sysAppLaunchCmdGoTo: 
      MemSetDebugMode(memDebugModeFillFree );
      if (launchFlags & sysAppLaunchFlagNewGlobals) {
        MemSet (&g, sizeof(GlobalData_t), 0);
        if ( (error = StartApplication(&g) )) return error;
        FrmGotoForm(mainForm);
        GoToRecord( (GoToParamsPtr) cmdPBP);
        EventLoop();
        StopApplication(&g);
      } else {
        // send the event, then exit.
        GoToRecord( (GoToParamsPtr) cmdPBP); 
      }
      break;
    default:
  }
  return 0;
}
