/***********************************************************************
 *
 * $Id: code07d0.c,v 1.5 2002/12/08 17:42:59 mankoff Exp $
 *
 ***********************************************************************
 *
 * PURPOSE: This is the config form for the Schizophrenic hack. It is
 * called by X-Master when someone taps the "Configure" button. The
 * HackMaster protocol is not smart, and just jumps to the first
 * executable statement in this file. Therefore, all helper functions
 * have to come after the PrefsHandler function.
 *
 ***********************************************************************
 * 
 * Here is the planned behavior of the Schizophrenic Config form:
 *
 * o The USERNAME and USERID fields are used to enter the details of a
 *   new personality
 * o The DONE button behaves like this:
 *   - It exits the config form, not saving anything.
 * o The SAVE button behaves as follows:
 *   - Write personality in correct (alphabetic) order to the DB
 *   - Clear fields
 *   - redraw the list with the new DB
 *   - IF the fields (personality) is blank, use 0 and '\0'
 * o The DEL button behaves as follows:
 *   - Remove the selected personality from the DB
 *   - Clear fields
 *   - redraw the list with the new DB
 *   - if select app is not in DB, do nothing
 * o The LIST is designed like this:
 *   - First, in BOLD, are the applications in the Schizophrenic DB
 *     (i.e. the applications that have an associated personality).
 *   - Second, in regular font, are all remaining applications on the
 *     Palm that do not have an associated personality.
 *
 *      ----------------------   
 *     | App: \/Select App    |  <-- Drop-list of apps (\/ is TRIGGER)
 *     |                      |  
 *     | UserName: __________ |  <-- User data entry fields
 *     |   UserID: __________ |  <-- Numbers only > 0 < 65535
 *     |    ROMID: __________ |  <-- OS specific? Only show these
 *     |   OS ver: __________ |      fields if its available?
 *     |                      |  
 *     | Witty text goes here |  <-- Maybe a warning? Disclaimer?
 *     |   if there is room   |      Not sure what...
 *     | ____  ____  ___      |  
 *     | DONE  SAVE  DEL    I |  <-- Buttons
 *     |______________________|  
 *
 *
 * Layout issues:
 * PROS:
 *  o Allows viewing, adding, and deleting with only one form
 *  o The main purpose/use will be to ADD a new personality, not to
 *    view existing or delete current. This form is optimized for
 *    adding. Viewing existing is actually just a side-effect. In
 *    fact, the existing personalities don't have to be first, they
 *    can even be in-order (but BOLD to distinguish them).
 * CONS:
 *  o Is not optimized for viewing/deleting existing personalities
 *  o Because everything is in one form, there is some possibly of
 *    unexpected behavior. For example, if you enter a personality,
 *    but don't hit SAVE, and then pick a new app to enter a new
 *    personality, the previous one will NOT be SAVED.
 *
 ***********************************************************************
 *
 * Here is the planned format of the database:
 *
 * I have not decided if the database should be an external file, or
 * if I should use the SavedPreferences. I think it will probably be
 * an external file.
 *
 * Here are the main drivers for the design:
 * o Speed. Speed (repeated twice on purpose). The DB will be accessed
 *   every time ANY application starts. Therefore, it must be fast
 *   to find out if this application has a personality associated with
 *   it or not.
 * o Size. The necessary information is:
 *         04 bytes: CRID
 *         41 bytes: UserName (40 characters + null)
 *         16 bytes: UserID (2^16 = 65535)
 *         ?? bytes: ROMID  (maybe 12?. Lets say 20+null for now)
 *       + ?? bytes: OS string (no idea. Lets say 20+null for now)
 *         ---------
 *        103 bytes per personality.
 *   Its safe to assume that most USERNAME will be much less than 40
 *   characters. So, if we pack bytes, its even smaller. Finally, if
 *   we leave off the ROMID and OSString its even smaller.
 *
 * Palm (c) says "first optimize size, then speed", but since we are a
 * writing a HACK, I think we have good arguments for changing the
 * order.
 *
 * 99% of the time, an app will launch, and this app will NOT have an
 * associated personality. Therefore, the database design is optimized
 * for traversing only the 4 byte CRID field and comparing each with
 * the CRID of the app thats getting launched, with as few
 * instructions as possible. 
 *
 * One a matching CRID is found, its index is known, so it should be
 * an easy lookup to get the personality USERID. 
 *
 * Its here that packed USERNAME strings start to raise all sorts of
 * problems in terms of optimizing the CRID search algorithm. So, for
 * now, I think its just best to store unpacked strings. This section
 * of comments has lead to the PREFS struct found in the
 * schizophrenic.h header file.
 *
 */

