/*	XGFileSpecifier.cpp
 *
 *		This is the file specifier object; this encapsulates the file
 *	and directory information for a file in a universally accessable
 *	object
 */

/**********************************************************************

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>
#include <XStringUtil.h>
#include <XError.h>
#include <XFile.h>
#include <XWindow.h>
#include <string.h>
#include <ctype.h>

/************************************************************************/
/*																		*/
/*	Construction/Destruction											*/
/*																		*/
/************************************************************************/

#if OPT_MACOS

/*$	XGFileSpecifier::XGFileSpecifier
 *
 *		Construct this file specifier from the data provided.
 */

XGFileSpecifier::XGFileSpecifier(FSSpec &f)
{
	fFileName = f;
}

#else

/*$	XGFileSpecifier::XGFileSpecifier
 *
 *		Construct this file specifier from the data provided.
 */

XGFileSpecifier::XGFileSpecifier(const char *f)
{
	fFileName = f;
}

#endif

/*$	XGFileSpecifier::XGFileSpecifier
 *
 *		Create an 'empty' file specifier. You must use a copy
 *	operation to initialize this before using.
 */

XGFileSpecifier::XGFileSpecifier()
{
#if OPT_MACOS
	fFileName.name[0] = 0;
#endif
}

/*$	XGFileSpecifier::XGFileSpecifier
 *
 *		Construct this file specifier from an alias handle.
 */

XGFileSpecifier::XGFileSpecifier(XGHandle &alias)
{
	(*this) = alias;
}

XGFileSpecifier::XGFileSpecifier(const XGFileSpecifier& alias)
{
	this->fFileName = alias.fFileName;
}

/*$	XGFileSpecifier::~XGFileSpecifier
 *
 *		Deconstruct me
 */

XGFileSpecifier::~XGFileSpecifier()
{
}

/************************************************************************/
/*																		*/
/*	Copy operators														*/
/*																		*/
/************************************************************************/

/*$	XGFileSpecifier::operator = 
 *
 *		Copy an OS specific file specifier into this object.
 */

#if OPT_MACOS
XGFileSpecifier &XGFileSpecifier::operator = (const FSSpec &f)
{
	fFileName = f;
	return *this;
}
#else
XGFileSpecifier &XGFileSpecifier::operator = (const char *f)
{
	fFileName = f;
	return *this;
}
#endif

/*$	XGFileSpecifier::operator =
 *
 *		Create this specifier from the alias record provided.
 */

XGFileSpecifier &XGFileSpecifier::operator = (XGHandle &alias)
{
#if OPT_MACOS
	extern long _GMacOSVersion;
	
	if ((_GMacOSVersion < 0x0700) || (alias.GetSize() == sizeof(FSSpec))) {
		/*
		 *	Either we only have a file specifier or we're running
		 *	an earlier version than System 7
		 */
		
		alias.GetMemory(0,sizeof(fFileName),(void *)&fFileName);
	} else {
		/*
		 *	We've got an alias...
		 */
		
		Handle h;
		long l;
		OSErr err;
		Boolean change;
		
		l = alias.GetSize() - sizeof(FSSpec);
		h = NewHandle(l);
		Assert(h != NULL);
		HLock(h);
		alias.GetMemory(sizeof(FSSpec),l,*h);
		HUnlock(h);
		
		err = ResolveAlias(NULL,(AliasHandle)h,&fFileName,&change);
		DisposeHandle(h);
		Assert(err == noErr);
	}
#else
	XGHandleLock lock(alias);
	
	fFileName = (char *)*alias;
#endif

	return *this;
}

/*$	XGFileSpecifier::operator =
 *
 *		Copy from file specifier
 */

XGFileSpecifier &XGFileSpecifier::operator = (const XGFileSpecifier &data)
{
	fFileName = data.fFileName;
	return *this;
}

bool XGFileSpecifier::operator == (const XGFileSpecifier &data) const
{
#if OPT_MACOS
	return data.fFileName.vRefNum == fFileName.vRefNum &&
			data.fFileName.parID == fFileName.parID &&
			!strcmp( (const char *)data.fFileName.name, (const char *)fFileName.name );
#else
	return data.fFileName == fFileName;
#endif
}


/************************************************************************/
/*																		*/
/*	Access operators													*/
/*																		*/
/************************************************************************/

