/* 
 * tclMacUtil.c --
 *
 *  This contains utility functions used to help with
 *  implementing Macintosh specific portions of the Tcl port.
 *
 * Copyright (c) 1993-1994 Lockheed Missle & Space Company, AI Center
 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tclMacUtil.c,v 1.7 2003/03/03 20:22:44 das Exp $
 */

#include "tcl.h"
#include "tclInt.h"
#include "tclMacInt.h"
#include "tclMath.h"
#include "tclMacPort.h"

#include <Aliases.h>
#include <Errors.h>
#include <Files.h>
#include <Folders.h>
#include <FSpCompat.h>
#include <Strings.h>
#include <TextUtils.h>
#include <MoreFilesExtras.h>

/* 
 * The following two Includes are from the More Files package.
 */
#include <FileCopy.h>
#include <MoreFiles.h>

/*
 *----------------------------------------------------------------------
 *
 * hypotd --
 *
 *	The standard math function hypot is not supported by Think C.
 *	It is included here so everything works. It is supported by
 *	CodeWarrior Pro 1, but the 68K version does not support doubles.
 *	So we hack it in.
 *
 * Results:
 *	Result of computation.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
 
#if defined(THINK_C)
double hypotd(double x, double y);

double
hypotd(
    double x,		/* X value */
    double y)		/* Y value */
{
    double sum;

    sum = x*x + y*y;
    return sqrt(sum);
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * FSpGetDefaultDir --
 *
 *	This function gets the current default directory.
 *
 * Results:
 *	The provided FSSpec is changed to point to the "default"
 *	directory.  The function returns what ever errors
 *	FSMakeFSSpecCompat may encounter.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
FSpGetDefaultDir(
	FSSpecPtr dirSpec)	/* On return the default directory. */
{
    OSErr err;
    short vRefNum = 0;
    long int dirID = 0;

    err = HGetVol(NULL, &vRefNum, &dirID);
	
    if (err == noErr) {
	err = FSMakeFSSpecCompat(vRefNum, dirID, (ConstStr255Param) NULL,
		dirSpec);
    }
	
    return err;
}

/*
 *----------------------------------------------------------------------
 *
 * FSpSetDefaultDir --
 *
 *	This function sets the default directory to the directory
 *	pointed to by the provided FSSpec.
 *
 * Results:
 *	The function returns what ever errors HSetVol may encounter.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
FSpSetDefaultDir(
	FSSpecPtr dirSpec)	/* The new default directory. */
{
    OSErr err;

    /*
     * The following special case is needed to work around a bug
     * in the Macintosh OS.  (Acutally PC Exchange.)
     */
    
    if (dirSpec->parID == fsRtParID) {
	err = HSetVol(NULL, dirSpec->vRefNum, fsRtDirID);
    } else {
	err = HSetVol(dirSpec->name, dirSpec->vRefNum, dirSpec->parID);
    }
    
    return err;
}

/*
 *----------------------------------------------------------------------
 *
 * FSpFindFolder --
 *
 *	This function is a version of the FindFolder function that 
 *	returns the result as a FSSpec rather than a vRefNum and dirID.
 *
 * Results:
 *	Results will be simaler to that of the FindFolder function.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

OSErr
FSpFindFolder(
    short vRefNum,		/* Volume reference number. */
    OSType folderType,		/* Folder type taken by FindFolder. */
    Boolean createFolder,	/* Should we create it if non-existant. */
    FSSpec *spec)		/* Pointer to resulting directory. */
{
    short foundVRefNum;
    long foundDirID;
    OSErr err;

    err = FindFolder(vRefNum, folderType, createFolder,
	    &foundVRefNum, &foundDirID);
    if (err != noErr) {
	return err;
    }
		
    err = FSMakeFSSpecCompat(foundVRefNum, foundDirID, "\p", spec);
    return err;
}

static int
FSpLocationFromPathAlias _ANSI_ARGS_((int length, CONST char *path,
	FSSpecPtr fileSpecPtr, Boolean resolveLink));

/*
 *----------------------------------------------------------------------
 *
 * FSpLocationFromPath --
 *
 *	This function obtains an FSSpec for a given macintosh path.
 *	Unlike the More Files function FSpLocationFromFullPath, this
 *	function will also accept partial paths and resolve any aliases
 *	along the path.  
 *
 * Results:
 *	OSErr code.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
FSpLocationFromPath(
    int length,			/* Length of path. */
    CONST char *path,		/* The path to convert. */
    FSSpecPtr fileSpecPtr)	/* On return the spec for the path. */
{
	return FSpLocationFromPathAlias(length, path, fileSpecPtr, TRUE);
}

