#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

#include "toba.h"
#include "runtime.h"

#include "java_lang_Win32Process.h"
#include "java_lang_String.h"

extern char **environ;

/*
 * We use the `handle' field of java.lang.Win32Process
 * to store the process status.  If the process is uninitialized
 * the value is zero.  If the process is alive (or not yet
 * waited for) the value is the process id.  After the
 * process has been reaped the handle is set to the negative
 * value of the process return status.
 */

/* create a Process object appropriate for Win32 Systems */
struct in_java_lang_Process *
create_process(Object args, Object env)
{
    struct in_java_lang_Process *process;

    process = (struct in_java_lang_Process *)
                 new(&cl_java_lang_Win32Process.C);

    /* Win32Process.init(String[],String[]) */
    init_aSaS_BtCI4(process, args, env);

    return process;
}


/* java/lang/Win32Process/exitValue ()I */
Int
exitValue__hCREY(Object Harg1) 
{
    struct in_java_lang_Win32Process *this =
        (struct in_java_lang_Win32Process *)Harg1;

    /* if it hasn't exited yet, return zero */
    if(this->handle >= 0)
        return 0;

    return (- this->handle);
}

/* java/lang/Win32Process/waitFor ()I */
Int
waitFor__EPkVE(Object Harg1) 
{
    struct in_java_lang_Win32Process *this =
        (struct in_java_lang_Win32Process *)Harg1;
    int status;

    /* if it hasn't yet exited, wait for it */
    if(this->handle > 0) {
        if(waitpid(this->handle, &status, 0) == -1)
            throwInterruptedIOException("wait4", errno);
        this->handle = - WEXITSTATUS(status);
    }
    return (- this->handle);
}

/* java/lang/Win32Process/destroy ()V */
Void
destroy__HniP7(Object Harg1) 
{
    struct in_java_lang_Win32Process *this =
        (struct in_java_lang_Win32Process *)Harg1;

    if(this->handle > 0) {
        kill(this->handle, SIGTERM);
        waitFor__EPkVE(this);
    }
}

/* java/lang/Win32Process/close ()V */
Void
close__Px4wg(Object Harg1) 
{
    struct in_java_lang_Win32Process *this =
        (struct in_java_lang_Win32Process *)Harg1;
    struct in_java_io_FileDescriptor *infd, *outfd, *errfd;

    infd = (struct in_java_io_FileDescriptor *)this->stdin_fd;
    outfd = (struct in_java_io_FileDescriptor *)this->stdout_fd;
    errfd = (struct in_java_io_FileDescriptor *)this->stderr_fd;
    close(unix_fd(infd->fd));
    close(unix_fd(outfd->fd));
    close(unix_fd(errfd->fd));
    infd->fd = 0;
    outfd->fd = 0;
    errfd->fd = 0;

    destroy__HniP7(this);
}

/* split a string int a null terminated array */
static char **
build_array(char *str, char sep, int len) 
{
    char **arr, *last;
    int count, i;

    count = 0;
    for(i = 0; i < len; i++) {
        if(str[i] == sep)
            count++;
    }

    arr = (char **)allocate((count + 2) * sizeof(char *));
    count = 0;
    last = &str[0];
    for(i = 0; i < len; i++) {
        if(str[i] == sep) {
            str[i] = '\0';
            arr[count++] = last;
            last = &str[i + 1];
        }
    }
    if(str[len - 1] != sep)
        arr[count++] = last;
    arr[count] = 0;

    return arr;
}

/* java/lang/Win32Process/create (Ljava/lang/String;Ljava/lang/String)V */
Void
create_SS_lHY3f(Object Harg1, Object Harg2, Object Harg3) 
{
    struct in_java_lang_Win32Process *this = 
        (struct in_java_lang_Win32Process *)Harg1;
    struct in_java_lang_String *cmdline =
        (struct in_java_lang_String *)Harg2;
    struct in_java_lang_String *env =
        (struct in_java_lang_String *)Harg3;
    struct in_java_io_FileDescriptor *infd, *outfd, *errfd;
    int inpipe[2], outpipe[2], errpipe[2];
    int len, pid, old_errno;
    char *mesg, *cmdstr, **argv, **envp;
    char *path, *envstr;
    int envlen;
    
    /* these should all be non-null */
    infd = (struct in_java_io_FileDescriptor *)this->stdin_fd;
    outfd = (struct in_java_io_FileDescriptor *)this->stdout_fd;
    errfd = (struct in_java_io_FileDescriptor *)this->stderr_fd;
    if(!infd || !outfd || !errfd || !cmdline)
        throwNullPointerException("process create");

    cmdstr = cstring(cmdline); 
    argv = build_array(cmdstr, ' ', strlen(cmdstr));

    if(env) {
        /* user supplied environment */
        envstr = cstring(env);
        envlen = env->count;
        envp = build_array(envstr, '\0', envlen);

        /*
         * we need to pass on the PATH since without the
         * cygwin.dll  nothing will run - this is somewhat
         * of a hack,  if a user provides a PATH it will
         * be ignored.
         */
        path = 0;
        for(len = 0; environ[len]; len++) {
            if((path == 0) && (strncmp("PATH=", environ[len], 5) == 0))
                path = environ[len];
        }
        if(path) {
            char **newenvp;
            int i;

            newenvp = (char **)allocate(sizeof(char *) * (len + 2));
            newenvp[0] = path;
            for(i = 0; envp[i]; i++) {
                newenvp[i + 1] = envp[i];
            }
            newenvp[i] = 0;
            envp = newenvp;
        }
    } else {
        /* no environment given, use parents environment */
        envp = environ;
    }

    /* open pipes then do a fork */
    inpipe[0] = -1; inpipe[1] = -1;
    outpipe[0] = -1; outpipe[1] = -1;
    errpipe[0] = -1; errpipe[1] = -1;
    if(pipe(inpipe) == -1 || pipe(outpipe) == -1 || pipe(errpipe) == -1) {
        mesg = "pipe";
        goto err;
    }

    pid = fork();
    switch(pid) {
    case -1:
        mesg = "fork";
        goto err;

    case 0: /* child */
        close(inpipe[1]);
        close(outpipe[0]);
        close(errpipe[0]);
        dup2(inpipe[0], 0);		/* our input */
        dup2(outpipe[1], 1);		/* out output */
        dup2(errpipe[1], 2);		/* our error output */

        /* we could be nice and close fds > 2 here */

        execve(argv[0], argv, envp);	/* should not return */

        /* send error information on stderr */
        /* (we can't throw an exception; we already forked) */
        perror(argv[0]);
        exit(1);

    default: /* parent */
        this->handle = pid;
        close(inpipe[0]);
        close(outpipe[1]);
        close(errpipe[1]);
        infd->fd = java_fd(inpipe[1]);		/* write -> childs input */
        outfd->fd = java_fd(outpipe[0]);	/* read <- childs output */
        errfd->fd = java_fd(errpipe[0]);	/* read <- childs err */
        break;
    }
    return;

err:
    old_errno = errno;
    close(inpipe[0]);
    close(inpipe[1]);
    close(outpipe[0]);
    close(outpipe[1]);
    close(errpipe[0]);
    close(errpipe[1]);
    throwIOException(mesg, old_errno);
    /*NOTREACHED*/
}