/*$	XGFileSpecifier::GetAlias
 *
 *		Get the alias for this file. This alias provides a mechanism for
 *	finding this file later. This alias is only useful on one machine,
 *	but provids a way of finding that file later.
 */

void XGFileSpecifier::GetAlias(XGHandle &alias) const
{
#if OPT_MACOS
	extern long _GMacOSVersion;
	
	if (_GMacOSVersion < 0x0700) {
		alias.SetSize(sizeof(FSSpec));
		alias.SetMemory(0,sizeof(FSSpec),(char *)&fFileName);
	} else {
		AliasHandle h;
		OSErr err;
		long l;
		
		err = NewAlias(NULL,&fFileName,&h);

		Assert(err == noErr);
		l = GetHandleSize((Handle)h);
		alias.SetSize(sizeof(FSSpec) + l);

		HLock((Handle)h);
		alias.SetMemory(0,sizeof(FSSpec),(void *)&fFileName);
		alias.SetMemory(sizeof(FSSpec),l,(void *)*h);
		DisposeHandle((Handle)h);
	}
#else
	short len;
	const char *str;
	
	str = fFileName.Get();
	len = strlen(str) + 1;
	alias.SetSize(len);
	strcpy((char *)*alias,str);
#endif
}

/*$	XGFileSpecifier::OpenFile
 *
 *		Open this file using the ANSI access routines. This provides
 *	a way to use the standard ANSI file access routines fprintf(),
 *	fread(), fwrite(), etc., when reading/writing this file. Normally
 *	you wouldn't use this routine, but there it is anyways.
 */

FILE *XGFileSpecifier::OpenFile(const char *access) const
{
#if OPT_WINOS || OPT_XWIN
	return fopen(fFileName.Get(),access);
#else
	/*
	 *	MacOS version sets the default volume first
	 */
	
	FILE *f;
	char name[64];
	short wdRefNum;
	OSErr err;
	short vRefNum;
	
	GetName(name);
	err = OpenWD(fFileName.vRefNum,fFileName.parID,'ERIK',&wdRefNum);
	if (err) return NULL;
	GetVol(NULL,&vRefNum);
	SetVol(NULL,wdRefNum);
	
	f = fopen(name,access);
	
	SetVol(NULL,vRefNum);
	CloseWD(wdRefNum);
	
	return f;
#endif
}

/*$	XGFileSpecifier::SetFile
 *
 *		Set the file from the directory/name pair
 */

void XGFileSpecifier::SetFile(XGDirectory &dir, char *name)
{
	Assert(dir.fInit);
	
#if OPT_MACOS
	fFileName.vRefNum = dir.fVolRefNum;
	fFileName.parID = dir.fDirID;
	cvtc2p(fFileName.name,name);
#endif

#if OPT_WINOS || OPT_XWIN
	char buffer[512];
	char *x;
	
	strcpy(buffer,dir.fPath.Get());
	for (x = buffer; *x; x++) ;
	x--;

#if OPT_WINOS
	if (*x != '\\') strcat(buffer,"\\");
#else
	if (*x != '/') strcat(buffer,"/");
#endif

	strcat(buffer,name);
	fFileName = buffer;
#endif
}

/*$	XGFileSpecifier::GetName
 *
 *		Get the file name of this file. This extracts just the file
 *	name for naming windows
 */

void XGFileSpecifier::GetName(char *name) const
{
#if OPT_MACOS
	cvtp2c(name,fFileName.name);
#else
	const char *c,*d;

	d = fFileName.Get();
	for (c = d; *c != 0; c++) {
#if OPT_WINOS
		if ((*c == ':') || (*c == '\\')) d = c+1;
#else
		if (*c == '/') d = c+1;
#endif
	}
	
	strcpy(name,d);
#endif
}


void XGFileSpecifier::GetBaseName( char *name, int maxext ) const
{
	this->GetName( name );
	int len = strlen( name ) - 1;
	for (int i = len - 1; i > 0 && i >= len - maxext; --i) {
		if (name[i] == '.') {
			name[i] = 0;
			return;
		}
	}
}


void XGFileSpecifier::GetExtension( char *ext, int maxext ) const
{
	char name[256];
	GetName( name );
	char *ptr = strrchr( name, '.' );
	if (!ptr) {
		*ext = 0;
		return;
	}
	int len = strlen(name);
	if (ptr < name + len - 1 - maxext) {
		*ext = 0;
		return;
	}
	ptr += 1;
	do {
		*ext++ = tolower(*ptr);
	} while (*ptr++);
}


