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

Palm Chess Client
Copyright (C) 1999 David Barr (davidbarr@iname.com)

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

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

#define SQUARESIZE 12 /* the size of the bitmaps in pixels */
#define BOARDTOP   14 /* where on the screen to draw the board */
#define USERFONTID 129 /* the fontid of the font used for messages */
#define FONTHEIGHT 8   /* the height of the font used for message */

/* Main code for chesscli */

#include <Pilot.h>
#include "callback.h"
#include "tcpip.h"

#include "chesscliRsc.h"

Err errno;

char currentline[256]; /* the text we just read from the server */
char currentboard[256]; /* the last currentline that began with <12> */
char initstr[256]; /* initialization commands to send to the server */
Word sock = -1; /* the socket number of our connection to the server */
int MvTmWh=-1; /* last clock time that white moved */
int MvTmBl=-1; /* last clock time that black moved */
int BlToMv=-1; /* whether or not it is black's turn to move */
ULong MvTmLast=-1; /* last system time that either side moved */
int clientflip=0; /* whether or not the board is flipped on the client */
int serverflip=0; /* whether or not the board is flipped on the server */
/* string used to decode style 12 */
static char piecelabels[] = "-PRNBQKprnbqk";
#define NSAVELINES 16
char savelines[NSAVELINES][81];
BitmapPtr bitmapP[7*2*2];

void
putstr(char *s) {
  int wintop, nlines, i;
  RectangleType r, v;
  FormPtr frm;

  frm = FrmGetActiveForm();
  if(frm->formId == BoardForm) {
    nlines = 6;
  } else {
    nlines = NSAVELINES;
  }


  wintop=160-FONTHEIGHT*nlines;
  
  r.topLeft.x = 0;
  r.topLeft.y = wintop;
  r.extent.x = 160;
  r.extent.y = nlines*FONTHEIGHT;

  v.topLeft.x = 0;
  v.topLeft.y = wintop+(nlines-1)*FONTHEIGHT;
  v.extent.x = 160;
  v.extent.y = FONTHEIGHT;
  
  WinScrollRectangle(&r, up, FONTHEIGHT, &v);
  WinEraseRectangle(&v, 1);
  FntSetFont(USERFONTID);
  WinDrawChars(s, strlen(s), 0, wintop+(nlines-1)*FONTHEIGHT);
  for(i=0; i<(NSAVELINES-1); i++) {
    strcpy(savelines[i], savelines[i+1]);
  }
  if(strlen(s) <= 40) {
    strcpy(savelines[i], s);
  } else {
    strncpy(savelines[i], s, 40);
    savelines[i][80] = '\0';
    putstr(s+40);
  }
}

struct NetSettings_s {
  char host[80];
  char port[80];
  char user[80];
  char pass[80];
};

typedef struct NetSettings_s NetSettings_t;
NetSettings_t NetSettings;

static DmOpenRef ccDB;

static Err OpenDatabase(void)
{
  UInt          index = 0;
  VoidHand      RecHandle;
  VoidPtr       RecPointer;
  Err           err;

  /* Create database, if it doesn't exist, and save default settings status. */
  if (!(ccDB = DmOpenDatabaseByTypeCreator('Data', 'CHCL', dmModeReadWrite))) {
    if ((err = DmCreateDatabase(0, "ChessClientDB", 'CHCL', 'Data', false)))
      return err;
    ccDB = DmOpenDatabaseByTypeCreator('Data', 'CHCL', dmModeReadWrite);

    RecHandle = DmNewRecord(ccDB, &index, sizeof(NetSettings_t));
    DmWrite(MemHandleLock(RecHandle), 0, &NetSettings, sizeof(NetSettings_t));
    MemHandleUnlock(RecHandle);
    DmReleaseRecord(ccDB, index, true);
  }

  // Load a saved game status.
  RecHandle = DmGetRecord(ccDB, 0);
  RecPointer = MemHandleLock(RecHandle);
  MemMove(&NetSettings, RecPointer, sizeof(NetSettings_t));
  MemHandleUnlock(RecHandle);
  DmReleaseRecord(ccDB, 0, true);

  return 0;
}

/*
 * Save game status information.
 */
static void SaveStatus()
{
  VoidPtr p = MemHandleLock(DmGetRecord(ccDB, 0));
  //game.seconds = TimGetSeconds() - GameStartedAt;
  DmWrite(p, 0, &NetSettings, sizeof(NetSettings_t));
  MemPtrUnlock(p);
  DmReleaseRecord(ccDB, 0, true);
}

