// FILE:      BrailleConverter.m
// AUTHOR:    Daniel Lee
// PURPOSE:   
// REFERENCE: None. 
// COMMENTS:  Required that the Braille Font is installed, or the program will not function
// REQUIRES:  BrailleConverter.h 
// Last Mod:  13-07-2006




#import "BrailleConverter.h"

@implementation BrailleConverter


// NAME: awakefromNib
// PURPOSE: To initilise the view for use
// IMPORTS: nil
// EXPORTS: nil
// AUTHOR: Daniel Lee
// REMARKS: Should be called after the object is started
// LAST MODIFIED: 01-06-2006

- (void) awakeFromNib
{
	
	// *******************
	// Initialise the data
	// *******************
	
	oldDirection = TRUE;
	
	// Build up the language list and to set it to the interface so that
	// language switching is possible
	// Also, should be set to the current language of the system
    // ??

    // Set the boxes to set the delegate to this object
    [inputTextView setDelegate: self];
	[brailleTextView setDelegate: self];
	// asciiTextView does not needs a delegate since only braille is being sent there
	// There is no input that is allowed to be placed in that box

    // Set the font of the asciiTextView and brailleTextView to braille, size 16
    NSFont* braille = [NSFont fontWithName: @"Braille" size: 0.0];
    [brailleTextView setFont: braille];

    // create the tags
    [self loadTags];
    // 
	// Build the tags into the systembrailleMenu
	//
	[brailleMenu removeAllItems];
    [brailleMenu addItemsWithTitles: tagsArray];

    return;
}


// NAME: loadTags
// PURPOSE: To read the ini file and create the tags
// IMPORTS: nil
// EXPORTS: nil
// AUTHOR: Daniel Lee
// REMARKS: None
// LAST MODIFIED: 13-07-2006

