// FileSaver.m
// By Charles G. Fleming and Judy D. Halchin, 
// Educational Computing Services, Allegheny College.
// Copyright 1992, 1993 Allegheny College
// You may freely copy, distribute and reuse this code. 
// Allegheny College and the authors disclaim any warranty of any kind, 
// expressed or implied, as to its fitness for any particular use.
// This work was partially supported by a grant from the Pew Charitable Trusts
// and by an NSF Teacher Preparation Grant.

#import "SaverAndRetriever.h"

#import <objc/error.h>

#import <appkit/SavePanel.h>
#import <appkit/Application.h>

#import <stdlib.h>
#import <strings.h>

@implementation FileSaver

- (const char *)getInspectorClassName
{
	return "FileSaverAndRetrieverInspector";
}

// Initially the filename is the empty string, and the delegate and 
// dataDelegate are nil.
- init
{
	self = [super init];	

	filename = (char *)malloc(MAXPATHLEN + 1);
	directory = (char *)malloc(MAXPATHLEN + 1);	
	strcpy(filename, "");
	strcpy(directory, NXHomeDirectory());	
	return self;
}

- setDelegate:anObject
{
	delegate = anObject;
	return self;
}

- setDataDelegate:anObject
{
	dataDelegate = anObject;
	return self;
}	
	
- setFileRetriever:anObject
{
	fileRetriever = anObject;
	return self;
}
 
// Returns the file saver's delegate.
- delegate
{
	return delegate;
}

// Returns the file saver's dataDelegate.
- dataDelegate
{
	return dataDelegate;
}
	
// Returns the fileRetriever with which this FileSaver works
- fileRetriever
{
	return fileRetriever;
}

// Set the name of the file where the data will be stored.  This message 
// can be sent by a FileRetriever object. 
- setFilename:(char *)name
{
	strcpy(filename, name);
	return self;
}

// Set the path to the directory of filename.  This message 
// can be sent by a FileRetriever object. 
- setDirectory:(char *)path
{
	strcpy(directory, path);
	return self;
}

// Returns the name of the file where the data will be stored.
- (char *)filename
{
	return filename;
}
			
// Returns the directory path of the file where the data will be stored.
- (char *)directory
{
	return directory;
}
			
// If there is a file, call save, otherwise run a save panel. The save or
// saveAs method will notify the delegate of any errors.
- save:sender
{
	if(strcmp(filename, "") != 0)
		[self save];
	else
		[self saveAs:self];	
	return self;
}

// Run a save panel.  If the cancel button is not pushed, call save.
- saveAs:sender
{
	SavePanel *savePanel = [SavePanel new];
	char tempFilename[MAXPATHLEN], *lastSlash;
	int count;
	
	// Try to run a SavePanel.  If unable to, raise an exception.	
	if([savePanel runModalForDirectory:directory file:filename])
	{
		// Get the filename from the save panel and split into directory
		// and file.
		strcpy(tempFilename, [savePanel filename]);
		lastSlash = strrchr(tempFilename, '/');
		strcpy(filename, lastSlash + 1);
		if(lastSlash == tempFilename)
		{
			count = 1;
			directory[1] = '\0';
		}	
		else 
		{
			count = lastSlash - tempFilename;	
			directory[lastSlash - tempFilename] = '\0';
		}	
		strncpy(directory, tempFilename, count);
		
		[self save];
	}
	else
		if([delegate respondsTo:
				@selector(dataWasWritten:success:errorCode:)])
			[delegate dataWasWritten:self success:NO
					errorCode:cancelButtonPushed];	
	return self;
}