void
handlePenDown(int x, int y) {
  static int prevr=-1, prevc=-1;
  int r, c;
  char move[32];
  int flip;

  flip = clientflip ^ serverflip;
  c = x/SQUARESIZE;
  r = 7-(y-BOARDTOP)/SQUARESIZE;
  if(flip) {
    c = 7-c;
    r = 7-r;
  }
  StrPrintF(move, "square = %c%d", "abcdefgh"[c], r+1);
  //putstr(move);
  if(c>=0&&c<=7&&r>=0&&r<=7) {
    if(prevr != -1) {
      StrPrintF(move, "%c%d-%c%d\n", "abcdefgh"[prevc], prevr+1, "abcdefgh"[c], r+1);
      SendData(sock, move, strlen(move));
      prevr=-1;
      prevc=-1;
    } else {
      prevr = r;
      prevc = c;
    }
  } else {
    prevr=-1;
    prevc=-1;
  }
}

static FieldPtr SetFieldTextFromHandle(FormPtr frm, Word fieldID, Handle txtH) {
  Handle oldTxtH;
  FieldPtr fldP;
  Word objIdx;

  objIdx = FrmGetObjectIndex(frm, fieldID);
  fldP = FrmGetObjectPtr(frm, objIdx);
  oldTxtH = FldGetTextHandle(fldP);
  FldSetTextHandle(fldP, txtH);
  FldDrawField(fldP);
  if(oldTxtH) {
    MemHandleFree((VoidHand)oldTxtH);
  }
  return fldP;
}

static FieldPtr SetFieldTextFromStr(FormPtr frm, Word fieldID, CharPtr strP) {
  Handle txtH;

  txtH = MemHandleNew(StrLen(strP) + 1);
  if(!txtH) {
    return NULL;
  }
  strcpy(MemHandleLock(txtH), strP);
  MemHandleUnlock(txtH);
  return SetFieldTextFromHandle(frm, fieldID, txtH);
}

static void updateClocks() {
  char TmWhSt[16], TmBlSt[16];
  ULong CurrTm = TimGetSeconds();
  int TmWh = MvTmWh, TmBl = MvTmBl;
  FormPtr frm = FrmGetActiveForm();
  int flip;

  flip = clientflip ^ serverflip;
  
  if(frm->formId != BoardForm) {
    return;
  }
  if(MvTmLast == ((ULong)-1)) {
    return;
  }
  if(BlToMv) {
    TmBl -= (CurrTm - MvTmLast);
  } else {
    TmWh -= (CurrTm - MvTmLast);
  }
  StrPrintF(TmWhSt, "%d:%02d", TmWh/60, TmWh%60);
  StrPrintF(TmBlSt, "%d:%02d", TmBl/60, TmBl%60);
  SetFieldTextFromStr(frm, UpTimeLbl,   flip ? TmWhSt : TmBlSt);
  SetFieldTextFromStr(frm, DownTimeLbl, flip ? TmBlSt : TmWhSt);
}

void
updateBoard(int newdata) {
  char tmpstr[256];
  char *fields[48], *whitename, *blackname;
  int i, nfields=0, cll, j, k, bmpno, labellen, row, col;
  int flip;

  FormPtr frm;

  if(newdata) {
    MvTmLast = TimGetSeconds();
    strcpy(currentboard, currentline);
  }

  frm = FrmGetActiveForm();
  
  if(frm->formId != BoardForm) {
    return;
  }

  strcpy(tmpstr, currentline);
  cll = strlen(tmpstr);
  for(i=0; i<cll; i++) {
    if(tmpstr[i] == ' ') {
      fields[nfields++] = &tmpstr[i+1];
      tmpstr[i] = 0;
    }
  }

  whitename = fields[16];
  blackname = fields[17];
  MvTmWh = atoi(fields[23]);
  MvTmBl = atoi(fields[24]);
  serverflip = atoi(fields[29]);

  flip = clientflip ^ serverflip;

  BlToMv = (fields[8][0] == 'B'); /* see who's turn it is */

  SetFieldTextFromStr(frm, UpPlayerLbl, flip ? whitename : blackname);
  SetFieldTextFromStr(frm, DownPlayerLbl, flip ? blackname : whitename);
  updateClocks();

  labellen = strlen(piecelabels);
  for(i=0; i<8; i++) {
    row = i;
    if(flip) {
      row = 7-row;
    }
    for(j=0; j<8; j++) {
      col = j;
      if(flip) {
	col = 7-col;
      }
      for(k=0; k<labellen; k++) {
	if(fields[i][j] == piecelabels[k]) {
	  break;
	}
      } /* end for k */
      bmpno = 0x2000 + k * 0x10;
      if((i+j)%2) {
	bmpno++;
      }
      //saveno = (pieceside*7+piecetype)*2+background;
      WinDrawBitmap (bitmapP[k*2+((i+j)%2)], col*SQUARESIZE,
		     row*SQUARESIZE+BOARDTOP);
    } /* end for j */
  } /* end for i */
} /* end updateBoard */

