/* This file contains code for both Application level "Find" (the silk screen button) and
   "Local Finds" */

/* Includes required */
#include <Common.h>
#include <System/SysAll.h>
#include <System/SysEvtMgr.h>
#include <UI/UIAll.h>
#include <UI/charattr.h>
#include "listrc.h"
#include "list.h"

/* More private globals used to store the search state.
   This makes is easy to span multiple databases for
   a single application. */
#if 0
static DmSearchStateType searchDBState;
static UInt searchCardNo;
static LocalID searchDbID;
static Boolean searchFilledScreenOnTitle; /* with a little thought we can find a neater solution */
#endif

static UInt localFindRecordNum;
static UInt localFindTableIndex;

/* Prototypes for private helpers */
static localSearchInitializeTable();
static localSearchFillTable();
static CharPtr localFindStrStr(CharPtr source, CharPtr token);

/* Handler for the popup find prompter */
Boolean FindFormEventHandler(EventPtr event)
{
  Boolean handled = false;
  FormPtr form;
  FieldPtr field;
  VoidHand textHandle;
  Char *text;
  int len;
  CALLBACK_PROLOGUE;   /* Work around PilotGCC 0.5.0 bug */  

  form = FrmGetActiveForm();

  switch( event->eType ) 
    {
    case ctlSelectEvent:
      if( event->data.ctlSelect.controlID == idFindOkButton ) {
	field = FrmGetObjectPtr(form, FrmGetObjectIndex( form, idFindText ) );
	textHandle = (VoidHand)FldGetTextHandle( field );
	text = MemHandleLock( textHandle );
	len = StrLen( text );
	if( len > 0 ) {
	  /* Perform a "local find" with the given text */
	  MemMove( LocalFindText, text, len + 1 );
	}
	MemHandleUnlock( textHandle );
	if( len > 0 ) {
	  FrmGotoForm( idFindResultsForm );
	}
      }
      /* Default action is to close the form (ie: cancel or no-text ok) */
      FrmReturnToForm( idMainForm );
      handled = true;
      break;

    case menuEvent:
      switch( event->data.menu.itemID ) 
	{
	case idEditMenuUndo:
	case idEditMenuCut:
	case idEditMenuCopy:
	case idEditMenuPaste:
	case idEditMenuSelectAll:
	  
	  field = FrmGetObjectPtr(form, FrmGetObjectIndex( form, idFindText ) );
	  
	  /* A second switch to actually do the operation */
	  switch( event->data.menu.itemID)
	    {
	    case idEditMenuUndo:
	      FldUndo (field);
	      break;
	    case idEditMenuCut:
	      FldCut (field);
	      break;
	    case idEditMenuCopy:
	      FldCopy (field);
	      break;
	    case idEditMenuPaste:
	      FldPaste (field);
	      break;
	    case idEditMenuSelectAll:
	      FldSetSelection (field, 0, FldGetTextLength (field));
	      break;
	    }
	  handled = true;
	  break;
	case idEditMenuKeyboard:
	  SysKeyboardDialogV10();
	  handled = true;
	  break;
	}
      break;

    case frmOpenEvent:
      field = FrmGetObjectPtr(form, FrmGetObjectIndex( form, idFindText ) );
      len = StrLen( LocalFindText );
      textHandle = MemHandleNew( len + 1 );
      StrCopy( MemHandleLock( textHandle ), LocalFindText );
      MemHandleUnlock( textHandle );
      FldSetText( field, textHandle, 0, len + 1);
      FldSetSelection( field, 0, len );
      FrmDrawForm( form );
      FrmSetFocus( form, FrmGetObjectIndex( form, idFindText ) );
      handled = true;
      break;
    }

  CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
  return( handled );
}

Boolean FindResultsFormEventHandler(EventPtr event)
{
  Boolean handled = false;
  FormPtr form;
  TablePtr table;
  UInt attr;
  CALLBACK_PROLOGUE;   /* Work around PilotGCC 0.5.0 bug */  

  switch( event->eType ) 
    {
    case ctlSelectEvent:
      if( event->data.ctlSelect.controlID == idFindResCancelButton ) {
	FrmGotoForm( idMainForm);
	handled = true;
      }
      if( event->data.ctlSelect.controlID == idFindResMoreButton ) {
	localSearchInitializeTable();

	/* Spiff up the display */
	form = FrmGetActiveForm();
	WinEraseWindow();
	FrmDrawForm( form );

	localFindTableIndex = 0;
	localSearchFillTable();
	handled = true;
      }
      break;

    case tblSelectEvent:

      form = FrmGetActiveForm();
      table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idFindTable ));
      CurrentRecord = TblGetRowData( table, event->data.tblSelect.row );
      DmRecordInfo( CurrentDB, CurrentRecord, &attr, NULL, NULL );
      /* change the current category if it is not appropriate */
      if( CurrentCategory != dmAllCategories ) {
	CurrentCategory = attr & dmRecAttrCategoryMask;
      }  
      FrmGotoForm( idViewForm );
      handled = true;
      break;

    case frmOpenEvent:

      /* Initialize the table */
      localSearchInitializeTable();

      form = FrmGetActiveForm();
      FrmDrawForm( form );

      localFindRecordNum = 0; /* Start from the first record */
      localFindTableIndex = 0;

      /* fill in the table */
      localSearchFillTable();

      handled = true;
      break;

    }
  CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
  return( handled );
}

