/* 
 * tclMacEnv.c --
 *
 *	Implements the "environment" on a Macintosh.
 *
 * Copyright (c) 1995-1996 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
 */

#include <Gestalt.h>
#include <Folders.h>
#include <TextUtils.h>
#include <Resources.h>
#include <string.h>

#include "tcl.h"
#include "tclInt.h"
#include "tclMacInt.h"
#include "tclPort.h"

#define kMaxEnvStringSize 	255
#define kMaxEnvVarSize 		100
#define kLoginnameTag 		"LOGIN="
#define kUsernameTag 		"USER="
#define kDefaultDirTag 		"HOME="

/* 
 * The following specifies a text file where additional environment variables 
 * can be set.  The file must reside in the preferences folder.  If the file 
 * doesn't exist NO error will occur.  Commet out the difinition if you do 
 * NOT want to use an environment variables file. 
 */
#define kPrefsFile	 		"Tcl Environment Variables"

/* 
 * The following specifies the Name of a 'STR#' resource in the application 
 * where additional environment variables may be set.  If the resource doesn't
 * exist no errors will occur.  Commet it out if you don't want it.
 */
#define REZ_ENV "\pTcl Environment Variables"

/* Globals */
char **environ = NULL;

/*
 * Declarations for local procedures defined in this file:
 */
static char ** RezRCVariables _ANSI_ARGS_((void));
static char ** FileRCVariables _ANSI_ARGS_((void));
static char ** PathVariables _ANSI_ARGS_((void));
static char ** SystemVariables _ANSI_ARGS_((void));
static char * MakeFolderEnvVar _ANSI_ARGS_((char * prefixTag,
	long whichFolder));
static char * GetUserName _ANSI_ARGS_((void));

/*
 *----------------------------------------------------------------------
 *
 * RezRCVariables --
 *
 *  Creates environment variables from the applications resource fork.
 *  The function looks for the 'STR#' resource with the name defined
 *  in the #define REZ_ENV.  If the define is not defined this code
 *  will not be included.  If the resource doesn't exist or no strings
 *  reside in the resource nothing will happen.
 *
 * Results:
 *	ptr to value on success, NULL if error.
 *
 * Side effects:
 *	Memory is allocated and returned to the caller.
 *
 *----------------------------------------------------------------------
 */