/*$	XGFileSpecifier::GetDirectory
 *
 *		Get the directory that this file is contained as a directory object.
 *	This overrides the directory object provided.
 */

void XGFileSpecifier::GetDirectory(XGDirectory &dir) const
{
#if OPT_MACOS
	dir.fInit = true;
	dir.fVolRefNum = fFileName.vRefNum;
	dir.fDirID = fFileName.parID;
#endif

#if OPT_WINOS || OPT_XWIN
	char *c,*d;
	char buffer[512];

	strcpy(buffer,fFileName.Get());
	d = buffer;
	for (c = d; *c != 0; c++) {
#if OPT_WINOS
		if ((*c == ':') || (*c == '\\')) d = c;
#else
		if (*c == '/') d = c;
#endif
	}
	
#if OPT_WINOS
	if (*d == '\\') {
		if (*(d-1) != ':') d--;
	}
#endif

#if OPT_XWIN
	if (*d == '/') {
		if (d != fFileName.Get()) d--;
	}
#endif

	d[1] = 0;
	dir.fPath = buffer;
	dir.fInit = true;
#endif
}


/************************************************************************/
/*																		*/
/*	File Dialogs (Mac)													*/
/*																		*/
/************************************************************************/

#if OPT_MACOS

/*$	XGFile::OpenDialog
 *
 *		Open the file by running the file open dialog. This returns NULL
 *	if there was an error or the file open dialog was canceled. The
 *	Macintosh version of this takes as it's arguments a 'paramResID',
 *	which refers to an 'Open' resource which specifies the file types
 *	that can be opened.
 *
 *		On Windows, the param string is a string giving the file types
 *	which can be opened. The parameters should be of the format:
 *
 *			(description) '|' *.ext '|' (description) '|' *.ext ...
 *
 *		The second parameter is the default file extension to be used
 *	if one is not provided in the open dialog box.
 *
 *		On Unix, the param string is a comma-separated list of
 *	extensions which can be opened.
 */

XGFileSpecifier *XGFileSpecifier::OpenDialog(short paramResID)
{
	SFTypeList list;
	Handle h;
	short i,len;
	FSSpec f;
	OSErr err;
	extern bool _GHasFSSpec;
	
	/*
	 *	Get resource information
	 */
	
	h = Get1Resource('Open',paramResID);
	Assert(h);
	len = *(short *)*h;
	if (len > 4) len = 4;
	for (i = 0; i < len; i++) {
		list[i] = ((OSType *)(2 + *h))[i];
	}
	if (len == 0) len = -1;
	
	/*
	 *	Get the specified file from the user
	 */
	
	if (_GHasFSSpec) {
		StandardFileReply sfReply;

		/*
		 *	Sys7 version of this uses StandardGetFile() routine
		 *	to get file specifier
		 */
		
		StandardGetFile(NULL,len,list,&sfReply);
		if (!sfReply.sfGood) return NULL;
		f = sfReply.sfFile;
	} else {
		Point pt;
		SFReply reply;
		
		/*
		 *	Sys6 (and earlier) version uses SFGetFile and
		 *	converts the wdRefNum to a FileSpec
		 */
		
		pt.h = 75, pt.v = 75;
		SFGetFile(pt,NULL,NULL,len,list,NULL,&reply);
		if (!reply.good) return NULL;
		
		pstrcpy(f.name,reply.fName);
		err = GetWDInfo(reply.vRefNum,&f.vRefNum,&f.parID,NULL);
		Assert(err == noErr);
	}
	
	/*
	 *	Create the file and open it
	 */
	
	return new XGFileSpecifier(f);
/*	file = new XGFile;
	file->Open(&spec,rwflag);
	return file; */
}

/*$	XGFile::SaveDialog
 *
 *		Run a save dialog and create a new file with the specified name.
 *
 *		On the Macintosh, this specifies a resource ID of a 'Save' resource
 *	with the default prompt, the creator and file type specifiers. The creator
 *	and file type specifier is used when the file is created.
 *
 *		On Windows, this specifies the file types, in the same format
 *	string used by OpenDialog. On Unix, this specifies the default extension
 *	to be used, as well as a prompt string.
 */
 