#include <PalmOS.h>
#include "resource.h"
#include "schizophrenic.h"

static void InitConfig(FormType *frm);
static void SaveButtonPressed( void );
static void DelButtonPressed( void );
static void FirstTimeDB( void );
void get_set_palm_name(Boolean set, Char *palm_name, UInt16 *palm_id);
static void PrefsListDrawFunc(Int16 index, RectangleType *bounds,Char **data);

/* 
 * Functions I want:
 *
 * InitConfig() // initialize the configuration form
 *  - Draw form
 *  - Do initializations if this is the first time
 *    ex: set up the DB and warning bit, say hello to the user, etc.
 *  - Generate list and draw
 *
 * SaveButtonPressed() // handles when save button pressed
 *  - Insert personality to DB (update if already exists)
 *  - re-generate list
 *
 * DelButtonPresesd() // handles del button press event
 *  - checks if personality selected is in DB
 *  - deletes personality
 *  - re-generate list
 *
 * DoneButtonPressed() // done button press
 *  - cleans up memory
 *  - closes form
 *  - jumps to form 9000 (b/c HackMaster protocol says so)
 *
 * Other sub-functions:
 *
 * RegenerateList()
 *  - just changes an item from/to bold if everything is alphabetic,
 *    or does a lot more if personalities are first and apps second.
 *
 * PersonalityExists()
 *  - Checks if a personality is in the DB. This is used by SAVE to
 *    see if it needs to update or insert, and also by DEL to see if
 *    it was pressed with a personality loaded or not.
 *  - This could also be used by the hack to traverse the CRIDs ??
 * 
 */


Boolean PrefsHandler(EventType *event)
{
  FormType  *form;
  Boolean   handled = false;
  
  switch (event->eType)
    {
	case frmOpenEvent:
	  form = FrmGetActiveForm();
	  InitConfig(form);
	  FrmDrawForm(form);
	  handled=true;
	  break;
	case ctlSelectEvent: 
	  switch (event->data.ctlEnter.controlID)
		{
		case btnDel:
		  DelButtonPressed();
		  handled=true;
		  break;
		case btnSave:
		  SaveButtonPressed();
		  handled=true;
		  break;
		case btnDone:
		  FrmCloseAllForms();
		  FrmGotoForm(9000);  // return to HackMaster control panel
		  handled=true;
		  break;
		default:
		  break;
		}
	  break;
	default:
	  break;
    }
  return handled;
}


static void DelButtonPressed()
{
  /* fill me in */
  UInt16 response;
  //response = FrmCustomAlert( 2000, NULL, NULL, NULL );
  response = FrmAlert( alertDelete );
}

static void SaveButtonPressed()
{
  // Form stuff
  FormType *form=FrmGetActiveForm();
  FieldType *field;
  MemHandle textH = NULL;
  Char *c;
  // DB stuff
  DmOpenRef db_ref;
  MemHandle recH = NULL;
  MemPtr p;
  // misc
  Err err;
  Prefs *prefs;
  // end of vars

  prefs = MemPtrNew( sizeof( Prefs ) );
  // populate the prefs struct from the form

  // get app name from form
  /*
  field=FrmGetObjectPtr(form,FrmGetObjectIndex(form,configFieldApp));
  textH = FldGetTextHandle( field );
  c = (Char *)MemHandleLock( textH );
  StrCopy( prefs->application, c );
  */

  // get user name from form
  field=FrmGetObjectPtr(form,FrmGetObjectIndex(form,configFieldName));
  textH = FldGetTextHandle( field );
  c = (Char *)MemHandleLock( textH );
  StrCopy( prefs->userName, c );

  // get user ID from form
  field=FrmGetObjectPtr(form,FrmGetObjectIndex(form,configFieldUser));
  textH = FldGetTextHandle( field );
  c = (Char *)MemHandleLock( textH );
  StrCopy( prefs->userID, c );

  // open DB
  db_ref = DmOpenDatabaseByTypeCreator( 'DATA', 'Schz', dmModeWrite );
  if ( !db_ref ) 
	ErrAlert( DmGetLastErr() );

  // write prefs struct
  recH = DmGetRecord( db_ref, 0 );
  p = MemHandleLock( recH );
  err = DmWrite(p, 0, prefs, sizeof(Prefs) );
  MemPtrUnlock(p);
  DmReleaseRecord( db_ref, 0, true);

  // close DB
  err = DmCloseDatabase( db_ref );
  if ( err )
	ErrAlert( err );

  // clean up
  if (prefs)
	MemPtrFree(prefs);
}