- (void) loadTags
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
	NSData *dataFromFile;
    tagsArray = [[NSMutableArray alloc] init];
   	forwardFileArray = [[NSMutableArray alloc] init];
    reverseFileArray = [[NSMutableArray alloc] init];
	BOOL tags = FALSE;
	BOOL file = FALSE;
	int numberOfFiles = 2;	
		
    if( [fileManager fileExistsAtPath: tagTable] )
	{
        dataFromFile = [fileManager contentsAtPath: tagTable];
    	NSString *stringData = [NSString stringWithCString: (const char*) [dataFromFile bytes] encoding: NSASCIIStringEncoding];
		
		NSRange range;
		int tagLocation = 0;
		int fileLocation = 0;
		
		int i;
		
		// Parsing the tags
		for( i = 0; i < [stringData length]; i++ )
		{
		    if( [stringData characterAtIndex: i] == '<' )
		    {
			    if( tags )
				{
				    [self quit: @"Cannot have < in a tag."];
				}
				else if( file )
				{
				    [self quit: @"Cannot have < in a file."];
				}
				else if( numberOfFiles < 2 )
				{
				    [self quit: @"Need two files per tag"];
				}
				else
				{
    			    tags = TRUE;
	    			tagLocation = i;
					numberOfFiles = 0;
				}
			}
			else if( [stringData characterAtIndex: i] == '>' )
			{
			    // No need to check lastItem_Tag.
				// If there is no '<' found first, it is still an abort
			    if( file )
				{
				    [self quit: @"Cannot have > in a file."];
				}
				else if( !tags )
				{
				    [self quit: @"Cannot have > without a accopmanying <."];
				}
				else
				{
			        tags = FALSE;
				    range.location = tagLocation;
			    	range.length = i - tagLocation + 1;

					// There is a limit of how large the tag is
					if( range.length > tagLengthLimit )    
					{
					    NSString *error = @"Length of the tag is too long. Limit is ";
						error = [error stringByAppendingString: tagLengthLimitName];
						[self quit: error];
					}

			        NSString* tagData = [stringData substringWithRange: range];
					// NSLog( tagData );
					[tagsArray addObject: tagData];
			    }
			}
			else if( [stringData characterAtIndex: i] == '\"' )
			{
			    // Should check is the tag is still set
				// No quotation marks in the tags at all

				// If there is a tag, then abort using a window

                if( tags )
				{
    				[self quit: @"\" found between a tag."];
				}
				else if( numberOfFiles == 2 )    // Three files?
                {
				    [self quit: @"Cannot have three files after a tag."];
				}
				else if( numberOfFiles == 1 )
				{
				    if( file )    // If the file tag is set
				    {
    				    file = FALSE;
						numberOfFiles = 2;
						range.location = fileLocation + 1;
		    		    range.length = i - fileLocation - 1;
			    		NSString* fileName = [stringData substringWithRange: range];
                        // NSLog( fileName );
						[reverseFileArray addObject: fileName];
				    }
    				else
	    			{
		    		    file = TRUE;
			    		fileLocation = i;
				    }
				}
				else    // first file after tag?
				{
				    // If lastItem_Tag and file are false
					// Then the last item was a file and this is
					// a new file. Violates the pair

                    if( file )    // If the file tag is set
				    {
    				    file = FALSE;
                        numberOfFiles = 1;
	    			    range.location = fileLocation + 1;
		    		    range.length = i - fileLocation - 1;
			    		NSString* fileName = [stringData substringWithRange: range];

						[forwardFileArray addObject: fileName];
				    }
    				else
	    			{
		    		    file = TRUE;
			    		fileLocation = i;
				    }
				}
			}
			else if( ( [stringData characterAtIndex: i] == '\r' )
			       || ( [stringData characterAtIndex: i] == '\n' ) )
			{
			    // If there is a dangling tag or file name.
			    if( file || tags )
				{
				    [self quit: @"New line character seen in between a tag or file."];
				}
			}
		}
	}
	else
	{
	    // Need to make a window for this
	    NSString *iniError = @"No ini file found at";
		iniError = [iniError stringByAppendingString: tagTable];
	    [self quit: iniError];
    }

	//
	// Check end conditions
    //
	if( tags )
	{
	    [self quit: @"Lone tag"];
	}
	else if( numberOfFiles < 2 )    // No lone tag, but not enough files?
	{
	    [self quit: @"Need two files per tag"];
	}

	// Check to see if the files exist
	int i;
    BOOL filePass = TRUE;
    NSString *failFileString = @"Unable to find file:\n";
	
    // tempName used to add on the absolute path before the final build
    // Need to remove @ final build
    NSString *tempName = @"/Users/daniellee/FinalYearProject/";
	
	for( i = 0; i < [forwardFileArray count]; i++ )
	{
        NSString *fileName = (NSString *) [forwardFileArray objectAtIndex: i];
		fileName = [tempName stringByAppendingString: fileName];

	    if( ![fileManager fileExistsAtPath: fileName] )
		{
		    filePass = FALSE;
    		fileName = [fileName stringByAppendingString: @"\n"];
			failFileString = [failFileString stringByAppendingString: fileName];
		}
	}
	
	for( i = 0; i < [reverseFileArray count]; i++ )
	{
        NSString *fileName = (NSString *) [reverseFileArray objectAtIndex: i];
		fileName = [tempName stringByAppendingString: fileName];

	    if( ![fileManager fileExistsAtPath: fileName] )
		{
		    filePass = FALSE;
    		fileName = [fileName stringByAppendingString: @"\n"];
			failFileString = [failFileString stringByAppendingString: fileName];
		}
	}
		
    // To give spacing
	failFileString = [failFileString stringByAppendingString: @"\n"];

    // Checking for tag duplicates
    BOOL duplicates = FALSE;
    NSString *duplicateTagString = @"Duplicate tags:\n";
	i = 0;    // Reusing variable
	
	while( i < [tagsArray count] )
    {
        NSString * stringI = (NSString *) [tagsArray objectAtIndex: i];
        NSString * stringJ;
	    int j;
		for( j = i + 1; j < [tagsArray count]; j++ )
		{
    		stringJ = (NSString *) [tagsArray objectAtIndex: j];
	        if( [stringI isEqualToString: stringJ] )
			{
			    duplicates = TRUE;
				duplicateTagString = [duplicateTagString stringByAppendingString: stringJ];
			}
		}
	    i++;
    }
	
	duplicateTagString = [duplicateTagString stringByAppendingString: @"\n\n"];
	if( !filePass || duplicates )
	{
		duplicateTagString = [duplicateTagString stringByAppendingString: failFileString];
	    [self quit: duplicateTagString];
	}

    // Print tagsArray
	int j;
	for( j = 0; j < [tagsArray count]; j++ )
	{
	    NSLog( @"Tag table: %@", [tagsArray objectAtIndex: j]);
	}
	

}