XGFileSpecifier *XGFileSpecifier::SaveDialog(short paramResID, OSType c, OSType t, char *defname)
{
	unsigned char prompt[256];
	unsigned char str[64];
	Handle h;
	FSSpec f;
	OSErr err;
	extern bool _GHasFSSpec;
	
	h = Get1Resource('Save',paramResID);
	Assert(h);
	pstrcpy(prompt,(unsigned char *)*h);
	ReleaseResource(h);

	/*
	 *	Get the specified file from the user
	 */
	
	cvtc2p(str,defname);
	if (_GHasFSSpec) {
		StandardFileReply sfReply;

		/*
		 *	Sys7 version of this uses StandardPutFile routine
		 *	to get file specifier
		 */
		
		StandardPutFile(prompt,str,&sfReply);
		if (!sfReply.sfGood) return NULL;
		f = sfReply.sfFile;
	} else {
		Point pt;
		SFReply reply;
		
		/*
		 *	Sys6 (and earlier) gets the file name and
		 *	converts into a file specifier
		 */
		
		pt.h = 75, pt.v = 75;
		SFPutFile(pt,prompt,str,NULL,&reply);
		if (!reply.good) return NULL;
		
		pstrcpy(f.name,reply.fName);
		err = GetWDInfo(reply.vRefNum,&f.vRefNum,&f.parID,NULL);
		Assert(err == noErr);
	}
	
	/*
	 *	Create the file and open it
	 */
	
	return new XGFileSpecifier( f );
/*	spec = f;
	file = new XGFile;
	file->Create(&spec,c,t);
	return file; */
}

#endif



/************************************************************************/
/*																		*/
/*	File Dialogs (Win)													*/
/*																		*/
/************************************************************************/

#if OPT_WINOS

/*	XGFileSpecifier::OpenDialog
 *
 *		Open the file by running the file open dialog. This returns NULL
 *	if there was an error or the file open dialog was canceled. The
 *	Macintosh version of this takes as it's arguments a 'paramResID',
 *	which refers to an 'Open' resource which specifies the file types
 *	that can be opened.
 *
 *		On Windows, the param string is a string giving the file types
 *	which can be opened. The parameters should be of the format:
 *
 *			(description) '|' *.ext '|' (description) '|' *.ext ...
 *
 *		The second parameter is the default file extension to be used
 *	if one is not provided in the open dialog box.
 *
 *		On Unix, the param string is a comma-separated list of
 *	extensions which can be opened.
 */