#ifdef REZ_ENV
static char ** 
RezRCVariables()
{
    Handle envStrs = NULL;
    char** rezEnv = NULL;
    short int numStrs;

    envStrs = GetNamedResource('STR#', REZ_ENV);
    if (envStrs == NULL) return NULL;
    numStrs = *((short *) (*envStrs));
	
    rezEnv = (char **) ckalloc((numStrs + 1) * sizeof(char *));

    if (envStrs != NULL) {
	ResType	theType;
	Str255 theName;
	short theID, index = 1;
	int i = 0;
	char* string;
	
	GetResInfo(envStrs, &theID, &theType, theName);
	for(;;) {
	    GetIndString(theName, theID, index++);
	    if (theName[0] == '\0') break;
	    string = (char *) ckalloc(theName[0] + 2);
	    strncpy(string, (char *) theName + 1, theName[0]);
	    string[theName[0]] = '\0';
	    rezEnv[i++] = string;
	}
	ReleaseResource(envStrs);
		
	rezEnv[i] = NULL;
	return rezEnv;
    }
	
    return NULL;
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * FileRCVariables --
 *
 *  Creates environment variables from a file in the system preferences
 *  folder.  The function looks for a file in the preferences folder 
 *  a name defined in the #define kPrefsFile.  If the define is not 
 *  defined this code will not be included.  If the resource doesn't exist or
 *  no strings reside in the resource nothing will happen.
 *
 * Results:
 *	ptr to value on success, NULL if error.
 *
 * Side effects:
 *	Memory is allocated and returned to the caller.
 *
 *----------------------------------------------------------------------
 */

#ifdef kPrefsFile
static char ** 
FileRCVariables()
{
    char *prefsFolder = NULL;
    char *tempPtr = NULL;
    char **fileEnv = NULL;
    FILE *thePrefsFile = NULL;
    int	i;
    FSSpec prefDir;
    OSErr err;
    Handle theString = NULL;
    Tcl_Channel chan;
    int size;
    Tcl_DString lineRead;
	
    err = FSpFindFolder(kOnSystemDisk, kPreferencesFolderType, 
	    kDontCreateFolder, &prefDir);
    if (err != noErr) {
	return NULL;
    }
    err = FSpPathFromLocation(&prefDir, &size, &theString);
    if (err != noErr) {
	return NULL;
    }
    (void) Munger(theString, size, NULL, 0, kPrefsFile, strlen(kPrefsFile));

    HLock(theString);
    chan = Tcl_OpenFileChannel(NULL, *theString, "r", 0);
    HUnlock(theString);
    DisposeHandle(theString);
    if (chan == NULL) {
	return NULL;
    }

    /*
     * We found a env file.  Let start parsing it.
     */
    fileEnv = (char **) ckalloc((kMaxEnvVarSize + 1) * sizeof(char *));

    i = 0;
    Tcl_DStringInit(&lineRead);
    while (Tcl_Gets(chan, &lineRead) != -1) {
	/*
	 * First strip off new line char
	 */
	if (lineRead.string[lineRead.length-1] == '\n') {
	    lineRead.string[lineRead.length-1] = '\0';
	}
	if (lineRead.string[0] == '\0' || lineRead.string[0] == '#') {
	    /*
	     * skip empty lines or commented lines
	     */
	    Tcl_DStringSetLength(&lineRead, 0);
	    continue;
	}
		
	tempPtr = (char *) ckalloc(lineRead.length + 1);
	strcpy(tempPtr, lineRead.string);
	fileEnv[i++] = tempPtr;
	Tcl_DStringSetLength(&lineRead, 0);
    }
	
    fileEnv[i] = NULL;
    Tcl_Close(NULL, chan);
    Tcl_DStringFree(&lineRead);
	
    return fileEnv;
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * MakeFolderEnvVar --
 *
 *	This function creates "environment" variable by taking a prefix and
 *	appending a folder path to a directory.  The directory is specified
 *	by a integer value acceptable by the FindFolder function.
 *
 * Results:
 *	The function returns an *allocated* string.  If the folder doesn't
 *	exist the return string is still allocated and just contains the
 *	given prefix.
 *
 * Side effects:
 *	Memory is allocated and returned to the caller.
 *
 *----------------------------------------------------------------------
 */

static char * 
MakeFolderEnvVar(
    char * prefixTag,		/* Prefix added before result. */
    long whichFolder)		/* Constant for FSpFindFolder. */
{
    char * thePath = NULL;
    char * result = NULL;
    OSErr theErr = noErr;
    Handle theString = NULL;
    FSSpec theFolder;
    int size;
    Tcl_DString pathStr;
    Tcl_DString tagPathStr;
    
    Tcl_DStringInit(&pathStr);
    theErr = FSpFindFolder(kOnSystemDisk, whichFolder, 
	    kDontCreateFolder, &theFolder);
    if (theErr == noErr) {
	theErr = FSpPathFromLocation(&theFolder, &size, &theString);
		
	HLock(theString);
	tclPlatform = TCL_PLATFORM_MAC;
	Tcl_DStringAppend(&pathStr, *theString, -1);
	HUnlock(theString);
	DisposeHandle(theString);
		
	Tcl_DStringInit(&tagPathStr);
	Tcl_DStringAppend(&tagPathStr, prefixTag, strlen(prefixTag));
	Tcl_DStringAppend(&tagPathStr, pathStr.string, pathStr.length);
	Tcl_DStringFree(&pathStr);
	
	/*
	 * Make sure the path ends with a ':'
	 */
	if (tagPathStr.string[tagPathStr.length - 1] != ':') {
	    Tcl_DStringAppend(&tagPathStr, ":", 1);
	}

	/*
	 * Don't free tagPathStr - rather make sure it's allocated
	 * and return it as the result.
	 */
	if (tagPathStr.string == tagPathStr.staticSpace) {
	    result = (char *) ckalloc(tagPathStr.length + 1);
	    strcpy(result, tagPathStr.string);
	} else {
	    result = tagPathStr.string;
	}
    } else {
	result = (char *) ckalloc(strlen(prefixTag) + 1);
	strcpy(result, prefixTag);
    }
	
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * PathVariables --
 *
 *  Creates environment variables from the system call FSpFindFolder.
 *  The function generates environment variables for many of the 
 *  commonly used paths on the Macintosh.
 *
 * Results:
 *	ptr to value on success, NULL if error.
 *
 * Side effects:
 *	Memory is allocated and returned to the caller.
 *
 *----------------------------------------------------------------------
 */

static char ** 
PathVariables()
{
    int i = 0;
    char **sysEnv;
    char *thePath = NULL;
    
    sysEnv = (char **) ckalloc((12) * sizeof(char *));

    sysEnv[i++] = MakeFolderEnvVar("PREF_FOLDER=", kPreferencesFolderType);
    sysEnv[i++] = MakeFolderEnvVar("SYS_FOLDER=", kSystemFolderType);
    sysEnv[i++] = MakeFolderEnvVar("TEMP=", kTemporaryFolderType);
    sysEnv[i++] = MakeFolderEnvVar("APPLE_M_FOLDER=", kAppleMenuFolderType);
    sysEnv[i++] = MakeFolderEnvVar("CP_FOLDER=", kControlPanelFolderType);
    sysEnv[i++] = MakeFolderEnvVar("DESK_FOLDER=", kDesktopFolderType);
    sysEnv[i++] = MakeFolderEnvVar("EXT_FOLDER=", kExtensionFolderType);
    sysEnv[i++] = MakeFolderEnvVar("PRINT_MON_FOLDER=",
	    kPrintMonitorDocsFolderType);
    sysEnv[i++] = MakeFolderEnvVar("SHARED_TRASH_FOLDER=",
	    kWhereToEmptyTrashFolderType);
    sysEnv[i++] = MakeFolderEnvVar("TRASH_FOLDER=", kTrashFolderType);
    sysEnv[i++] = MakeFolderEnvVar("START_UP_FOLDER=", kStartupFolderType);
    sysEnv[i++] = NULL;
	
    return sysEnv;
}

/*
 *----------------------------------------------------------------------
 *
 * SystemVariables --
 *
 *  Creates environment variables from various Mac system calls.
 *
 * Results:
 *	ptr to value on success, NULL if error.
 *
 * Side effects:
 *	Memory is allocated and returned to the caller.
 *
 *----------------------------------------------------------------------
 */

static char ** 
SystemVariables()
{
    int i = 0;
    char ** sysEnv;
    char * thePath = NULL;
    Handle theString = NULL;
    FSSpec currentDir;
    int size;
    
    sysEnv = (char **) ckalloc((4) * sizeof(char *));

    /*
     * Get user name from chooser.  It will be assigned to both
     * the USER and LOGIN environment variables.
     */
    thePath = GetUserName();
    if (thePath != NULL) {
	sysEnv[i] = (char *) ckalloc(strlen(kLoginnameTag) + strlen(thePath) + 1);
	strcpy(sysEnv[i], kLoginnameTag);
	strcpy(sysEnv[i]+strlen(kLoginnameTag), thePath);
	i++;
	sysEnv[i] = (char *) ckalloc(strlen(kUsernameTag) + strlen(thePath) + 1);
	strcpy(sysEnv[i], kUsernameTag);
	strcpy(sysEnv[i]+strlen(kUsernameTag), thePath);
	i++;
    }

    /*
     * Get 'home' directory
     */
#ifdef kDefaultDirTag
    FSpGetDefaultDir(&currentDir);
    FSpPathFromLocation(&currentDir, &size, &theString);
    HLock(theString);
    sysEnv[i] = (char *) ckalloc(strlen(kDefaultDirTag) + size + 4);
    strcpy(sysEnv[i], kDefaultDirTag);
    strncpy(sysEnv[i]+strlen(kDefaultDirTag) , *theString, size);
    if (sysEnv[i][strlen(kDefaultDirTag) + size - 1] != ':') {
	sysEnv[i][strlen(kDefaultDirTag) + size] = ':';
	sysEnv[i][strlen(kDefaultDirTag) + size + 1] = '\0';
    } else {
	sysEnv[i][strlen(kDefaultDirTag) + size] = '\0';
    }
    HUnlock(theString);
    DisposeHandle(theString);
    i++;
#endif

    sysEnv[i++] = NULL;
    return sysEnv;
}

/*
 *----------------------------------------------------------------------
 *
 * TclMacCreateEnv --
 *
 *	This function allocates and populates the global "environ"
 *	variable.  Entries are in traditional Unix format but variables
 *	are, hopefully, a bit more relevant for the Macintosh.
 *
 * Results:
 *	The number of elements in the newly created environ array.
 *
 * Side effects:
 *	Memory is allocated and pointed too by the environ variable.
 *
 *----------------------------------------------------------------------
 */

int
TclMacCreateEnv()
{
    char ** sysEnv = NULL;
    char ** pathEnv = NULL;
    char ** fileEnv = NULL;
    char ** rezEnv = NULL;
    int count = 0;
    int i, j;

    sysEnv = SystemVariables();
    if (sysEnv != NULL) {
	for (i = 0; sysEnv[i] != NULL; count++, i++) {
	    /* Empty Loop */
	}
    }

    pathEnv = PathVariables();
    if (pathEnv != NULL) {
	for (i = 0; pathEnv[i] != NULL; count++, i++) {
	    /* Empty Loop */
	}
    }

#ifdef kPrefsFile
    fileEnv = FileRCVariables();
    if (fileEnv != NULL) {
	for (i = 0; fileEnv[i] != NULL; count++, i++) {
	    /* Empty Loop */
	}
    }
#endif

#ifdef REZ_ENV
    rezEnv = RezRCVariables();
    if (rezEnv != NULL) {
	for (i = 0; rezEnv[i] != NULL; count++, i++) {
	    /* Empty Loop */
	}
    }
#endif

    /*
     * Create environ variable
     */
    environ = (char **) ckalloc((count + 1) * sizeof(char *));
    j = 0;
	
    if (sysEnv != NULL) {
	for (i = 0; sysEnv[i] != NULL;)
	    environ[j++] = sysEnv[i++];
	ckfree((char *) sysEnv);
    }

    if (pathEnv != NULL) {
	for (i = 0; pathEnv[i] != NULL;)
	    environ[j++] = pathEnv[i++];
	ckfree((char *) pathEnv);
    }

#ifdef kPrefsFile
    if (fileEnv != NULL) {
	for (i = 0; fileEnv[i] != NULL;)
	    environ[j++] = fileEnv[i++];
	ckfree((char *) fileEnv);
    }
#endif

#ifdef REZ_ENV
    if (rezEnv != NULL) {
	for (i = 0; rezEnv[i] != NULL;)
	    environ[j++] = rezEnv[i++];
	ckfree((char *) rezEnv);
    }
#endif

    environ[j] = NULL;
    return j;
}

/*
 *----------------------------------------------------------------------
 *
 * GetUserName --
 *
 *	Get the user login name.
 *
 * Results:
 *  ptr to static string, NULL if error.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char *
GetUserName()
{
    static char buf[33];
    short refnum;
    Handle h;
	
    refnum = CurResFile();
    UseResFile(0);
    h = GetResource('STR ', -16096);
    UseResFile(refnum);
    if (h == NULL) {
	return NULL;
    }
    
    HLock(h);
    strncpy(buf, (*h)+1, **h);
    buf[**h] = '\0';
    HUnlock(h);
    ReleaseResource(h);
    return(buf[0] ? buf : NULL);
}
