#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <process.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>

#define SPAWN_EXEC "tclspawn.exe"

static char *FindExec(char *);
static int Directory(char *);

static char *argv0_dir;
/*
 *----------------------------------------------------------------------
 * Function: forkexec:
 * Arguments:
 *	execName: The executable the launcher should spawn.
 *	argv:     Arglist for the executable
 *	fdstdin:  file descriptor that will become stdin in the child
 *	fdstout:  file descriptor that will become stdout in the child
 *	fdsterr:  file descriptor that will become stderr in the child
 *
 * Purpose:
 *	Try to simulate Unix as much as possible.  Because there is no
 *	fork command in NT, we first spawn a launcher process which
 *	spawns the child we really want to deal with.  The launcher closes
 *	all open files except for the three for stdin, stdout, and stderr.
 *	If the file descriptors for these three are not on 0, 1, and 2,
 *	it dups them there and then forks the child.
 *
 * Problems:
 *	We don't check for the spawner process where the wish program
 *	got started from.  We need to do this if the spawn fails.
 *----------------------------------------------------------------------
 */
int
forkexec(char *execName, char **argv,
	 int fdstdin, int fdstdout, int fdstderr,
	 int joinThisError)
{
    int pid;
    char stdin_str[20], stdout_str[20], stderr_str[20];
    int argc, i;
    char **newargv;
    char *newargv0;

    itoa(fdstdin, stdin_str, 10);
    itoa(fdstdout, stdout_str, 10);
    if (joinThisError) {
	itoa(fdstdout, stderr_str, 10);
    } else {
	itoa(fdstderr, stderr_str, 10);
    }

    for (argc = 0; argv[argc] != NULL; argc++);
    newargv = (char **) malloc((argc + 6) * sizeof(char *));

    newargv0 = (char *) malloc(strlen(SPAWN_EXEC) + strlen(argv0_dir) + 1);
    strcpy(newargv0, argv0_dir);
    strcat(newargv0, SPAWN_EXEC);

    newargv[0] = FindExec(newargv0);

    newargv[1] = stdin_str;
    newargv[2] = stdout_str;
    newargv[3] = stderr_str;
    newargv[4] = execName;
    for (i = 0; i < argc; i++) {
	newargv[5+i] = argv[i];
    }
    newargv[5+i] = NULL;

    pid = spawnv(P_NOWAIT, newargv[0], newargv);
    free(newargv0);
    if (pid == -1) {
	newargv[0] = FindExec(SPAWN_EXEC);
	pid = spawnv(P_NOWAIT, newargv[0], newargv);
    }
	
    return pid;
}

void
forkexec_setargv0_dir(char *largv0_dir)
{
    argv0_dir = largv0_dir;
}

static char *
FindExec(char *command)
{
    /* Run through the PATH environment variable to find the executable */
    char *path, *bp, *start, *dir;
    char save;
    int len, need, cmdlen, dirlen;
    int have = 0;
    static char *exec = NULL;

#define X_OK 0

    len = strlen(command);
    if (len > 2) {
	if (command[1] == ':' ||
	    strchr(command, '/') != NULL || strchr(command, '\\') != NULL)
	{
	    return command;
	}
    }

    path = getenv("PATH");
    if (path == NULL) {
	path = getenv("Path");
	if (path == NULL) {
	    return command;
	}
    }

    path = strdup(path);
    if (path == NULL) {
	return command;
    }

    for (start = path, bp = start; *start != '\0'; start = bp) {
	while(*bp != '\0' && *bp != ';') {
	    bp++;
	}
	save = *bp;
	*bp = '\0';
	dirlen = strlen(start);
	if (dirlen == 0) {
	    dir = "./";
	    dirlen = 2;
	} else {
	    dir = start;
	}
	cmdlen = strlen(command);
	need = cmdlen + dirlen + 6;
	if (need > have) {
	    if (exec) {
		free(exec);
	    }
	    exec = (char *) malloc(need);
	    if (exec == NULL) {
		return command;
	    }
	    have = need;
	}
	strcpy(exec, dir);
	if (dir[dirlen-1] != '/' && dir[dirlen-1] != '\\') {
	    exec[dirlen] = '/';
	    dirlen++;
	}
	strcpy(&exec[dirlen], command);
	if (access(exec, X_OK) == 0) {
	    if (! Directory(exec)) {
		return exec;
	    }
	}
	len = dirlen + cmdlen;
	strcpy(&exec[len], ".exe");
	if (access(exec, X_OK) == 0) {
	    if (! Directory(exec)) {
		return exec;
	    }
	}
	strcpy(&exec[len], ".bat");
	if (access(exec, X_OK) == 0) {
	    if (! Directory(exec)) {
		return exec;
	    }
	}
	*bp = save;
	bp++;
    }
    return command;
}

int
Directory(char *path)
{
    struct stat statBuf;

    if (stat(path, &statBuf) == -1) {
	return 0;
    }
    if (_S_IFDIR & statBuf.st_mode) {
	return 1;
    }
    return 0;
}
