/***********************************************************************
Controller class for Convert TEXT which converts between Mac and NeXT text.
Copyright (C) 1993 David John Burrowes

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

The author, David John Burrowes, can be reached at:
	davidjohn@kira.net.netcom.com
	David John Burrowes
	1926 Ivy #10
	San Mateo, CA 94403-1367
***********************************************************************/

#import "CharController.h"
#import "NeXTToMacText.h"
#import "MacToNeXTText.h"
#import "CRLFToNeXTText.h"
#import "NeXTToCRLFText.h"
#import "File.h"
#import <appkit/Cell.h>
#import <appkit/Matrix.h>

@implementation CharController



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		init
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This overrides the defalut init method.  But it does little else.
//		Other initializations done in AppDidInit, above...
//		Note that subclasses should OVERRIDE this 
//	Bugs:
//	History:
//	93.07.05	djb	Reworked conversion preferences.  Used tobe a boolean for yes or no
//				to indicate mac vs. next source conversion.  Now use a string to
//				indicate what kinda conversion should be default.  Non-intuitively,
//				I've decided to leave the default name the same (DoMacConversio)
//				and let it keep the old yes/no, as well as newer MacToNeXT, etc
//				options.  Icky, but provides some upward compat..  HOWEVER.
//				This has taught me, I think, the lesson that: defaults names should
//				be a bit general, and it's better if their options are meaningful rather
//				than booleans.  Both allow later changes without breaking things like
//				this.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- init
{
	//
	//	Get all the default values from the database
	//
	static NXDefaultsVector theDefaults =
	{
		{"UseCurlyQuotes", "NO"},
		{"UseIMv1", "NO"},
		{"DoMacConversion", "MacToNeXT"},
		{NULL, NULL}
	};
	CString	tempPreference;
	
	[super init];
	//
	//	Set up strings for the interface.
	//
	ConversionString = "Converting text files";
	SourcePrompt = "Source file:";
	DestPrompt = "Dest file:";
	DestExtension = "";
	DefaultsOwner = "MacToNeXTText";

	NXRegisterDefaults(DefaultsOwner, theDefaults);
	
	//
	//	Note that we completely ignore the old DoMacConversion preference.
	//	We neither read nor remove it.
	//

	UseCurlyQuotes = [self   GetBoolPref: "UseCurlyQuotes"];

	UseIMv1 = [self   GetBoolPref: "UseIMv1"];
	
	tempPreference = [self   GetPref: "DoMacConversion"];
	//
	//	New for 1.1.  Get a more detailed conversion type from the defaults database,
	//	and set our new ConversionType flag appropriately.
	//
	if ((strcmp(tempPreference, "MacToNeXT") == 0) ||
		(strcmp(tempPreference, "YES") == 0) )
		DefaultConversion = MacToNeXT;
	else if ((strcmp(tempPreference, "NeXTToMac") == 0) ||
		(strcmp(tempPreference, "NO") == 0) )
		DefaultConversion = NeXTToMac;
	else if (strcmp(tempPreference, "CRLFToNeXT") == 0)
		DefaultConversion = CRLFToNeXT;
	else if (strcmp(tempPreference, "NeXTToCRLF") == 0)
		DefaultConversion = NeXTToCRLF;

	//
	//	Since we are pretending to be a converter as well as controller, define our manager
	//	And, then set ourselves up to recieve the messages we sent to a converter!
	//
	myManager = NullInstance;

	converterInst = self;
	//
	//	Set up default converter.
	//
	theConverter = NullInstance;
	CurrentConversion = DefaultConversion;

	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		displayPreferences: 
//	Parameters:	the object that called us
//	Returns:		self
//	Stores:		none
//	Description:
//		This is called whenever we need to bring the preferences panel onto the screen.
//		Using the value in our internal preference variables, then we set the buttons
//		in the preference panel to reflect these, and then we show the panel.
//	History:
//		93.07.05	djb	Modified for 1.1's more complex default conversion choices.
//	Bugs:
//		Well, strictly speaking, this is only needed the first time this is called,
//		because thereafter the buttons maintain their own highlite properly.
//		Oh well.  This whole scheme is a bit awkward, given my trivial use, and the fact
//		that it really seems to be designed for bigger things.
//		This doesn't set any ResultObject fields. =(
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-displayPreferences: sender
{
	//
	//	Get the default preference values, and compare judge what buttons to
	//	highlight for the user.
	//
	if (UseIMv1 == YES)
		[UseIMv1Button  setIntValue: 1];
	else
		[UseIMv1Button  setIntValue: 0];
		
	if (UseCurlyQuotes == YES)
		[UseCurlyQuotesButton  setIntValue: 1];
	else
		[UseCurlyQuotesButton  setIntValue: 0];

	switch (DefaultConversion)
	{
		case MacToNeXT:
			[DefaultConversionButton  selectCellWithTag: MacToNeXT];
			break;
		case NeXTToMac: 
			[DefaultConversionButton  selectCellWithTag: NeXTToMac];
			break;
		case CRLFToNeXT:
			[DefaultConversionButton  selectCellWithTag: CRLFToNeXT];
			break;
		case NeXTToCRLF:
			[DefaultConversionButton  selectCellWithTag: NeXTToCRLF];
			break;
	}

	[prefPanel	makeKeyAndOrderFront:self];
	//
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine: SetIMV1: 
//	Parameters: the object that called us
//	Returns:		self
//	Stores:		nothing!
//	Description:
//		This method gets called whenever the user clicks on a button to change
//		the setting of whether we should use IMv1 when converting Mac files.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- SetIMV1: sender
{
	if ( [sender intValue]  == 1)
		UseIMv1 = YES;
	else
		UseIMv1 = NO;

	[self   SetBoolPref: "UseIMv1" To: UseIMv1];

	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		SetUseCurlyQuotes: 
//	Parameters:	the object that called us
//	Returns:		self
//	Stores:		nothing!
//	Description:
//		This method gets called whenever the user clicks on a button to change
//		the setting of whether we should convert to curly quotes when converting NeXT files.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-SetUseCurlyQuotes: sender
{
	if ( [sender intValue]  == 1)
		UseCurlyQuotes = YES;
	else
		UseCurlyQuotes = NO;

	[self   SetBoolPref: "UseCurlyQuotes" To: UseCurlyQuotes];

	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		SetDefaultConversion: 
//	Parameters:	the object that called us
//	Returns:		self
//	Stores:		nothing!
//	Description:
//		This method gets called whenever the user clicks on a button to change
//		the setting of whether we should assume files to be converted are Mac ones or not.
//	History:
//		93.07.05	djb	Modified for 1.1's richer conversion options.  NOTE: This
//					makes some assumptions that the sender's value will always be
//					one of it's conversionvalues.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-SetDefaultConversion: sender
{
	DefaultConversion =  [[sender selectedCell] tag] ;
	
	switch ( DefaultConversion )
	{
		case MacToNeXT:
			[self   SetPref: "DoMacConversion" To: "MacToNeXT"];
			break;
		case NeXTToMac: 
			[self   SetPref: "DoMacConversion" To: "NeXTToMac"];
			break;
		case CRLFToNeXT:
			[self   SetPref: "DoMacConversion" To: "CRLFToNeXT"];
			break;
		case NeXTToCRLF:
			[self   SetPref: "DoMacConversion" To: "NeXTToCRLF"];
			break;
	}

	CurrentConversion = DefaultConversion;

	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		PrepareToConvertMac 
//	Parameters:	the object that send this message
//	Returns:		self
//	Stores:		nothing
//	Description:
//		Sets the flag for the kind of conversion to do (not a mac one), and convert a file.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- PrepareToConvertMac:sender
{
	CurrentConversion = MacToNeXT;
	[self  PrepareForConversion: self];
	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		PrepareToConvertNeXT 
//	Parameters:	the object that send this message
//	Returns:		self
//	Stores:		nothing
//	Description:
//		Sets the flag for the kind of conversion to do (not a mac one), and convert a file.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- PrepareToConvertNeXT:sender
{
	CurrentConversion = NeXTToMac;
	[self 	PrepareForConversion: self];
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		PrepareToConvertCRLF 
//	Parameters:	the object that send this message
//	Returns:		self
//	Stores:		nothing
//	Description:
//		Sets the flag for the kind of conversion to do (CRLF to NeXT) and does the
//		conversion.  We set the CurrentConversion flag here to override the default
//		value it would have..
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- PrepareToConvertCRLF:sender
{
	CurrentConversion = CRLFToNeXT;
	[self 	PrepareForConversion: self];
	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		PrepareToConvertNeXTToCRLF
//	Parameters:	the object that send this message
//	Returns:		self
//	Stores:		nothing
//	Description:
//		Sets the flag for the kind of conversion to do (NeXT to CRLF) and does the
//		conversion.  We set the CurrentConversion flag here to override the default
//		value it would have..
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- PrepareToConvertNeXTToCRLF:sender
{
	CurrentConversion = CRLFToNeXT;
	[self 	PrepareForConversion: self];
	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		ReportTo:
//	Parameters:	The caller
//	Returns: 	none
//	Stores:		none
//	Description:
//		ConvertController's use this to tell the converter what object to report status
//		to.  That is, we must tell sender when we are, for example, 50% of the way
//		done so it can let the user know what's going on.  Subclasses should have no
//		need of subclassing this.  It will always be set up before a call to the main
//		conversion routine.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ReportTo: sender
{
	myManager = sender;
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		isThisAGoodFile:
//	Parameters:	A File instance
//	Returns: 	YES if it is, NO if it isn't.
//	Stores:		none
//	Description:
//		A converter, in addition to converting a file X to a file Y, should be able to
//		identify when a source file is a legitimate file for conversion.  In the case of
//		this abstract class, this method does nothing.  A subclass, however, will
//		subclass this method and have it examine the specified file, and determine
//		if it's a legit file or not.  It returns YES if the file is good, or NO if the file is
//		questionable or bad.  
//	Bugs:
//		We need to use the proposed Fact datatype here, instead of Boolean.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (Boolean) isThisAGoodFile: Instance
{
	return YES;
}




//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ConvertFrom:To: 
//	Parameters:	The file to be converted from, and the file to be converted to
//	Returns:		self
//	Stores:		error code
//	Description:
//		This uses the object stored in this object's instance variable theConverter
//		to convert the contents of the source file to a new form, and writes them out
//		into the destination file.  Note that this assumes that the converter is a subclass
//		of textConverter.  Basiclally, loop until we find the eof, readin 512 byte chunks,
//		converting, and writing out.  In the last time through, we find Eof, and
//		write out what we read in that was just before this eof, then quit when we
//		loop around.
//	History:
//		93.07.05	djb	Modified for the now 4 types of conversions, rather than the older 2.
//	Bugs
//		We ignore errors that the file access might generate.
//		(if we don't read 512 bytes, and we don't find eof, then we have an error.
//		but we don't check for it either )
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ConvertFrom: sourceFile To: destinationFile
{
	Pointer			sourceBuffer		= NewPointer(512);
	Pointer			destinationBuffer;
	PositiveInteger	numWritten,
					bytesRead		= 512;
	Boolean			eofFound			= NO;
	Real				modifier		=  100.0 / [sourceFile   FileSize];

	[super   ConvertFrom: sourceFile To: destinationFile];

	[MacConvertCommand	setEnabled: NO];
	[NextConvertCommand	setEnabled: NO];
	[CRLFConvertCommand	setEnabled: NO];
	[NeXT2CRLFConvertCommand	setEnabled: NO];
	[ConvertCommands	setEnabled: NO];
	//
	//	Kill any converter waiting around, then set up the proper one for this conversion.
	//
	if (theConverter != NullInstance)
		[theConverter   free];

	switch ( CurrentConversion )
	{
		case MacToNeXT:
			theConverter = [ [MacToNeXTText  alloc] init ];
			[theConverter  UseIM1: UseIMv1];
			ConversionString = "Converting Macintosh text to NeXT text";
			break;
		case NeXTToMac: 
			theConverter = [[NeXTToMacText alloc] init];
			[theConverter ConvertSingleQuotes: UseCurlyQuotes];
			ConversionString = "Converting NeXT text to Macintosh text";
			break;
		case CRLFToNeXT:
			theConverter = [ [CRLFToNeXTText  alloc] init ];
			ConversionString = "Converting CRLF text to NeXT text";
			break;
		case NeXTToCRLF:
			theConverter = [ [NeXTToCRLFText  alloc] init ];
			ConversionString = "Converting NeXT text to CRLF text";
			break;
	}
	//
	//	With the converter set up, set the flag so it will do the default conversion
	//	(stored in DoMacConversion) next time if none other is specified.
	//	(that is, if someone drags a file onto our window, do the default conversion,
	//	and not the one we are actually about to do).
	//
	CurrentConversion = DefaultConversion;


	[super   ConvertFrom: sourceFile To: destinationFile];

	while ((eofFound != YES) && (bytesRead == 512))
	{
		[sourceFile	Read: 512 BytesInto: sourceBuffer];
		bytesRead = [sourceFile   GetPositiveInteger];
		eofFound = [sourceFile   GetBooleanFrom: SECOND_RESULT];
		//
		//	If eof was found, then this is our last pass through the converter.
		//
		[theConverter	ConvertString: sourceBuffer WithLength: bytesRead];
		destinationBuffer = [theConverter	GetPointer];
		numWritten = [theConverter   GetPositiveIntegerFrom: SECOND_RESULT];
		[destinationFile	Write:  numWritten  BytesFrom: destinationBuffer];
		[myManager   SetPercentageDone: modifier * [sourceFile  GetCurrentPosition]];
	}
	[MacConvertCommand	setEnabled: YES];
	[NextConvertCommand	setEnabled: YES];
	[CRLFConvertCommand	setEnabled: YES];
	[NeXT2CRLFConvertCommand	setEnabled: YES];
	[ConvertCommands	setEnabled: YES];
	//
	return self;
}



@end

