 
#import "FoneTones.h"
#import "FoneTonesConstants.h"

#import <appkit/appkit.h>
#import <strings.h>
#import <soundkit/soundkit.h>
#import <drivers/event_status_driver.h>

const char *knownFoneTones[] = {
	"zero", "one", "two", "three", "four", "five",
	"six", "seven", "eight", "nine", "splat", "pound", NULL
};

static char *handlerList[] = {	
		"dialnumber",
//		"remembernumber",
//		"forgetnumber",
//		"NUMBERSSTARTINGWITH",
//		"ALLPHONENUMBERS",
//		"PHONEBOOK",
//		"PHONEBOOKS",
//		"switchtobook",
//		"speeddialing",
		"preloadtones",
//		"phonebookeditor",
		"CURRENTVOLUME",
		"setcurrentvolume",
		"ft_version",
		NULL
};


static struct mach_header	*loadHeader = NULL;
static NXEventHandle		ourEventStatusHandle;

@implementation FoneTones : XModule

- init
{
	[super init];
	
	// Do your stuff here...
	foneTonesAreLoaded = NO;
	ourEventStatusHandle = NXOpenEventStatus();	
		
	return ( self );
}

- free
{
	// Do your stuff here...

	return ( [super free] );
}

+ (const char *)moduleName
{
	return ( "FoneTones" );
}

+ (const char **)xCmdsAndFcns
{
	return ( handlerList );
}

- prepareToExecuteHandlers
{
	// [self loadInterfaceData];
	return ( self );
}

- executeHandler:(NXAtom)handlerName
{
	if (!strcmp(handlerName, "dialnumber")) {
		return ( [self dial] );
	} else if (!strcmp(handlerName, "remembernumber")) {
		//return ( [self rememberNumber] );
		return ( [[self getEmptyContainer]
				setString:"rememberNumber command, not implemented yet."] );
	} else if (!strcmp(handlerName, "forgetnumber")) {
		// return ( [self forgetNumber] );
		return ( [[self getEmptyContainer]
				setString:"forgetNumber command, not implemented yet."] );
	} else if (!strcmp(handlerName, "NUMBERSSTARTINGWITH")) {
		// return ( [self numbersStartingWith] );
		return ( [[self getEmptyContainer]
				setString:"forgetNumber command, not implemented yet."] );
	} else if (!strcmp(handlerName, "ALLPHONENUMBERS")) {
		// return ( [self allPhoneNumbers] );
		return ( [[self getEmptyContainer]
				setString:"forgetNumber command, not implemented yet."] );
	} else if (!strcmp(handlerName, "PHONEBOOK")) {
		// return ( [self phoneBook] );
		return ( [[self getEmptyContainer]
				setString:"phoneBook command, not implemented yet."] );
	} else if (!strcmp(handlerName, "PHONEBOOKS")) {
		// return ( [self phoneBooks] );
		return ( [[self getEmptyContainer]
				setString:"phoneBooks command, not implemented yet."] );
	} else if (!strcmp(handlerName, "switchtobook")) {
		// return ( [self switchToBook] );
		return ( [[self getEmptyContainer]
				setString:"switchToBook command, not implemented yet."] );
	} else if (!strcmp(handlerName, "speeddialing")) {
		// return ( [self speedDialing] );
		return ( [[self getEmptyContainer]
				setString:"speedDialing command, not implemented yet."] );
	} else if (!strcmp(handlerName, "preloadtones")) {
		return ( [self preloadTones] );
	} else if (!strcmp(handlerName, "phonebookeditor")) {
		// return ( [self phoneBookEditor] );
		return ( [[self getEmptyContainer]
				setString:"phoneBookEditor command, not implemented yet."] );
	} else if (!strcmp(handlerName, "ft_version")) {
		return ( [self ft_version] );
	} else if (!strcmp(handlerName, "setcurrentvolume")) {
		return ( [self setCurrentVolume] );
	} else if (!strcmp(handlerName, "CURRENTVOLUME")) {
		return ( [self currentVolume] );
	} else return ( nil ); // handlerName not recognized!
}

+ finishLoading:(struct mach_header *)header
{
	loadHeader = header; // Save this for later use
	return ( self );
}

- loadInterfaceData
{
	//[NXApp loadNibSection:"MyXModule.nib" owner:self
	//    withNames:NO fromHeader:loadHeader] )
		
	return ( self );
}