// NAME: quit
// PURPOSE: If there is a problem, esp with loading tags, exit the program
// IMPORTS: nil
// EXPORTS: nil
// AUTHOR: Daniel Lee
// REMARKS: The window mainWindow may need to be changed so that the performClose
//          is not required and can stay in the system while he error message is
//          shown.
// LAST MODIFIED: 31-05-2006

- (void) quit: (NSString *) string
{
    NSString *error = @"Aborted: ";
	error = [error stringByAppendingString: string];
    // NSLog( error );

    [errorMessage setTitleWithMnemonic: error];
	[mainWindow performClose: self];
	[errorWindow makeKeyAndOrderFront: self];
}


// NAME: setBrailleTag
// PURPOSE: To get the value and insert a tag.
// IMPORTS: nil
// EXPORTS: nil
// AUTHOR: Daniel Lee
// REMARKS: None
// LAST MODIFIED: 01-06-2006

- (IBAction) setBrailleTag: (id) sender
{
    NSString* data = [brailleMenu titleOfSelectedItem];
	NSString* speakingText;
	NSTextView *input, *output;
    int locationOfCursor = 0;

    // Set the input and output variables to the respective ones which are to be used
	// This is done because the code is very similar on a lot of places and that the
	// program should be a s compact as possible.
	
    if( translateToBraille )
	{
        input = inputTextView;
        output = brailleTextView;
	}
	else
	{
        input = brailleTextView;
        output = inputTextView;	
	}

	locationOfCursor = [input selectedRange].location;
    [input insertText: data];
    speakingText = @"Tag inserted.";
    speakingText = [speakingText stringByAppendingString: data];

    // Output which grade had been changed
    if( ![speakingText isEqualToString: @""] )
	{
        [speechObject speak:speakingText];
    }
}


// NAME: errorOK
// PURPOSE: User presses OK on the error window. Shut down program
// IMPORTS: id
// EXPORTS: nil
// AUTHOR: Daniel Lee
// REMARKS: 
// LAST MODIFIED: 31-05-2006

- (IBAction) errorOK: (id) sender;
{
	exit(0);
}


// NAME: textDidChange
// PURPOSE: A delegate function to do the translation when the text is edited
// IMPORTS: NSNotification
// EXPORTS: nil
// AUTHOR: Daniel Lee
// REMARKS: If there is no tag at the start of the text, then the first tag is assumed to be used.
//          
// LAST MODIFIED: 13-07-2006