static Boolean BoardFormHandleEvent (EventPtr e) {
  Boolean handled = false;
  FormPtr frm;
  int wintop, i;
    
  CALLBACK_PROLOGUE

    switch (e->eType) {
    case frmOpenEvent:
      frm = FrmGetActiveForm();
      FrmDrawForm(frm);
      if(strlen(currentboard)) {
	strcpy(currentline, currentboard);
	updateBoard(0);
      }
      wintop=160-FONTHEIGHT*6;
      for(i=0; i<6; i++) {
	WinDrawChars(savelines[i+10], strlen(savelines[i+10]), 0,
		     wintop+i*FONTHEIGHT);
      }
      handled = true;
      break;

    case menuEvent:
      MenuEraseStatus(NULL);

      switch(e->data.menu.itemID) {
      }

      handled = true;
      break;

    case ctlSelectEvent:
      switch(e->data.ctlSelect.controlID) {
      case ToMessagesBtn:
	FrmGotoForm(MessagesForm);
	break;
      case ToLoginBtn:
	FrmGotoForm(LoginForm);
	break;
      }
      break;

    case nilEvent:
      break;

    case penDownEvent:
      handlePenDown(e->screenX, e->screenY);
      break;

    case penUpEvent:
      break;

    case penMoveEvent:
      break;

    case ctlEnterEvent:
      break;

    default:
      //StrPrintF(debugstr, "main form event type = %d", e->eType);
      //putstr(debugstr);  /* 0 1 2 7 9 */
      break;
    }

  CALLBACK_EPILOGUE

    return handled;
}

static Boolean LoginFormHandleEvent (EventPtr e) {
  Boolean handled = false;
  static FormPtr frm;
  char *serverPtr, *portPtr, *userPtr, *passPtr, nullStr[1];
    
  CALLBACK_PROLOGUE

    switch (e->eType) {
    case frmOpenEvent:
      frm = FrmGetActiveForm();
      SetFieldTextFromStr(frm, ServerFld, NetSettings.host);
      SetFieldTextFromStr(frm, PortFld, NetSettings.port);
      SetFieldTextFromStr(frm, UserFld, NetSettings.user);
      SetFieldTextFromStr(frm, PassFld, NetSettings.pass);
      FrmDrawForm(frm);
      handled = true;
      break;

    case menuEvent:
      MenuEraseStatus(NULL);

      switch(e->data.menu.itemID) {
      }

      handled = true;
      break;

    case ctlSelectEvent:
      switch(e->data.ctlSelect.controlID) {
      case ConnectBtn:
	strcpy(nullStr, "");
	serverPtr = FldGetTextPtr(FrmGetObjectPtr(frm, FrmGetObjectIndex(frm, ServerFld)));
	portPtr   = FldGetTextPtr(FrmGetObjectPtr(frm, FrmGetObjectIndex(frm, PortFld)));
	userPtr   = FldGetTextPtr(FrmGetObjectPtr(frm, FrmGetObjectIndex(frm, UserFld)));
	passPtr   = FldGetTextPtr(FrmGetObjectPtr(frm, FrmGetObjectIndex(frm, PassFld)));
	if(!serverPtr) { serverPtr = nullStr; }
	if(!portPtr)   { portPtr = nullStr; }
	if(!userPtr)   { userPtr = nullStr; }
	if(!passPtr)   { passPtr = nullStr; }
	StrCopy(NetSettings.host, serverPtr);
	StrCopy(NetSettings.port, portPtr);
	StrCopy(NetSettings.user, userPtr);
	StrCopy(NetSettings.pass, passPtr);
	sock = Connect(serverPtr, portPtr, "tcp");
	strcpy(currentline, "");
	StrPrintF(initstr, "%s\n%s\nset style 12\n", userPtr, passPtr);
	FrmGotoForm(BoardForm);
      }
      break;

    case nilEvent:
      break;

    case penDownEvent:
      break;

    case penUpEvent:
      break;

    case ctlEnterEvent:
      break;

    case keyDownEvent:
      break;

    default:
      break;
    }

  CALLBACK_EPILOGUE
  return handled;
}