static void InitConfig(FormType *form)
{
  // field/string vars
  FieldType *field;
  MemHandle textH = NULL;
  Char *c;
  // database vars
  DmOpenRef db_ref;
  MemHandle recH = NULL;
  Prefs *prefs;
  MemPtr p;
  // list vars
  ListType *list;
  // misc vars
  Err err;


  // SWITCHEROO VARS
  MemHandle  appListH;
  SysDBListItemType  *appList;
  UInt16     appCount;
  UInt32     selected = -1;
  //  ListType   *list;
  UInt16     i, j;
  //  UInt16     launchPrefsSize, numSavedApps;
  UInt16 numSavedApps = 1;
  //  SavedLaunchCharPref  *savedApps;
  //

  // open the DB
  db_ref = DmOpenDatabaseByTypeCreator( 'DATA', 'Schz', dmModeReadOnly );
  if ( !db_ref ) // it does not exist... create it.
	{
	  FirstTimeDB( ); // create and populate the DB, then open it
	  db_ref = DmOpenDatabaseByTypeCreator( 'DATA', 'Schz', dmModeReadOnly );
	  if ( !db_ref )
		ErrAlert( DmGetLastErr() );
	}

  // read the prefs struct out of the DB
  prefs = MemPtrNew( sizeof( Prefs ) );
  recH = DmQueryRecord( db_ref, 0 );
  p = MemHandleLock( recH );
  MemMove( prefs, p, sizeof( Prefs ) );
  MemPtrUnlock( p );

  // app field
  /*
  field=FrmGetObjectPtr(form,FrmGetObjectIndex(form,configFieldApp));
  textH = MemHandleNew( sizeof( prefs->application ) );
  c = (Char *)MemHandleLock( textH );
  StrCopy( c, prefs->application );
  MemHandleResize( textH, StrLen( c )+1 );
  MemHandleUnlock( textH );
  FldSetTextHandle( field, textH );
  FldEraseField( field );
  FldDrawField( field );
  */

  // userName field
  field=FrmGetObjectPtr(form,FrmGetObjectIndex(form,configFieldName));
  textH = MemHandleNew( sizeof( prefs->userName ) );
  c = (Char *)MemHandleLock( textH );
  StrCopy( c, prefs->userName );
  MemHandleResize( textH, StrLen( c )+1 );
  MemHandleUnlock( textH );
  FldSetTextHandle( field, textH );
  FldEraseField( field );
  FldDrawField( field );

  // userID field
  field=FrmGetObjectPtr(form,FrmGetObjectIndex(form,configFieldUser));
  textH = MemHandleNew( sizeof( prefs->userID ) );
  c = (Char *)MemHandleLock( textH );
  StrCopy( c, prefs->userID );
  MemHandleResize( textH, StrLen( c )+1 );
  MemHandleUnlock( textH );
  FldSetTextHandle( field, textH );
  FldEraseField( field );
  FldDrawField( field );


  /*
   * LIST BEGIN
   */
  // Get every stinking little application.
  if (SysCreateDataBaseList('appl', 0, &appCount, &appListH, true))
    {
	  // Publish the database list handle and make a space to store the
	  // currently selected list item.
	  FtrSet(APP_ID, FTR_HAPPLIST, (UInt32)appListH);
	  FtrSet(APP_ID, FTR_APPCOUNT, (UInt32)appCount);
	  FtrSet(APP_ID, FTR_OLDSELECT, selected);

	  // Cheat and use version field to store each app's launch character.
	  appList = MemHandleLock(appListH);

	  for (i = 0; i < appCount; i++)
        {
		  appList[i].version = 0;
		  for (j = 0; j <= numSavedApps; j++)
			{
			  //			  if (appList[i].creator == savedApps[j].creator)
			  //				appList[i].version = (UInt16)savedApps[j].launchChar;
			}
        }
	  MemHandleUnlock(appListH);
	  // Initialize the list.
	  list = FrmGetObjectPtr(form, FrmGetObjectIndex(form, configAppList));
	  LstSetListChoices(list, NULL, appCount);
	  LstSetDrawFunction(list, PrefsListDrawFunc);
	    LstSetSelection(list, -1);
    }
  else
    {
	  FtrUnregister(APP_ID, FTR_HAPPLIST);
	  FtrUnregister(APP_ID, FTR_APPCOUNT);
	  FtrUnregister(APP_ID, FTR_OLDSELECT);
    }
  //  list = FrmGetObjectPtr(form,FrmGetObjectIndex(form,configAppList));
  //  LstEraseList( list );
  //  LstDrawList( list );
  /*
   * LIST END
   */

  // clean up
  err = DmCloseDatabase( db_ref );
  if ( err )
	ErrAlert( err );
  if (prefs)
	MemPtrFree(prefs);
}