/*
 *----------------------------------------------------------------------
 *
 * FSpLLocationFromPath --
 *
 *	This function obtains an FSSpec for a given macintosh path.
 *	Unlike the More Files function FSpLocationFromFullPath, this
 *	function will also accept partial paths and resolve any aliases
 *	along the path expect for the last path component.
 *
 * Results:
 *	OSErr code.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
FSpLLocationFromPath(
    int length,			/* Length of path. */
    CONST char *path,		/* The path to convert. */
    FSSpecPtr fileSpecPtr)	/* On return the spec for the path. */
{
	return FSpLocationFromPathAlias(length, path, fileSpecPtr, FALSE);
}

static int
FSpLocationFromPathAlias(
    int length,			/* Length of path. */
    CONST char *path,		/* The path to convert. */
    FSSpecPtr fileSpecPtr,	/* On return the spec for the path. */
    Boolean resolveLink)	/* Resolve the last path component? */
{
    Str255 fileName;
    OSErr err;
    short vRefNum;
    long dirID;
    int pos, cur;
    Boolean isDirectory;
    Boolean wasAlias=FALSE;
    FSSpec lastFileSpec;

    /*
     * Check to see if this is a full path.  If partial
     * we assume that path starts with the current working
     * directory.  (Ie. volume & dir = 0)
     */
    vRefNum = 0;
    dirID = 0;
    cur = 0;
    if (length == 0) {
        return fnfErr;
    }
    if (path[cur] == ':') {
	cur++;
	if (cur >= length) {
	    /*
	     * If path = ":", just return current directory.
	     */
	    FSMakeFSSpecCompat(0, 0, NULL, fileSpecPtr);
	    return noErr;
	}
    } else {
	while (path[cur] != ':' && cur < length) {
	    cur++;
	}
	if (cur > 255) {
	    return bdNamErr;
	}
	if (cur < length) {
	    /*
	     * This is a full path
	     */
	    cur++;
	    strncpy((char *) fileName + 1, path, cur);
	    fileName[0] = cur;
	    err = FSMakeFSSpecCompat(0, 0, fileName, fileSpecPtr);
	    if (err != noErr) return err;
	    FSpGetDirectoryID(fileSpecPtr, &dirID, &isDirectory);
	    vRefNum = fileSpecPtr->vRefNum;
	} else {
	    cur = 0;
	}
    }
    
    isDirectory = 1;
    while (cur < length) {
	if (!isDirectory) {
	    return dirNFErr;
	}
	pos = cur;
	while (path[pos] != ':' && pos < length) {
	    pos++;
	}
	if (pos == cur) {
	    /* Move up one dir */
	    /* cur++; */
	    strcpy((char *) fileName + 1, "::");
	    fileName[0] = 2;
	} else if (pos - cur > 255) {
	    return bdNamErr;
	} else {
	    strncpy((char *) fileName + 1, &path[cur], pos - cur);
	    fileName[0] = pos - cur;
	}
	err = FSMakeFSSpecCompat(vRefNum, dirID, fileName, fileSpecPtr);
	if (err != noErr) return err;
	lastFileSpec=*fileSpecPtr;
	err = ResolveAliasFile(fileSpecPtr, true, &isDirectory, &wasAlias);
	if (err != noErr) {
	    /* ignore alias resolve errors on last path component */
	    if (pos < length) return err;
	    else *fileSpecPtr=lastFileSpec;
	}
	FSpGetDirectoryID(fileSpecPtr, &dirID, &isDirectory);
	vRefNum = fileSpecPtr->vRefNum;
	cur = pos;
	if (path[cur] == ':') {
	    cur++;
	}
    }
    
    if(!resolveLink && wasAlias)
    	*fileSpecPtr=lastFileSpec;
    
    return noErr;
}

