// BWBorkPadModel.m -- the sheets of notes for BorkPad

// Copyright (C) 2003 Borkware
// We use the BSD License.  Check out http://borkware.com/license

#import "BWBorkPadModel.h"


/*
 * ----- random constants
 */

// the key we use to store the notes under in the userDefaults file

static NSString *BWNotesKey = @"com.borkware.borkpad.notesarray";


// handy variable for initializing a find result variable to an empty
// value.  I wish NSRange had one of these

BWBorkPadFindResult BWZeroResult;



/*
 * ----- class implementation
 */

@implementation BWBorkPadModel


// initialize the object.  Read in any saved notes from the user defaults.
// otherwise create a blank pad with three pages.  the first page having 
// a little bit of greeting text for the user.

- (id) init
{
    if (self = [super init]) {
	notes = [[NSUserDefaults standardUserDefaults] objectForKey: BWNotesKey];

	if (notes == nil) {
	    // couldn't load notes.  load defaults
	    notes = [[NSMutableArray alloc] init];

	    // the actual contents of the greeting text lives in 
	    // LocalizableStrings.plist
	    [notes addObject: NSLocalizedString (@"first page contents",
						 @"default first page contents")];
	    [notes addObject: @""];
	    [notes addObject: @""];

	} else {
	    [notes retain];
	}
    }

    return (self);

} // init



// clean up our mess

- (void) dealloc
{
    [notes release];
    [super dealloc];
} // dealloc



// write the notes to the user defaults.  This happens pretty often,
// like when the UI changes pages, when new note pages are added or
// removed.

- (BOOL) saveNotes
{
    NSUserDefaults *defaults;

    defaults = [NSUserDefaults standardUserDefaults];

    [defaults removeObjectForKey: BWNotesKey];
    [defaults setObject: notes  forKey: BWNotesKey];

    // make sure stuff actually gets written
    [defaults synchronize];

    return (YES);

} // saveNotes



//  the number of note pages we're currently managing

- (int) count
{
    return ([notes count]);

} // count



// the notes are indexed from zero to count - 1.  Return the contents of
// a note that lives at a given index.

- (NSString *) noteAtIndex: (int) index
{
    NSString *item;

    item = [notes objectAtIndex: index];

    return (item);

} // noteAtIndex



// create a new blank note and stick it to the end of the pad

- (void) addNote
{
    [notes addObject: @""];

    [self saveNotes];

} // addNote



// nuke a note.  Remove it and its contents.

- (void) deleteNoteAtIndex: (int) index
{
    [notes removeObjectAtIndex: index];

    [self saveNotes];

} // deleteNoteAtIndex



// update a note with new contents.  Typically this will be used after
// the user has edited a note and has moved on (switching to another note
// pehaps, or quitting the app)

- (void) replaceNoteAtIndex: (int) index  withString: (NSString *) string
{
    NSString *oldNote = [self noteAtIndex: index];

    // only save if notes are different
    if (![oldNote isEqualToString: string]) {
	[notes replaceObjectAtIndex: index  withObject: string];
	[self saveNotes];
    }

} // replaceNoteAtIndex



// This is a helper method for findString: startingAt: justOneNote
// it looks in a particular note for the contents, and then returns
// the found range.  the returnedRange.location is set to NSNotFound if
// the contents aren't found
//
//       string -- the text to locate
//    noteNumbe -- index (zero-base) of the note to look for 'string' in
//   startingAt -- Where to start searching.  handy for doing "find again"
//                 functionality
//   wrapAround -- is this a "just on this note" kind of search?  If so, 
//                 start searching from the top if we can't find 'string'
//                 starting at 'startingAt'.
//                 If this is NO, if 'string' can't be found, we immediately
//                 admit defeat and don't search again from the beginning.

- (NSRange) findString: (NSString *) string
		inNote: (int) noteNumber
	    startingAt: (int) start
	    wrapAround: (BOOL) wrapAround
{
    NSRange searchRange, foundRange;

    // the text to look in

    NSString *note;
    note = [notes objectAtIndex: noteNumber];

    // start searching from where they told us, and stop searching
    // at the end of the text

    searchRange.location = start;
    searchRange.length = [note length] - searchRange.location;

    // do the actual search.  There are probably faster algorithms out there
    // we can use, but it is expected that the size of the notes the users
    // will have will be of moderate size.

    foundRange = [note rangeOfString: string
		       options: NSCaseInsensitiveSearch
		       range: searchRange];

    if (foundRange.location == NSNotFound && wrapAround) {

	// we didn't find it.  Wrap around to the top looking for it
	searchRange.location = 0;
	searchRange.length = [note length];

	foundRange = [note rangeOfString: string
			   options: NSCaseInsensitiveSearch
			   range: searchRange];
	// if this still doesn't find it, foundRange.location will still be 
	// NSNotFound, so our caller knows the search value does not exist
	// anywhere on this page
    }

    return (foundRange);

} // findString (helper)



// this is the public API for searching.  It support finding a string in the
// note pad, whether it's just looking at the a single page, or searching
// through multiple pages.
//
//    findString -- the string to locate in the notepad
//    startingAt -- the page and location to start the search from.  The search
//		    starts at end end of the range indicated by 
// 		    startingAt.foundRange.
//		    This lets you use the results of a prior search as the 
//		    'startingAt' for a subsequent search (hand for "find next" kinds
//		    of features)
//   justOneNote -- The choice between looking at just the note indicated by
//	            'startingAt' or going through all notes.  In any case
//		    searches wrap-around, so that subsequent calls finding the
//		    the same string will eventually find every occurance in the
//	       	    note pad

- (BWBorkPadFindResult) findString: (NSString *) string
			startingAt: (BWBorkPadFindResult) initialLocation
		       justOneNote: (BOOL) justOneNote
{
    BWBorkPadFindResult result;
    NSRange foundRange;

    // used to indicate the current note for doign setup.  
    // Also used as loop variable if we end up looking in
    // subsequent pages
    int noteNumber;
    noteNumber = initialLocation.noteNumber;

    // start looking after the end of the previous find
    foundRange = [self findString: string
		       inNote: noteNumber
		       startingAt: (initialLocation.foundRange.location
				    + initialLocation.foundRange.length)
		       wrapAround: justOneNote];

    // didn't find it, and the justOneNote parameter says to look
    // at other notes.  Also only do this loop if we have more than one
    // note, otherwise it's just wasted work.

    if (foundRange.location == NSNotFound 
	&& !justOneNote
	&& [notes count] > 1) {
	int startingNote = noteNumber;

	do {
	    noteNumber = (noteNumber + 1) % [notes count];

	    foundRange = [self findString: string
			       inNote: noteNumber
			       startingAt: 0
			       wrapAround: NO];

	    if (foundRange.location != NSNotFound) {
		break;
	    }

	} while (noteNumber != startingNote);
    }

    // whether we found it or not, return the result to the caller
    result.noteNumber = noteNumber;
    result.foundRange = foundRange;
    
    return (result);

} // findString


@end // BWBorkPadModel


/* $Id: BWBorkPadModel.m,v 1.8 2003/01/03 21:29:46 markd Exp $ */
