/*
 *  R : A Computer Language for Statistical Data Analysis
 *  file run.c: a simple 'reading' pipe (and a command executor)
 *  Copyright (C) 1999-2001  Guido Masarotto  and Brian Ripley
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "run.h"

static char RunError[256] = "";


static char * expandcmd(char *cmd)
{
    char  c;
    char *s, *p, *q, *f, *dest, *src;
    char  fl[MAX_PATH], fn[MAX_PATH];
    int   d , ext;

    if (!(s = (char *) malloc(MAX_PATH + strlen(cmd)))) {
	strcpy(RunError, "Insufficient memory (expandcmd)");
	return NULL;
    }
    for (p = cmd; *p && isspace(*p); p++);
    for (q = p, d = 0; *q && ( d || !isspace(*q) ); q++)
      if (*q == '\"') d = d ? 0 : 1;
    if (d) {
	strcpy(RunError, "A \" is missing(expandcmd)");
	return NULL;
    }
    c = *q;
    *q = '\0';

/*
 * I resort to this since SearchPath returned FOUND also
 * for file name without extension -> explicitly set
 *  extension
*/
   for ( f = p, ext = 0 ; *f ; f++) {
     if ((*f == '\\') || (*f == '/')) ext = 0;
     else if (*f == '.') ext = 1;
   }
   /* SearchFile doesn't like \" */
   for ( dest = fl , src = p; *src ; src++)
       if (*src != '\"') *dest++ = *src;
   *dest = '\0';
   if (ext) {
   /*
    * user set extension; we don't check that it is executable;
    * it might get an error after; but maybe sometimes
    * in the future every extension will be executable
   */
      d = SearchPath(NULL, fl, NULL, MAX_PATH, fn, &f);
   } else {
     int iexts = 0;
     char *exts[] = { ".exe" , ".com" , ".cmd" , ".bat" , NULL };
     while (exts[iexts]) {
       strcpy(dest, exts[iexts]);
       if ((d = SearchPath(NULL, fl, NULL, MAX_PATH, fn, &f))) break;
       iexts ++ ;
     }
   }
   if (!d) {
       free(s);
       strncpy(RunError, p, 200);
       strcat(RunError, " not found");
       *q = c;
       return NULL;
   }
   /*
      Paranoia : on my system switching to short names is not needed
      since SearchPath already returns 'short names'. However,
      this is not documented so I prefer to be explicit.
      Problem is that we have removed \" from the executable since
      SearchPath seems dislikes them
   */
   GetShortPathName(fn, s, MAX_PATH);
   *q = c;
   strcat(s, q);
   return s;
}

/*
   finput is either NULL or the name of a file from which to
     redirect stdin for the child.
   newconsole != 0 to use a new console (if not waiting)
   visible = -1, 0, 1 for hide, minimized, default
   inpipe != 0 to duplicate I/O handles
*/