/*
 *----------------------------------------------------------------------
 *
 * FSpPathFromLocation --
 *
 *	This function obtains a full path name for a given macintosh
 *	FSSpec.  Unlike the More Files function FSpGetFullPath, this
 *	function will return a C string in the Handle.  It also will
 *	create paths for FSSpec that do not yet exist.
 *
 * Results:
 *	OSErr code.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

OSErr
FSpPathFromLocation(
    FSSpec *spec,		/* The location we want a path for. */
    int *length,		/* Length of the resulting path. */
    Handle *fullPath)		/* Handle to path. */
{
    OSErr err;
    FSSpec tempSpec;
    CInfoPBRec pb;
	
    *fullPath = NULL;
	
    /* 
     * Make a copy of the input FSSpec that can be modified.
     */
    BlockMoveData(spec, &tempSpec, sizeof(FSSpec));
	
    if (tempSpec.parID == fsRtParID) {
	/* 
	 * The object is a volume.  Add a colon to make it a full 
	 * pathname.  Allocate a handle for it and we are done.
	 */
	tempSpec.name[0] += 2;
	tempSpec.name[tempSpec.name[0] - 1] = ':';
	tempSpec.name[tempSpec.name[0]] = '\0';
		
	err = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]);
    } else {
	/* 
	 * The object isn't a volume.  Is the object a file or a directory? 
	 */
	pb.dirInfo.ioNamePtr = tempSpec.name;
	pb.dirInfo.ioVRefNum = tempSpec.vRefNum;
	pb.dirInfo.ioDrDirID = tempSpec.parID;
	pb.dirInfo.ioFDirIndex = 0;
	err = PBGetCatInfoSync(&pb);

	if ((err == noErr) || (err == fnfErr)) {
	    /* 
	     * If the file doesn't currently exist we start over.  If the
	     * directory exists everything will work just fine.  Otherwise we
	     * will just fail later.  If the object is a directory, append a
	     * colon so full pathname ends with colon, but only if the name is
	     * not empty.  NavServices returns FSSpec's with the parent ID set,
	     * but the name empty...
	     */
	    if (err == fnfErr) {
		BlockMoveData(spec, &tempSpec, sizeof(FSSpec));
	    } else if ( (pb.hFileInfo.ioFlAttrib & ioDirMask) != 0 ) {
	        if (tempSpec.name[0] > 0) {
		    tempSpec.name[0] += 1;
		    tempSpec.name[tempSpec.name[0]] = ':';
		}
	    }
			
	    /* 
	     * Create a new Handle for the object - make it a C string.
	     */
	    tempSpec.name[0] += 1;
	    tempSpec.name[tempSpec.name[0]] = '\0';
	    err = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]);
	    if (err == noErr) {
		/* 
		 * Get the ancestor directory names - loop until we have an 
		 * error or find the root directory.
		 */
		pb.dirInfo.ioNamePtr = tempSpec.name;
		pb.dirInfo.ioVRefNum = tempSpec.vRefNum;
		pb.dirInfo.ioDrParID = tempSpec.parID;
		do {
		    pb.dirInfo.ioFDirIndex = -1;
		    pb.dirInfo.ioDrDirID = pb.dirInfo.ioDrParID;
		    err = PBGetCatInfoSync(&pb);
		    if (err == noErr) {
			/* 
			 * Append colon to directory name and add 
			 * directory name to beginning of fullPath.
			 */
			++tempSpec.name[0];
			tempSpec.name[tempSpec.name[0]] = ':';
						
			(void) Munger(*fullPath, 0, NULL, 0, &tempSpec.name[1],
				tempSpec.name[0]);
			err = MemError();
		    }
		} while ( (err == noErr) &&
			(pb.dirInfo.ioDrDirID != fsRtDirID) );
	    }
	}
    }
    
    /*
     * On error Dispose the handle, set it to NULL & return the err.
     * Otherwise, set the length & return.
     */
    if (err == noErr) {
	*length = GetHandleSize(*fullPath) - 1;
    } else {
	if ( *fullPath != NULL ) {
	    DisposeHandle(*fullPath);
	}
	*fullPath = NULL;
	*length = 0;
    }

    return err;
}

/*
 *----------------------------------------------------------------------
 *
 * GetGlobalMouseTcl --
 *
 *	This procedure obtains the current mouse position in global
 *	coordinates.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
GetGlobalMouseTcl(
    Point *mouse)		/* Mouse position. */
{
    EventRecord event;
    
    OSEventAvail(0, &event);
    *mouse = event.where;
}

pascal OSErr	FSpGetDirectoryIDTcl (CONST FSSpec * spec, 
				long * theDirID, Boolean * isDirectory)
{
	return(FSpGetDirectoryID(spec, theDirID, isDirectory));
}

pascal short	FSpOpenResFileCompatTcl (CONST FSSpec * spec, SignedByte permission)
{
	return(FSpOpenResFileCompat(spec,permission));
}

pascal void	FSpCreateResFileCompatTcl (
				CONST FSSpec * spec, OSType creator, 
				OSType fileType, ScriptCode scriptTag)
{
	FSpCreateResFileCompat (spec,creator,fileType,scriptTag);
}