///
// FoneTones XModule Specific Methods
///
- dial
{
	int				pCount = [self paramCount];
	char				phoneNumber[256] = "";	// Hopefully enough space.
	char				*pnIndex;
	Sound				*sndObj = nil;
	float				approxTimeToPlay;
	
	if ( pCount != 1 ) {
		return ( [[self getEmptyContainer]
			setString:"Usage: dial <phoneNumber>"] );
	}
	
	if ( !foneTonesAreLoaded ) [self preloadTones:self];
	
	pnIndex = strncpy(phoneNumber, [[self getParam:1] stringVal], 255);
	if ( !pnIndex || !*pnIndex ) return ( self );

	// If we don't have a sound to pile tones into, then create one.	
	if ( ![Sound findSoundFor:"numberToPlay"] ) {
			[Sound addName:"numberToPlay" sound:[[Sound alloc] init]];		
	}

	// Go and remove all the junk!, anything but numbers, letters, and '*' or '#'
	while ( *pnIndex ) {
		if ( index("#", *pnIndex) ) sndObj = [Sound findSoundFor:"pound_tone"];
		else if ( index("*", *pnIndex) ) sndObj = [Sound findSoundFor:"splat_tone"];
		else if ( index("1", *pnIndex) ) sndObj = [Sound findSoundFor:"one_tone"];
		else if ( index("2abcABC", *pnIndex) ) sndObj = [Sound findSoundFor:"two_tone"];
		else if ( index("3defDEF", *pnIndex) ) sndObj = [Sound findSoundFor:"three_tone"];
		else if ( index("4ghiGHI", *pnIndex) ) sndObj = [Sound findSoundFor:"four_tone"];
		else if ( index("5jklJKL", *pnIndex) ) sndObj = [Sound findSoundFor:"five_tone"];
		else if ( index("6mnoMNO", *pnIndex) ) sndObj = [Sound findSoundFor:"six_tone"];
		else if ( index("7prsPRS", *pnIndex) ) sndObj = [Sound findSoundFor:"seven_tone"];
		else if ( index("8tuvTUV", *pnIndex) ) sndObj = [Sound findSoundFor:"eight_tone"];
		else if ( index("9wxyWXY", *pnIndex) ) sndObj = [Sound findSoundFor:"nine_tone"];
		else if ( index("0qzQZ", *pnIndex) ) sndObj = [Sound findSoundFor:"zero_tone"];
		else sndObj = nil;

		if (( sndObj != nil ) && [[Sound findSoundFor:"numberToPlay"] compatibleWith:sndObj]) {
			[[Sound findSoundFor:"numberToPlay"] compactSamples];
			[[Sound findSoundFor:"numberToPlay"] insertSamples:sndObj
					at:[[Sound findSoundFor:"numberToPlay"] dataSize]];
		}
		pnIndex++;
	}
	
	[[Sound findSoundFor:"numberToPlay"] compactSamples];	// Ensure no fragmentation.
	approxTimeToPlay = [[Sound findSoundFor:"numberToPlay"] duration];
	[[Sound findSoundFor:"numberToPlay"] play];	// Jam the sound out to the speaker.
	[[Sound findSoundFor:"numberToPlay"] deleteSamples];	// Get ready for next use?
	[[Sound findSoundFor:"numberToPlay"] compactSamples];  // Why not, just to clean up.

	return ( [[self getEmptyContainer] setFloat:approxTimeToPlay] );
}

// Add a number to the current fonebook.
- rememberNumber
{
	return ( self );
}

// Given a key, this method will remove the key-value association
// from the current fone book.  If the key doesn't exist, then
// nothing happens.
- forgetNumber
{
	return ( self );
}

// This puppy returns all the numbers starting with the letters
// in the parameter string passed to this function.
- numbersStartingWith
{
	return ( self );
}

// Returns a line delimited list of all the keys and associated
// phone numbers to go along with them.
- allPhoneNumbers
{
	return ( self );
}

// Return the name of the current fone book.
- phoneBook
{
	return ( self );
}

// Return the names of all the fone books located in...
//
//  ~/Library/FoneBooks
//  /Library/FoneBooks
//  /NextLibrary/FoneBooks
//
// directories.
- phoneBooks
{
	return ( self );
}

// Switch from one fone book to another.
- switchToBook
{
	return ( self );
}

// Don't know if I am going to bother with this one.  After Doug
// and I talked about it, it would seem that most phones will allow
// take speed dialing.  So for now, this function isn't worth working
// on.
- speedDialing
{
	return ( self );
}