static localSearchInitializeTable()
{
  FormPtr form;
  TablePtr table;
  int i;
  
  /* Initialize the table */
  form = FrmGetActiveForm();
  table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idFindTable ));
  for( i = 0; i < findTableRows; i++ ) {
    TblSetItemStyle( table, i, 0, customTableItem );
    TblSetRowUsable( table, i, false );
  }
  TblSetColumnUsable( table, 0, true );
}

static localSearchFillTable()
{
  FormPtr form;
  ControlPtr ctl;
  int x, len;

  form = FrmGetActiveForm();

  FntSetFont( boldFont );
  WinDrawChars( "Searching..", 11, 8, 14 );

  /* If there is more, make the "Find More" button usable */
  ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idFindResMoreButton ));
  if( ListLocalSearch() ) {
    CtlShowControl( ctl );
  } else {
    CtlHideControl( ctl );
  }
  
  FntSetFont( boldFont );
  WinEraseChars( "Searching..", 11, 8, 14 );
  
  /* Display either: "Matches for: string" or "No Matches for: string" */
  x = 8;
  if( localFindTableIndex == 0 ) {
    WinDrawChars( "No matches for \223", 16, x, 14 );
    x += FntCharsWidth( "No matches for \223", 16 );
  } else {
    WinDrawChars("Matches for \223", 13, x, 14 );
    x += FntCharsWidth( "Matches for \223", 13 );
  }
  len = StrLen( LocalFindText );
  WinDrawChars( LocalFindText, len, x, 14 );
  x += FntCharsWidth( LocalFindText, len );
  WinDrawChars( "\224", 1, x, 14 );
}

/* Return true if there are more items to be searched */
Boolean ListLocalSearch()
{
  VoidHand recordHandle;
  ListDBHeader *record;
  Boolean match;
  CharPtr string;
  FormPtr form;
  TablePtr table;
  RectangleType bounds;

  for(;;) {
    recordHandle = DmQueryNextInCategory( CurrentDB, &localFindRecordNum, dmAllCategories );
    if( !recordHandle ) {
      /* no more records in this database */
      break;
    }

    /* Try to match the record */
    record = MemHandleLock( recordHandle );
    match = false;
    if( record->field1 != 0 ) {
      string = (CharPtr) record + record->field1;
      match = localFindStrStr( string, LocalFindText ) != NULL;
    }
    if( !match && (record->field2 != 0 )) {
      string = (CharPtr) record + record->field2;
      match = localFindStrStr( string, LocalFindText ) != NULL;
    }
    if( !match && (record->note != 0 )) {
      string = (CharPtr) record + record->note;
      match = localFindStrStr( string, LocalFindText ) != NULL;
    }

    if( match ) {
      /* Found a match, add it to the local find results table */
      form = FrmGetActiveForm();
      table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idFindTable ));

      TblSetRowUsable( table, localFindTableIndex, true );
      /* Set the row data to be the record number */
      TblSetRowData( table, localFindTableIndex, localFindRecordNum );
      /* Draw the entry */
      TblGetItemBounds( table, localFindTableIndex, 0, &bounds );
      ListDrawEntry( record, &bounds, DisplayStyle );
      localFindTableIndex++;
      /* If the table is now full, return true, we will try for more later */
      if( localFindTableIndex == findTableRows ) {
	MemHandleUnlock( recordHandle );    
	localFindRecordNum++;
	return( true );
      }
    }
    MemHandleUnlock( recordHandle );    
    localFindRecordNum++;
  }

  return( false ); /* No more records to search */
}

/* A caseless version of StrStr.
   We can assume the token is a maximum of 32 characters, and source is a max of 4096 */
/* lazy lazy, more (hidden) globals */
static char caselessToken[32];
static char caselessSource[4096]; /* made into global due to Palm OS stack limits */

static CharPtr localFindStrStr(CharPtr source, CharPtr token)
{
  StrToLower( caselessToken, token );
  StrToLower( caselessSource, source );
  return( StrStr( caselessSource, caselessToken ));
}