// If the information has been saved to a file, go ahead and use that file.	
// The dataDelegate must write the data to the stream provided.
// Any errors that occur will be handled by an exception handler.
// If the data has not been saved previously, call saveAs:.
- (BOOL)save
{
	NXStream *stream;
	char fullPathname[MAXPATHLEN];
	BOOL fileSaved = NO;

	NX_DURING;
	if(strcmp(filename, "") != 0)
	{
		// Open a stream on memory.
		stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);

		// Raise an exception if we were unable open a stream on memory
		if(!stream)
			NX_RAISE(noStream, self, delegate);	
		
		// Raise an exception if there is no dataDelegate.
		if(dataDelegate == nil)
			NX_RAISE(noDataDelegate, self, delegate);
			
		// Send writeDataToStream: to the dataDelegate if it implements it. 
		// if not, raise an exception.
		if([dataDelegate respondsTo:@selector(writeDataToStream:)])
		{
			if (![dataDelegate writeDataToStream:stream])
				NX_RAISE(dataNotWritten, self, delegate);
		}
		else
			NX_RAISE(doesNotRespondToMethod, self, delegate);

		// Construct the full pathname from the directory and file.  If
		// the directory is "", we'll use the user's home directory.
		if(strcmp(directory, "") == 0)
			strcpy(directory, NXHomeDirectory());
		
		// Try to save the data.  If unable to, raise an exception.	
		sprintf(fullPathname, "%s/%s", directory, filename);			
		if(NXSaveToFile(stream, fullPathname) == -1)
			NX_RAISE(unableToSaveStreamToFile, self, delegate);	

		// If all went well, tell the delegate that the data has been saved
		// and tell the fileRetriever the new filename.
		if([delegate
				respondsTo:@selector(dataWasWritten:success:errorCode:)])
			[delegate dataWasWritten:self success:YES errorCode:noError];
		if([fileRetriever respondsTo:@selector(setFilename:)])
			[fileRetriever setFilename:filename];
		if([fileRetriever respondsTo:@selector(setDirectory:)])
			[(FileRetriever *)fileRetriever setDirectory:directory];
		fileSaved = YES;	
	}	
	else
		if([delegate respondsTo: 
				@selector(dataWasWritten:success:errorCode:)])
			[delegate dataWasWritten:self success:NO errorCode:noFile];

	// Here's the error handler.
	NX_HANDLER
		switch(NXLocalHandler.code)	
		{
			case noStream: 
				if([(id)NXLocalHandler.data2 
						respondsTo: 	
						@selector(dataWasWritten:success:errorCode:)])
					[(id)NXLocalHandler.data2 	
							dataWasWritten:(id)NXLocalHandler.data1 
							success:NO
							errorCode:noStream];	
				break;
			case noDataDelegate: 
				if([(id)NXLocalHandler.data2 
						respondsTo: 	
						@selector(dataWasWritten:success:errorCode:)])
					[(id)NXLocalHandler.data2 	
							dataWasWritten:(id)NXLocalHandler.data1 
							success:NO
							errorCode:noDataDelegate];	
				break;
			case doesNotRespondToMethod: 
				if([(id)NXLocalHandler.data2 
						respondsTo: 	
						@selector(dataWasWritten:success:errorCode:)])
					[(id)NXLocalHandler.data2 	
							dataWasWritten:(id)NXLocalHandler.data1 
							success:NO
							errorCode:doesNotRespondToMethod];	
				break;
			case dataNotWritten: 
				if([(id)NXLocalHandler.data2 
						respondsTo: 	
						@selector(dataWasWritten:success:errorCode:)])
					[(id)NXLocalHandler.data2 	
							dataWasWritten:(id)NXLocalHandler.data1 
							success:NO
							errorCode:dataNotWritten];	
				break;
			case unableToSaveStreamToFile:
				if([(id)NXLocalHandler.data2 
						respondsTo: 	
						@selector(dataWasWritten:success:errorCode:)])
					[(id)NXLocalHandler.data2 	
							dataWasWritten:(id)NXLocalHandler.data1 
							success:NO
							errorCode:unableToSaveStreamToFile];	
				break;
			default:
				NX_RERAISE();
				break;
		}
	NX_ENDHANDLER;
		
	//Close the stream.
	if(stream)
		NXCloseMemory(stream, NX_FREEBUFFER);
	return fileSaved;
}

- write:(NXTypedStream *)typedStream
{
	[super write:typedStream];
	NXWriteTypes(typedStream, "**", &directory, &filename);
	return self;
}
	
- read:(NXTypedStream *)typedStream
{
	char *tempDirectory, *tempFilename;
	
	filename = (char *)malloc(MAXPATHLEN + 1);
	directory = (char *)malloc(MAXPATHLEN + 1);

	[super read:typedStream];
	NXReadTypes(typedStream, "**", &tempDirectory, &tempFilename);
	
	strcpy(filename, tempFilename);
	strcpy(directory, tempDirectory);
	return self;
}	
@end