XGFileSpecifier *XGFileSpecifier::OpenDialog(char *type, char *ext)
{
	OPENFILENAME ofn;
	char buffer[256];
	char name[256];
	char *c;
	
	memset(&ofn,0,sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = _GMainWindow;
	ofn.hInstance = _GInstance;
	ofn.lpstrDefExt = ext;
	
	memset(buffer, 0, sizeof(buffer));
	strcpy(buffer,type);
	for (c = buffer; *c; c++) if (*c == '|') *c = '\0';
	ofn.lpstrFilter = buffer;
	
	name[0] = 0;
	ofn.lpstrFile = name;
	ofn.nMaxFile = 256;
	ofn.Flags = OFN_FILEMUSTEXIST | OFN_LONGNAMES;

	if (GetOpenFileName(&ofn)) {
		return new XGFileSpecifier( name );
	/*	XGFileSpecifier fspec;
		f = new XGFile;
		fspec = name;
		f->Open(&fspec,rwflag);
		return f; */
	} else return NULL;
}

/*	XGFile::SaveDialog
 *
 *		Run a save dialog and create a new file with the specified name.
 *
 *		On the Macintosh, this specifies a resource ID of a 'Save' resource
 *	with the default prompt, the creator and file type specifiers. The creator
 *	and file type specifier is used when the file is created.
 *
 *		On Windows, this specifies the file types, in the same format
 *	string used by OpenDialog. On Unix, this specifies the default extension
 *	to be used, as well as a prompt string.
 */

XGFileSpecifier *XGFileSpecifier::SaveDialog(char *type, char *ext, char *defname)
{
	OPENFILENAME ofn;
	char buffer[256];
	char name[256];
	char *c;
	
	memset(&ofn,0,sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = _GMainWindow;
	ofn.hInstance = _GInstance;
	ofn.lpstrDefExt = ext;
	
	memset(buffer, 0, sizeof(buffer));
	strcpy(buffer,type);
	for (c = buffer; *c; c++) if (*c == '|') *c = '\0';
	ofn.lpstrFilter = buffer;
	
	strcpy(name,defname);
	ofn.lpstrFile = name;
	ofn.nMaxFile = 256;
	ofn.Flags = OFN_OVERWRITEPROMPT | OFN_LONGNAMES;

	if (GetSaveFileName(&ofn)) {
		return new XGFileSpecifier( name );
/*		XGFileSpecifier fspec;
		
		f = new XGFile;
		fspec = name;
		f->Create(&fspec);
		return f; */
	} else return NULL;
}

#endif


bool XGFileSpecifier::ExchangeFileContents( XGFileSpecifier &file1, XGFileSpecifier &file2 )
{
#if OPT_MACOS
	return (FSpExchangeFiles( &file1.fFileName, &file2.fFileName ) == noErr);
#else
	XGFileSpecifier temp;
	temp = file1.TempInSameDirectory();
	temp.DeleteFile();
	if (RenameFile( file1, temp )) {
		if (RenameFile( file2, file1)) {
			if (RenameFile( temp, file2 )) {
				// success
				return true;
			} else {
				RenameFile( file1, file2 );
				RenameFile( temp, file1 );
				return false;
			}
		} else {
			RenameFile( temp, file1 );
			return false;
		}
	} else return false;
#endif
}

bool XGFileSpecifier::RenameFile(const XGFileSpecifier &file1, const XGFileSpecifier &file2 )
{
#if OPT_WINOS
	return MoveFile(file1.fFileName.Get(), file2.fFileName.Get());
#else
	return false;
#endif
}

#if OPT_MACOS
XGFileSpecifier XGFileSpecifier::TempInSameDirectory()
{
	int counter = 0;
	char name[256];
	FSSpec fs;
	for (;;) {
		sprintf( name+1, "temp-%x", ++counter );
		name[0] = strlen(name+1);
		OSStatus err;
		
		err = FSMakeFSSpec( fFileName.vRefNum, fFileName.parID, (unsigned char *)name, &fs );
		if (err == 0) continue;
		if (err != fnfErr) {
			fs.vRefNum = 0;
			fs.parID = 0;
			fs.name[0] = 0;
			break;
		}
		
		err = FSpCreate( &fs, 'temp', 'temp', smSystemScript );
		if (err == 0) break;
		if (err == dupFNErr) continue;
		FSpDelete( &fs );
		fs.vRefNum = 0;
		fs.parID = 0;
		fs.name[0] = 0;
		break;
	}
	
	return XGFileSpecifier( fs );
}
#else
XGFileSpecifier XGFileSpecifier::TempInSameDirectory()
{
	XGDirectory dir;
	this->GetDirectory( dir );
	
	int counter=0;
	char name[256];
	
	for(;;)
	{
		sprintf(name, "temp-%x", ++counter);
		XGFileSpecifier temp;
		temp.SetFile( dir, name );
		FILE *fp = temp.OpenFile( "rb" );
		if (!fp) {
			fp = temp.OpenFile( "wb" );
			if (fp) {
				fclose( fp );
				return temp;
			}
		}
		fclose(fp);
	}
}
#endif

#if OPT_MACOS
void XGFileSpecifier::DeleteFile()
{
	FSpDelete( &this->fFileName );
}
#else
void XGFileSpecifier::DeleteFile()
{
	::DeleteFile( fFileName.Get() );	
}
#endif




#if OPT_MACOS
void XGFileSpecifier::SetTypeCreator( OSType type, OSType creator )
{
	FInfo fi;
	if (FSpGetFInfo( &fFileName, &fi ) == noErr) {
		fi.fdType = type;
		fi.fdCreator = creator;
		FSpSetFInfo( &fFileName, &fi );
	}
}


OSType XGFileSpecifier::GetType()
{
	FInfo fi;
	if (FSpGetFInfo( &fFileName, &fi ) == noErr) return fi.fdType;
	return 0;
}


OSType XGFileSpecifier::GetCreator()
{
	FInfo fi;
	if (FSpGetFInfo( &fFileName, &fi ) == noErr) return fi.fdCreator;
	return 0;
}
#endif