static HANDLE pcreate(char* cmd, char *finput,
		      int newconsole, int visible, int inpipe)
{
    DWORD ret;
    SECURITY_ATTRIBUTES sa;
    PROCESS_INFORMATION pi;
    STARTUPINFO si;
    HANDLE hIN = INVALID_HANDLE_VALUE,
	hSAVED = INVALID_HANDLE_VALUE, hTHIS;
    char *ecmd;

    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    if (!(ecmd = expandcmd(cmd)))
	return NULL;
    hTHIS = GetCurrentProcess();
    if (finput && finput[0]) {
	hSAVED = GetStdHandle(STD_INPUT_HANDLE) ;
	hIN = CreateFile(finput, GENERIC_READ, 0,
			 &sa, OPEN_EXISTING, 0, NULL);
	if (hIN == INVALID_HANDLE_VALUE) {
	    free(ecmd);
	    strcpy(RunError, "Impossible to redirect input");
	    return NULL;
	}
	SetStdHandle(STD_INPUT_HANDLE, hIN);
    }
    si.cb = sizeof(si);
    si.lpReserved = NULL;
    si.lpReserved2 = NULL;
    si.cbReserved2 = 0;
    si.lpDesktop = NULL;
    si.lpTitle = NULL;
    if ((finput && finput[0]) || inpipe) {
	si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
	DuplicateHandle(hTHIS, GetStdHandle(STD_INPUT_HANDLE),
		      hTHIS, &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
	DuplicateHandle(hTHIS, GetStdHandle(STD_OUTPUT_HANDLE),
		     hTHIS, &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
	DuplicateHandle(hTHIS, GetStdHandle(STD_ERROR_HANDLE),
		      hTHIS, &si.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS);
    } else
	si.dwFlags = STARTF_USESHOWWINDOW;
    switch (visible) {
      case -1:
	si.wShowWindow = SW_HIDE;
	break;
      case 0:
	si.wShowWindow = SW_SHOWMINIMIZED;
	break;
      case 1:
	si.wShowWindow = SW_SHOWDEFAULT;
	break;
    }
    ret = CreateProcess(0, (char *) ecmd, &sa, &sa, TRUE,
		    (newconsole && (visible == 1)) ? CREATE_NEW_CONSOLE : 0,
			NULL, NULL, &si, &pi);
    CloseHandle(hTHIS);
    if (finput && finput[0]) {
	SetStdHandle(STD_INPUT_HANDLE, hSAVED);
	CloseHandle(hIN);
    }
    if (si.dwFlags & STARTF_USESTDHANDLES) {
	CloseHandle(si.hStdInput);
	CloseHandle(si.hStdOutput);
	CloseHandle(si.hStdError);
    }
    if (!ret) {
	strcpy(RunError, "Impossible to run '");
	strncat(RunError, ecmd, 200);
	free(ecmd);
	return NULL;
    }
    free(ecmd);
    CloseHandle(pi.hThread);
    return pi.hProcess;
}

static int pwait(HANDLE p)
{
    DWORD ret;

    WaitForSingleObject(p, INFINITE);
    GetExitCodeProcess(p, &ret);
    return ret;
}

/* used in rpipeOpen */
static DWORD CALLBACK
threadedwait(LPVOID param)
{
    rpipe *p = (rpipe *) param;

    p->exitcode = pwait(p->process);
    FlushFileBuffers(p->write);
    FlushFileBuffers(p->read);
    p->active = 0;
    return 0;
}

char *runerror()
{
    return RunError;
}


/*
   wait != 0 says wait for child to terminate before returning.
   visible = -1, 0, 1 for hide, minimized, default
   finput is either NULL or the name of a file from which to
     redirect stdin for the child.
 */
int runcmd(char *cmd, int wait, int visible, char *finput)
{
    HANDLE p;
    int ret;

/* I hope no program will use this as an error code */
    if (!(p = pcreate(cmd, finput, !wait, visible, 0))) return NOLAUNCH;
    if (wait) ret = pwait(p);
    else ret = 0;
    CloseHandle(p);
    return ret;
}

/*
   finput is either NULL or the name of a file from which to
     redirect stdin for the child.
   newconsole != 0 to use a new console (if not waiting)
   visible = -1, 0, 1 for hide, minimized, default
   io = 0 to read from pipe, 1 to write to pipe.
 */
rpipe * rpipeOpen(char *cmd, int visible, char *finput, int io)
{
    rpipe *r;
    HANDLE hIN, hOUT, hERR, hThread, hTHIS, hTemp;
    DWORD id;
    BOOL res;

    if (!(r = (rpipe *) malloc(sizeof(struct structRPIPE)))) {
	strcpy(RunError, "Insufficient memory (rpipeOpen)");
	return NULL;
    }
    r->process = NULL;
    if(io) { /* pipe to write to */
	res = CreatePipe(&(r->read), &hTemp, NULL, 0);
	if (res == FALSE) {
	    rpipeClose(r);
	    strcpy(RunError, "Impossible to create pipe");
	    return NULL;
	}
	hTHIS = GetCurrentProcess();
	hIN = GetStdHandle(STD_INPUT_HANDLE);
	DuplicateHandle(hTHIS, hTemp, hTHIS, &r->write,
			0, FALSE, DUPLICATE_SAME_ACCESS);
	CloseHandle(hTemp);
	CloseHandle(hTHIS);
	SetStdHandle(STD_INPUT_HANDLE, r->read);
	r->process = pcreate(cmd, NULL, 1, visible, 1);
	r->active = 1;
	SetStdHandle(STD_INPUT_HANDLE, hIN);
	if (!r->process) return NULL; else return r;
    }
    res = CreatePipe(&hTemp, &(r->write), NULL, 0);
    if (res == FALSE) {
	rpipeClose(r);
	strcpy(RunError, "Impossible to create pipe");
	return NULL;
    }
    hTHIS = GetCurrentProcess();
    hOUT = GetStdHandle(STD_OUTPUT_HANDLE) ;
    hERR = GetStdHandle(STD_ERROR_HANDLE) ;
    DuplicateHandle(hTHIS, hTemp, hTHIS, &r->read,
		    0, FALSE, DUPLICATE_SAME_ACCESS);
    CloseHandle(hTemp);
    CloseHandle(hTHIS);
    SetStdHandle(STD_OUTPUT_HANDLE, r->write);
    SetStdHandle(STD_ERROR_HANDLE, r->write);
    r->process = pcreate(cmd, finput, 0, visible, 1);
    r->active = 1;
    SetStdHandle(STD_OUTPUT_HANDLE, hOUT);
    SetStdHandle(STD_ERROR_HANDLE, hERR);
    if (!r->process)
	return NULL;
    if (!(hThread = CreateThread(NULL, 0, threadedwait, r, 0, &id))) {
	rpipeClose(r);
	strcpy(RunError, "Impossible to create thread/pipe");
	return NULL;
    }
    CloseHandle(hThread);
    return r;
}


int
rpipeGetc(rpipe * r)
{
    DWORD a, b;
    char  c;

    if (!r)
	return NOLAUNCH;
    while (PeekNamedPipe(r->read, NULL, 0, NULL, &a, NULL)) {
	if (!a && !r->active) {
	    /* I got a case in which process terminated after Peek.. */
	    PeekNamedPipe(r->read, NULL, 0, NULL, &a, NULL);
	    if (!a) return NOLAUNCH;/* end of pipe */
	}
	if (a) {
	    if (ReadFile(r->read, &c, 1, &b, NULL) == TRUE)
		return c;
	    else
		return NOLAUNCH;/* error but...treated as eof */
	}
    }
    return NOLAUNCH;		/* again.. */
}


char * rpipeGets(rpipe * r, char *buf, int len)
{
    int   i, c;

    if ((len < 2) || !r) return NULL;
    for (i = 0; i < (len - 1); i++) {
	if ((c = rpipeGetc(r)) == NOLAUNCH) {
	    if (i == 0) return NULL;
	    else {
		buf[i] = '\0';
		return buf;
	    }
	}
	buf[i] = c;
	if (c == '\n') {
	    if ((i > 0) && (buf[i - 1] == '\r')) {
		buf[i - 1] = '\n';
		buf[i] = '\0';
	    } else
		buf[i + 1] = '\0';
	    return buf;
	}
    }
    buf[len - 1] = '\0';
    return buf;
}

int rpipeClose(rpipe * r)
{
    int   i;

    if (!r) return NOLAUNCH;
    if (r->active) TerminateProcess(r->process, 99);
    CloseHandle(r->read);
    CloseHandle(r->write);
    CloseHandle(r->process);
    i = r->exitcode;
    free(r);
    return i;
}

/* ------------------- Windows pipe connections --------------------- */

#include <Defn.h>
#include <Fileio.h>
#include <Rconnections.h>

typedef struct Wpipeconn {
    rpipe *rp;
} *RWpipeconn;


static Rboolean Wpipe_open(Rconnection con)
{
    rpipe *rp;
    int visible = -1, io;

    io = con->mode[0] == 'w';
    if(io) visible = 1; /* Somewhere to put the output */
    rp = rpipeOpen(con->description, visible, NULL, io);
    if(!rp) {
	warning("cannot open cmd `%s'", con->description);
	return FALSE;
    }
    ((RWpipeconn)(con->private))->rp = rp;
    con->isopen = TRUE;
    con->canwrite = io;
    con->canread = !con->canwrite;
    if(strlen(con->mode) >= 2 && con->mode[1] == 'b') con->text = FALSE;
    else con->text = TRUE;
    con->save = -1000;
    return TRUE;
}

static void Wpipe_close(Rconnection con)
{
    rpipeClose( ((RWpipeconn)con->private) ->rp);
    con->isopen = FALSE;
}

static void Wpipe_destroy(Rconnection con)
{
    free(con->private);
}


static int Wpipe_fgetc(Rconnection con)
{
    rpipe *rp = ((RWpipeconn)con->private) ->rp;
    int c;

    c = rpipeGetc(rp);
    return c == NOLAUNCH ? R_EOF : c;
}


static long null_seek(Rconnection con, int where, int origin, int rw)
{
    error("seek not enabled for this connection");
    return 0; /* -Wall */
}

static void null_truncate(Rconnection con)
{
    error("truncate not enabled for this connection");
}

static int Wpipe_fflush(Rconnection con)
{
    BOOL res;

    rpipe *rp = ((RWpipeconn)con->private) ->rp;
    res = FlushFileBuffers(rp->write);
    return res ? 0 : EOF;
}

static size_t Wpipe_read(void *ptr, size_t size, size_t nitems,
			Rconnection con)
{
    rpipe *rp = ((RWpipeconn)con->private) ->rp;
    DWORD ntoread, read;

    while (PeekNamedPipe(rp->read, NULL, 0, NULL, &ntoread, NULL)) {
	if (!ntoread && !rp->active) {
	    /* I got a case in which process terminated after Peek.. */
	    PeekNamedPipe(rp->read, NULL, 0, NULL, &ntoread, NULL);
	    if (!ntoread) return 0; /* end of pipe */
	}
	if (ntoread) {
	    if (ReadFile(rp->read, ptr, nitems * size, &read, NULL) == TRUE)
		return read/size;
	    else return 0; /* error */
	}
    }
    return 0;
}

static size_t Wpipe_write(const void *ptr, size_t size, size_t nitems,
			 Rconnection con)
{
    rpipe *rp = ((RWpipeconn)con->private) ->rp;
    DWORD towrite = nitems * size, write, ret;

    if(!rp->active) return 0;
    GetExitCodeProcess(rp->process, &ret);
    if(ret != STILL_ACTIVE) {
	rp->active = 0;
	warning("broken Windows pipe");
	return 0;
    }
    if (WriteFile(rp->write, ptr, towrite, &write, NULL) != 0)
	return write/size;
    else return 0;
}

#define BUFSIZE 1000
static int Wpipe_vfprintf(Rconnection con, const char *format, va_list ap)
{
    char buf[BUFSIZE], *b = buf, *vmax = vmaxget();
    int res = 0, usedRalloc = FALSE;

    res = vsnprintf(b, BUFSIZE, format, ap);
    if(res < 0) { /* a failure indication, so try again */
	usedRalloc = TRUE;
	b = R_alloc(10*BUFSIZE, sizeof(char));
	res = vsnprintf(b, 10*BUFSIZE, format, ap);
	if (res < 0) {
	    *(b + 10*BUFSIZE) = '\0';
	    warning("printing of extremely long output is truncated");
	    res = 10*BUFSIZE;
	}
    }
    res = Wpipe_write(buf, res, 1, con);
    if(usedRalloc) vmaxset(vmax);
    return res;
}

Rconnection newWpipe(char *description, char *mode)
{
    Rconnection new;
    new = (Rconnection) malloc(sizeof(struct Rconn));
    if(!new) error("allocation of pipe connection failed");
    new->class = (char *) malloc(strlen("pipe") + 1);
    if(!new->class) {
	free(new);
	error("allocation of pipe connection failed");
    }
    strcpy(new->class, "pipe");
    new->description = (char *) malloc(strlen(description) + 1);
    if(!new->description) {
	free(new->class); free(new);
	error("allocation of pipe connection failed");
    }
    init_con(new, description, mode);
    new->open = &Wpipe_open;
    new->close = &Wpipe_close;
    new->destroy = &Wpipe_destroy;
    new->vfprintf = &Wpipe_vfprintf;
    new->fgetc = &Wpipe_fgetc;
    new->seek = &null_seek;
    new->truncate = &null_truncate;
    new->fflush = &Wpipe_fflush;
    new->read = &Wpipe_read;
    new->write = &Wpipe_write;
    new->private = (void *) malloc(sizeof(struct Wpipeconn));
    if(!new->private) {
	free(new->description); free(new->class); free(new);
	error("allocation of pipe connection failed");
    }
    return new;
}