static Boolean MessagesFormHandleEvent (EventPtr e) {
  Boolean handled = false;
  FormPtr frm;
  int wintop, i;
    
  CALLBACK_PROLOGUE

    switch (e->eType) {
    case frmOpenEvent:
      frm = FrmGetActiveForm();
      FrmDrawForm(frm);
      wintop=160-FONTHEIGHT*NSAVELINES;
      for(i=0; i<NSAVELINES; i++) {
	WinDrawChars(savelines[i], strlen(savelines[i]), 0,
		     wintop+(i)*FONTHEIGHT);
      }
      handled = true;
      break;

    case menuEvent:
      MenuEraseStatus(NULL);

      switch(e->data.menu.itemID) {
      }

      handled = true;
      break;

    case ctlSelectEvent:
      switch(e->data.ctlSelect.controlID) {
      case ToBoardBtn:
	FrmGotoForm(BoardForm);
      }
      break;

    case nilEvent:
      break;

    case penDownEvent:
      break;

    case penUpEvent:
      break;

    case ctlEnterEvent:
      break;

    case keyDownEvent:
      if (e->data.keyDown.chr == '\n') {
	Word focus;
        FormPtr frm;
	FieldPtr fld;
	
        frm = FrmGetActiveForm ();
	focus = FrmGetFocus (frm);
	fld = FrmGetObjectPtr(frm, focus);
	
        StrPrintF(currentline, "%s\n", FldGetTextPtr(fld));
	 
	SendData(sock, currentline, strlen(currentline));
	handled = true;
      }
      break;

    default:
      //StrPrintF(debugstr, "main form event type = %d", e->eType);
      //putstr(debugstr);  /* 0 1 2 7 9 */
      break;
    }

  CALLBACK_EPILOGUE

    return handled;
}

void mydebug(int lineno) {
  // do nothing
}

static Boolean ApplicationHandleLine() {
  int i;
  
  //strcpy(debugstr, currentline);
  //mydebug(__LINE__);
  for(i=0; i<5; i++) {
    if("<12> "[i] != currentline[i]) {
      break;
    }
  }
  if(i==5) {
    updateBoard(1);
  } else {
    putstr(currentline);
  }
  return true;
}

static Boolean ApplicationHandleData() {
  Err i;
  int cll, j;
  static int sentinitstr=0;

  if(!sentinitstr) {
    SendData(sock, initstr, strlen(initstr));
    sentinitstr=1;
  }

  //printf("trying ReceiveData...\n");
  cll = strlen(currentline);
  if(cll == 255) {
    strcpy(currentline, "");
    cll = 0;
  }
  i = NetLibReceive( AppNetRefnum,
		     sock,
		     ReceiveBuffer,
		     256,
		     0, // flags
		     &Sender,
		     &SenderLength,
		     -1,
		     &error );
  if(!i) {
    //strcpy(debugstr, "remote side closed connection\n");
    //mydebug(__LINE__);
    Disconnect(sock);
    sock = -1;
  }
  for(j=0; j<i; j++) {
    if(ReceiveBuffer[j] == '\n' || ReceiveBuffer[j] == '\r') {
      if(cll) {
	ApplicationHandleLine();
	strcpy(currentline, "");
	cll = 0;
      }
      continue;
    }
    currentline[cll++] = ReceiveBuffer[j];
    currentline[cll] = 0;
  }
  return true;
}
  

static Boolean ApplicationHandleEvent(EventPtr e) {
  FormPtr frm;
  Word    formId;
  Boolean handled = false;

  if (e->eType == frmLoadEvent) {
    formId = e->data.frmLoad.formID;
    frm = FrmInitForm(formId);
    FrmSetActiveForm(frm);
    switch(formId) {
    case BoardForm:
        FrmSetEventHandler(frm, BoardFormHandleEvent);
        break;
    case MessagesForm:
        FrmSetEventHandler(frm, MessagesFormHandleEvent);
        break;
    case LoginForm:
        FrmSetEventHandler(frm, LoginFormHandleEvent);
        break;
    }
    handled = true;
	
    //printf("all done\n");
  }

  return handled;
}