static void FirstTimeDB()
{
  Err err;
  DmOpenRef db_ref;
  MemHandle recH = NULL;
  UInt16 rec_loc;
  MemPtr p;
  UInt16 cardNo = 0;
  Prefs *prefs = NULL;
  
  // for get_set_palm_name
  MemHandle mhUserName = NULL, mhUserID=NULL;
  Char *userName;
  UInt16 userID = 0;
  Char *userIDstr;

  err = DmCreateDatabase(cardNo,"SchizophrenicDB", APP_ID, 'DATA', false);
  ErrFatalDisplayIf( err, "Init: Could not createDB.");
  /* DB now exists! ...open it */
  db_ref = DmOpenDatabaseByTypeCreator( 'DATA', 'Schz', dmModeWrite );
  
  // populate prefs DB with the current personality
  prefs = MemPtrNew( sizeof( Prefs ) );
  mhUserName = MemHandleNew( 41 );
  mhUserID = MemHandleNew( maxStrIToALen );
  userName = MemHandleLock( mhUserName );
  userIDstr = MemHandleLock( mhUserID );
  get_set_palm_name( 0, userName, &userID );
  userIDstr = StrIToA( userIDstr, (UInt32)userID );
  StrCopy( prefs->userName, userName );
  StrCopy( prefs->userID, userIDstr );
  MemHandleUnlock( mhUserName );
  MemHandleUnlock( mhUserID );
  MemHandleFree( mhUserName );
  MemHandleFree( mhUserID );
  // end population

  recH = DmNewRecord( db_ref, &rec_loc, sizeof( Prefs ) );
  p = MemHandleLock( recH );
  err = DmWrite( p, rec_loc, prefs, sizeof(Prefs));
  ErrFatalDisplayIf( err, "Could not write prefs.");
  MemPtrUnlock( p );
  err = DmReleaseRecord( db_ref, rec_loc, true );
  ErrFatalDisplayIf( err, "Could not release record.");
  
  // cleanup
  err = DmCloseDatabase( db_ref ); // close DB
  if ( err )
	ErrAlert( err );
  if ( prefs )
	MemPtrFree( prefs );
}







static void PrefsListDrawFunc(Int16 index, RectangleType *bounds,
                              Char **data)
{
  SysDBListItemType  *appList;
  MemHandle  appListH;
  FontID font = 0; // regular font
  //  MemHandle  textH;
  //  Char       *launchChar;


  // Get the applist.
  if (FtrGet(APP_ID, FTR_HAPPLIST, (UInt32*)&appListH))
    {
	  //WinDrawChars("Error", 5, bounds->topLeft.x, bounds->topLeft.y);
	  return;
    }
  appList = MemHandleLock(appListH);

  // Draw the application name and launch character, if any.
  /*
  if (appList[index].version)
    {
	  textH = MemHandleNew(sizeof(Char));
	  launchChar = MemHandleLock(textH);
	  launchChar[0] = (Char)appList[index].version;
	  WinDrawChars(launchChar, 1, bounds->topLeft.x, bounds->topLeft.y);
	  MemHandleUnlock(textH);
	  MemHandleFree(textH);
    }
  */
  if ( index % 2 == 0 )
	font = FntSetFont( 1 ); // bold
  WinDrawChars(appList[index].name, StrLen(appList[index].name),
			   bounds->topLeft.x+10, bounds->topLeft.y);
  if ( index % 2 == 0 )
	FntSetFont( font ); // regular

  // Cleanup.
  MemHandleUnlock(appListH);
}