void ListSearch( FindParamsPtr params )
{
  Err error;
  DmOpenRef searchDatabase;
  DmSearchStateType searchDBState;
  UInt searchCardNo;
  LocalID searchDbID;
  char dbName[dmDBNameLength];
  UInt recordNum;
  VoidHand recordHandle;
  ListDBHeader *record;
  Word position;
  CharPtr string;
  Boolean match;
  RectangleType bounds;
  LocalID appInfoID;
  ListAppInfoType *appInfo;
  Byte displayStyle;
  int i;
  
  /* To avoid the use of any globals (not legal if our application was
     not running at the time of the global find) we will borrow the
	 params->dbAccesMode flag to keep track of which database we are
	 searching */

  if( !params->continuation ) {
    /* The Find function may be called before any databases exist. */
	  error = DmGetNextDatabaseByTypeCreator( true, &searchDBState, ListType, ListCreator, 
											  false, &searchCardNo, &searchDbID );
	  if( error ) {
	  /* No databases exist */
		  params->more = false;
		  return;
	  }
	params->dbAccesMode = 1; /* We are on database 1 */
  } else {
	  /* We are being re-started, try to pick up where we left off */

	  /* Start at the first database */
	  DmGetNextDatabaseByTypeCreator( true, &searchDBState, ListType, ListCreator, 
											  false, &searchCardNo, &searchDbID );
	  for( i=1; i<params->dbAccesMode; i++ ) {
		  /* find the database we were last using */
		  error = DmGetNextDatabaseByTypeCreator( false, &searchDBState, ListType, ListCreator, false,
			  &searchCardNo, &searchDbID );
		  if( error ) {
			  /* No more databases */
			  params->more = false;
			  return;
		  }
	  }
  }
  do {
  /* Grab the name of the List database */
  DmDatabaseInfo( searchCardNo, searchDbID, dbName, NULL, NULL, NULL, NULL, NULL, NULL,
		  NULL, NULL, NULL, NULL );

  if( FindDrawHeader( params, dbName )) {
    /* The screen got full, stop - we will be called again */
    return;
  }

  /* Open the database */
  searchDatabase = DmOpenDatabase( searchCardNo, searchDbID, dmModeReadOnly );
  DmDatabaseInfo( searchCardNo, searchDbID, NULL, NULL, NULL, NULL, NULL, NULL,
		  NULL, &appInfoID, NULL, NULL, NULL );
  appInfo = MemLocalIDToLockedPtr( appInfoID, searchCardNo );
  displayStyle = appInfo->displayStyle;
  MemPtrUnlock( appInfo );
  
  /* iterate over all the records looking for a match */
  recordNum = params->recordNum;  

  for(;;) {
    recordHandle = DmQueryNextInCategory( searchDatabase, &recordNum, dmAllCategories );
    if( !recordHandle ) {
      /* no more records in this database */
      params->recordNum = 0; /* Start over for next database */
      break;
    }
    /* Try to match the record */

    record = MemHandleLock( recordHandle );
    match = false;
    if( record->field1 != 0 ) {
      string = (CharPtr) record + record->field1;
      match = FindStrInStr( string, params->strToFind, &position );
    }
    if( !match && (record->field2 != 0 )) {
      string = (CharPtr) record + record->field2;
      match = FindStrInStr( string, params->strToFind, &position );
    }
    if( !match && (record->note != 0 )) {
      string = (CharPtr) record + record->note;
      match = FindStrInStr( string, params->strToFind, &position );
    }
    if( match ) {
      if( FindSaveMatch (params, recordNum, position, 0, 0, searchCardNo, searchDbID) ) {
		  /* a true result means we have to stop the search here */
		  MemHandleUnlock( recordHandle );
		  DmCloseDatabase( searchDatabase );
		  return;
	  }
      /* Draw the record */
      FindGetLineBounds( params, &bounds );
      ListDrawEntry( record, &bounds, displayStyle );
      params->lineNumber++;
    }

    MemHandleUnlock( recordHandle );    

    recordNum++;

	/* Check if any events are pending every few (16)
	records, to allow the user to cancel this operation */
	if( QueryPalmOSVersion() >= 2 ) {
		if((recordNum & 15) == 0 ) {
			if( EvtSysEventAvail( true ) ) {
				params->more = true;
				DmCloseDatabase( searchDatabase );
				return;
			}
		}
	}
  }
  DmCloseDatabase( searchDatabase );
  /* ROOHACK: we want to make it seem like when we change databases the 
   new database is not being (Cont.) */
  params->continuation = false;
  
  /* Get the next database */
  error = DmGetNextDatabaseByTypeCreator( false, &searchDBState, ListType, ListCreator, false,
										  &searchCardNo, &searchDbID );
  params->dbAccesMode++; /* Remember which number of database we are on */
  } while( !error );

  /* Finished search? */
  params->more = false;
  return;
}