/* Get preferences, open (or create) app database */
static Word StartApplication(void) {
  VoidHand fontHandle;
  FontType *fontPtr;
  int i, pieceside, piecetype, background, saveno, bmpno;
  VoidHand bitmapHandle;
  char tmpstr[64];

  for(i=0; i<NSAVELINES; i++) {
    savelines[i][0] = '\0';
  }

  putstr("trying CheckForNetwork...");
  if((i=CheckForNetwork())) {
    StrPrintF(tmpstr, "checkfornetwork failed (%d)", i);
    putstr(tmpstr);
  }
  
  // 'Font' and 0x3000 are resource type and ID, defined by you in
  // resource description
  fontHandle=DmGetResource('Font',0x3000); // minico 9
  fontPtr=MemHandleLock(fontHandle);
  // user defined fonts start from 129
  FntDefineFont(USERFONTID,fontPtr);

  // lock the piece bitmaps
  for(pieceside=0; pieceside<2; pieceside++) {
    for(piecetype=0; piecetype<=7; piecetype++) {
      for(background=0; background<2; background++) {
	bmpno  = 0x2000 + (pieceside * 0x9 + piecetype) * 0x10 + background;
	saveno = (pieceside*7+piecetype)*2+background;
	if((bitmapHandle = DmGetResource('Tbmp', bmpno))) {
	  bitmapP[saveno] = MemHandleLock(bitmapHandle);
	}
      }
    }
  }

  OpenDatabase();

  FrmGotoForm(LoginForm);
  return 0;
}

/* Save preferences, close forms, close app database */
static void StopApplication(void) {
  VoidHand fontHandle;
  FontType *fontPtr;
  VoidHand bitmapHandle;
  BitmapPtr bitmapP;
  int pieceside, piecetype, background, saveno, bmpno;

  FrmSaveAllForms();
  FrmCloseAllForms();

  // unlock our font handle
  fontHandle=DmGetResource('Font',0x3000);
  fontPtr=MemHandleUnlock(fontHandle);

  // unlock the piece bitmaps
  for(pieceside=0; pieceside<2; pieceside++) {
    for(piecetype=0; piecetype<=7; piecetype++) {
      for(background=0; background<2; background++) {
	bmpno  = 0x2000 + (pieceside * 0x9 + piecetype) * 0x10 + background;
	saveno = (pieceside*7+piecetype)*2+background;
	if((bitmapHandle = DmGetResource('Tbmp', bmpno))) {
	  MemHandleUnlock(bitmapHandle);
	  DmReleaseResource(bitmapHandle);
	}
      }
    }
  }

  if(sock != (Word)-1) {
    Disconnect(sock);
  }
  NetLibClose(AppNetRefnum, false);
  SaveStatus();
}

/* The main event loop */
static void EventLoop(void) {
  Word err;
  EventType e;
  NetFDSetType readFDs,writeFDs,exceptFDs, setsize=1;

  strcpy(currentboard, "");
  
  for(;;) {
    netFDZero(&readFDs);
    setsize = 1;
    netFDSet(sysFileDescStdIn, &readFDs);
    setsize = sysFileDescStdIn+1;
    if(sock != (Word)-1) {
      netFDSet(sock, &readFDs);
      if(sock >= setsize) {
	setsize = sock+1;
      }
      NetLibSelect(AppNetRefnum, setsize, &readFDs, &writeFDs, &exceptFDs,
		   sysTicksPerSecond, &err);
      if(netFDIsSet(sock, &readFDs)) {
	ApplicationHandleData();
      }
/*        if(!netFDIsSet(sysFileDescStdIn, &readFDs)) { */
/*  	continue; */
/*        } */
    }

    //EvtGetEvent(&e, evtWaitForever);
    EvtGetEvent(&e, 0);
    
    //printf("event = %d\n", e.eType);
    if (SysHandleEvent (&e)) {
      continue;
    }
    if (MenuHandleEvent (NULL, &e, &err)) {
      continue;
    }
    if (ApplicationHandleEvent (&e)) {
      continue;
    }
    
    FrmDispatchEvent (&e);
    
    if(e.eType == appStopEvent) {
      return;
    }
    
    updateClocks();
  }
}

DWord PilotMain(Word cmd, Ptr cmdPBP, Word launchFlags) {
  Word err;

  if (cmd == sysAppLaunchCmdNormalLaunch) {
    err = StartApplication();
    if (err) {
      return err;
    }

    EventLoop();
    StopApplication();
  } else {
    return sysErrParamErr;
  }

  return 0;
}