// Used to avoid any delay loading of tones may cause.
- preloadTones:sender
{
	char		**kftIndex = knownFoneTones;
	char		sndName[256];
	
	if ( foneTonesAreLoaded ) {
		if ( sender == nil ) {	// Caller was HS
			return ( [[self getEmptyContainer] setString:"Tones already loaded."] );
		} else return ( self );
	}
	
	while ( *kftIndex ) {	// Loop until we don't have anymore to load.
		sprintf(sndName, "%s%s", *kftIndex, "_tone");
		if ( ![Sound findSoundFor:sndName] ) {
					NXLogError("%s Error -- preloadTones: Could not load the"
					" sound file %s.\n", [[self class] name], sndName);
		}
		kftIndex++;	// Point to next string.
	}
	
	foneTonesAreLoaded = TRUE;
	if ( sender == nil ) {
		return ( [[self getEmptyContainer] 
				setString:"Tones successfully loaded."] );
	}

	return ( self );
}

// Front to calling the real preloadTones: function.
- preloadTones
{
	return ( [self preloadTones:nil] );
}

// At some time in the future, it would be nice to have a graphical
// front end to the current fone book.  I figure a nice association
// of keys and values in a DBTableView would work.  But hey, this
// is something for the future.
- phoneBookEditor
{
	return ( self );
}

///
// FoneTones XModule Support Methods
///

#define MAXKEYSIZE		(30)
#define MAXVALUESIZE		(50)

// This puppy will create a new book if specified book doesn't exist.
// It will be created in the ~/Library/FoneBooks directory.  Only place
// we for sure can create directories/files.
- (HashTable *)loadPhoneBook:(const char *)newBookName
{
	// id					newFoneBook;
	BOOL				successfullyLoadedNewBook;
	// char				bookPathBuff[MAXPATHLEN + 1];
	const char		*defaultPath = NULL;
	NXStream			*fonebookStream;
	NXStringTable	*newStringTable;
	char				currKey[MAXKEYSIZE],
						currValue[MAXVALUESIZE];

	// Determine if we are to load the default fone book, or one specified.
	if ( !newBookName && !*newBookName ) {	// Go and try to load the defaults...
		newBookName = NXReadDefault("FoneTonesXModule", "StartupFoneBook");
		if ( !defaultPath && !*defaultPath ) {
			NXLogError("%s Error -- loadPhoneBook:: default fone book was not specified"
					" in the user's defaults data.\n", [[self class] name]);
			return ( (HashTable *)nil );
		}
	}

	// Attempt to get a stream open to the fone book.
	fonebookStream = NXMapFile(newBookName, NX_READONLY);
	if ( fonebookStream == NULL ) {
		NXLogError("%s Error -- loadPhoneBook:: could not get a stream open to the"
					" defaults file specified (%s)\n", [[self class] name], newBookName);
		return ( nil );
	}

	// Well, now we have to create a table, and go and parse the file...
	// File format (for convenience of user so they can use a text
	// processor [non-rtf] to define a phone book.
	newStringTable = [[NXStringTable alloc] init];
	NXScanf(fonebookStream, "%MAXKEYSIZEs\t%MAXVALUESIZEs\n", currKey, currValue);
	
	if ( successfullyLoadedNewBook ) {
	
	}
	
	return ( newStringTable );
}

- ft_version
{
	id		version;

	version = [[ self getEmptyContainer ] setString:"r" ];
	[ version appendString:FT_RELEASE ];
	[ version appendString:" v" ];
	[ version appendString:FT_VERSION ];
	[ version appendString:"    " ];
	[ version appendString:FT_RELTIME ];
	[ version appendString:"\n" ];
	[ version appendString:FT_COPYRIGHT ];

	return ( version );
}

/////////////////////
// Volume Functions
/////////////////////
- setCurrentVolume
{
	int		pCount = [ self paramCount ];
	float		newVolume;

	if (( pCount != 1 ) || ( !([[ self getParam:1 ] validAsNumber ]) )) {
		return ( [[ self getEmptyContainer ]
			setString:"Usage:  setCurrentVolume  <0.0 - 1.0>" ] );
	}

	newVolume = [[ self getParam:1 ] floatVal ];
	if (( newVolume < 0.0 ) || ( newVolume > 1.0 )) {
		return ( [[ self getEmptyContainer ]
			setString:"Usage:  setCurrentVolume  <0.0 - 1.0>" ] );
	}

	NXSetCurrentVolume(ourEventStatusHandle, newVolume);

	return ( self );
}


- currentVolume
{
	return ( [[ self getEmptyContainer ]
			setFloat:NXCurrentVolume(ourEventStatusHandle) ] );
}


@end