- (void) textDidChange: (NSNotification *) aNotification
{

	NSTextView *input, *output;
    NSRange range;
	NSString* lastBrailleTag;

    // Set the input and output variables to the respective ones which are to be used
	// This is done because the code is very similar on a lot of places and that the
	// program should be a s compact as possible.

    // if the notification is coming from the inputTextView then it is the box to take
	// text from and modify
    if( [aNotification object] == inputTextView )
	{
        input = inputTextView;
        output = brailleTextView;
		translateToBraille = TRUE;
		lastBrailleTag = [forwardFileArray objectAtIndex: 0];    // Take rule from first spot
	}
	else
	{
        input = brailleTextView;
        output = inputTextView;	
		translateToBraille = FALSE;
		lastBrailleTag = [reverseFileArray objectAtIndex: 0];    // Take rule from first spot
	}

    NSTextStorage* textInputString = [input textStorage];
    NSMutableAttributedString* copiedString = ( NSMutableAttributedString* ) [textInputString mutableCopy];
	NSString* stringToCheckTags = [copiedString string];
	
	// Stripping tags,
	// Firstly, check the entire code and then convert sections  
	unsigned int i = 0;
    unsigned int last_Tag_Location = 0;
	unsigned int current_Tag_Location = 0;
	NSString* outputString = @"";
    BOOL matchEndOfTag = FALSE;
    BOOL matchTable = FALSE;
	
	if( [stringToCheckTags length] > tagLengthLimit )   // string needs to be bigger than the tag to bother
    {
   	    NSLog( @"Length : %i", [stringToCheckTags length]);

	    while( i <= ( [stringToCheckTags length] - tagLengthLimit) ) // i is not to close to the end
        {
	        if( [stringToCheckTags characterAtIndex: i] == '<' )
	    	{
                range.location = i;    // Reusing the variable range for the tag searching

				int j = 1;

				// While loop to get the location of the end of the tag.
			    // If it's too long then it is not tag
				matchEndOfTag = FALSE;
				while( !matchEndOfTag && ( j < tagLengthLimit ) )
				{
			        if( [stringToCheckTags characterAtIndex: (i + j)] == '>' )
				    {
					    matchEndOfTag = TRUE;
					}
					else
					{
				        j++;
					}
				}
								
				range.length = j + 1;
                NSString *temp = [stringToCheckTags substringWithRange: range];

			    // match against the tagsArray
                matchTable = FALSE;
                int tagSearch = 0;  // Reused variable
				while( ( tagSearch < [tagsArray count] ) && !matchTable )
				{	
			        if( [temp isEqualToString: (NSString *) [tagsArray objectAtIndex: tagSearch]] )
				    {
					    matchTable = TRUE;
    					NSLog( @"Tag found in tagsArray: %@", temp);
					}
					else
					{
				        tagSearch++;
					}
				}

    			if( matchTable )    // matched to an item in table
	    		{
	    			current_Tag_Location = i + j + 1;    // End of the tag
                    range.location = last_Tag_Location;
			    	range.length = current_Tag_Location - last_Tag_Location - j - 1;    // To remove the tag
				
    				// Get substring from the text
	    			NSString* translationString = [stringToCheckTags substringWithRange: range];
	    			NSLog( @"Text to translate %@", translationString );
					// Fix up to include sending the file over
					outputString = [outputString stringByAppendingString: [translatorObject translateLine: translationString grade: lastBrailleTag]];

			        // Done last
			        last_Tag_Location = i + j + 1;    // To show the end of the tag
					if( translateToBraille )
					{
    				    lastBrailleTag = [forwardFileArray objectAtIndex: tagSearch];
                    }
					else
					{
					    lastBrailleTag = [reverseFileArray objectAtIndex: tagSearch];
					}
					// NSLog( @"Tag: %@\n", lastBrailleTag );
	    		}
		    }
		    i++;
		}    // End of while loop

		//NSLog( @"End of tag checks" );
	
    	// Catch the last section of text which hasn't been translated yet
	    if( last_Tag_Location < [stringToCheckTags length] )    // If there isn't a tag at the end
    	{
	        range.location = last_Tag_Location;
		    range.length = [stringToCheckTags length] - last_Tag_Location;
            NSString* lastBit = [stringToCheckTags substringWithRange: range];
		    NSLog( @"Last bit: %@ \n", lastBit );
			outputString = [outputString stringByAppendingString: [translatorObject translateLine: lastBit grade: lastBrailleTag]];
		}
    }
	else    // [stringToCheckTags length] <= tagLengthLimit
	{
	    NSLog( @"First tag Assumed" );
		outputString = [outputString stringByAppendingString: [translatorObject translateLine: outputString grade: lastBrailleTag]];
	}

    // Output to braille and ascii
	[output setString: outputString];

	textInputString = [brailleTextView textStorage];
    copiedString = ( NSMutableAttributedString* ) [textInputString mutableCopy];
    [asciiTextView setString: [copiedString string] ];

}


// NAME: textViewDidChangeSelection
// PURPOSE: A delegate function to do when the cursor has changed.
// IMPORTS: NSNotification
// EXPORTS: nil
// AUTHOR: Daniel Lee
// REMARKS: 
// LAST MODIFIED: 13-07-2006

- (void) textViewDidChangeSelection: (NSNotification *) aNotification
{

    if( ( [aNotification object] == inputTextView ) && ( oldDirection != TRUE ) )
	{
		translateToBraille = TRUE;
		oldDirection = TRUE;
		[speechObject speak: @"Translating from print to braille."];
	}
	else if( ( [aNotification object] == brailleTextView ) && ( oldDirection != FALSE ) )
	{
		translateToBraille = FALSE;
		oldDirection = FALSE;
		[speechObject speak: @"Translating from bralle to print."];
	}
}


@end
