/* Copyright Steve Donovan.  MIT license; see COPYING
 * While this was originally released under a different open source 
 * license, Steve licensed it as MIT here:
 *      https://marc.info/?l=lua-l&m=164087898814318&w=2
 *      https://archive.md/Zy1U1
 *      https://web.archive.org/web/20211230201231/https://marc.info/?l=lua-l&m=164087898814318&w=2
 * Changed by Sam Trenholme for Lunacy
 */

/* Implementation of popen2 for Lua
 * This is the only one I found which
 * 1) Works (LuaEx *does not* work)
 * 2) Works in Win32 (lpc *does not* have Win32 support)
 * 3) Has a known license (popen *does not* have a license)
 *
 * Usage example:

w, r = spawner.popen2("wc")
w:write("Hello, world!\n")
w:flush()
w:close()
print(r:read())

 * This file is adapted from 
 * http://mysite.mweb.co.za/residents/sdonovan/lua/spawner-ex.zip
 * That site is no longer online, but the file has been archived:
 * https://web.archive.org/web/20210217232306/http://mysite.mweb.co.za/residents/sdonovan/lua/spawner-ex.zip
 * I also have a mirror of the original file here:
 * https://maradns.samiam.org/lunacy/spawner-ex.zip
 */
#ifndef MINGW
// unix-spawner-ex.c
/* An extension for SciTE Lua that provides process management for SciTE scripts.
*  This extension provides a table 'spawner' which contains some very useful
*  functions, popen() and popen2(), for spawning processes and capturing their
*  input. (io.popen() is broken on Windows, and has serious problems on Unix)
*  It provides a spawner object which can be used to capture interactive processes
* Steve Donovan, 2007. 
*/
#include <pty.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <glob.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

/* #include <gdk/gdk.h> */

static int verbose = 0;

static char *quote_strtok(char *str, char str_delim)
{
// a specialized version of strtok() which treats quoted strings specially 
// (used for handling command-line parms)
    static unsigned char *tok;
    if(str != NULL) tok = (unsigned char *)str;
          
    while (*tok && isspace(*tok)) tok++;
    if (*tok == '\0') return NULL;
    
    if (*tok == str_delim) {       
       tok++;            // skip "
       str = (char *)tok;
       while (*tok && *tok != str_delim) tok++;        
    } else {
       str = (char *)tok;
       while (*tok && ! isspace(*tok)) tok++;
    }
    if (*tok) *tok++ = '\0';  
    return str;
}

#define READ 0
#define WRITE 1

pid_t
popen2(const char *command, int *infp, int *outfp)
{
    int p_stdin[2], p_stdout[2];
    pid_t pid;

    if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0)
        return -1;

    pid = fork();

    if (pid < 0)
        return pid;
    else if (pid == 0)
    {
        close(p_stdin[WRITE]);
        dup2(p_stdin[READ], READ);
        close(p_stdout[READ]);
        dup2(p_stdout[WRITE], WRITE);

        execl("/bin/sh", "sh", "-c", command, NULL);
        perror("execl");
        exit(1);
    }

    // note that it is NB to close the other ends of the pipes!
    if (infp == NULL)
        close(p_stdin[WRITE]);
    else {
        close(p_stdin[READ]);
        *infp = p_stdin[WRITE];
    }

    if (outfp == NULL)
        close(p_stdout[READ]);
    else {
        close(p_stdout[WRITE]);
        *outfp = p_stdout[READ];
    }

    return pid;
}


void msleep(long millisec)
{
    struct timespec tspec;
    tspec.tv_sec = 0;
    tspec.tv_nsec = 1000*1000*millisec;
    nanosleep(&tspec,NULL);
}

typedef struct {
	const char* command_line;	
	const char* output;
	const char* result;
    int pid;
    FILE* child;
    FILE* rdr;
    lua_State* L;
    int child_fd;
    /* gint gdk_handle; */

} Spawner;

int start_process(Spawner* p)
{
    char* argline = strdup(p->command_line);
    char* args[30];
    int i = 0;
    char *arg;
    if (verbose) fprintf(stderr,"'%s'\n",argline);
    arg = quote_strtok(argline,'"');
    if (arg == NULL) return 0;
    while (arg != NULL) {
        args[i++] = arg;
        if (verbose) fprintf(stderr,"-%s\n",arg);
        arg = quote_strtok(NULL,'"');
    }
    args[i] = NULL;
    p->pid = forkpty(&p->child_fd,NULL,NULL,NULL);
    if (p->pid == 0) { // child
        execvp(args[0],args);
        // if we get here, it's an error!
        perror("'unable to spawn process");
        exit(-1);
    } else {
        p->rdr = fdopen(p->child_fd,"r");        
        msleep(200);
        if (verbose) fprintf(stderr,"'listening\n");
        p->child = fdopen(p->child_fd,"w");
    }  
    return 1;
}

static char line[512];

void lua_invoke(lua_State* L, const char* fun, const char* arg)
{
    int res;
    lua_getglobal(L,fun);
    lua_pushstring(L,arg);
    res = lua_pcall(L,1,.0,0);   
    if (res == LUA_ERRRUN) {
        lua_getglobal(L, "print");
        lua_insert(L, -2); // use pushed error message
        lua_pcall(L, 1, 0, 0);
	}    
}

void read_callback(Spawner* p)
{
    int nr = read(p->child_fd,line,sizeof(line));
    if (nr > 0) {
        line[nr] = '\0';
        if (verbose) fprintf(stderr,"'%s'\n",line);
        lua_invoke(p->L,p->output,line);
    } else {        
        /*
        gdk_input_remove(p->gdk_handle);
        if (p->result != NULL) {
            char buff[15];
            int res = 0;
            wait(&res);
            sprintf(buff,"%d",res);
            lua_invoke(p->L,p->result,buff);
        }  
	*/
    }
}

static int do_verbose(lua_State* L)
{
    verbose = lua_toboolean(L,1);
    return 0;
}

// not implemented yet!
static int do_foreground(lua_State* L)
{
    return 0;
}

static int new_spawner(lua_State* L)
{
	Spawner* spp = (Spawner*)lua_newuserdata(L,sizeof(Spawner));
	memset(spp,0,sizeof(Spawner));
	spp->command_line = luaL_checkstring(L,1);
    spp->L = L;
    luaL_getmetatable(L,"SPAWNER");
    lua_setmetatable(L,-2);
	return 1;
}

static int spawner_set_output(lua_State* L)
{
	Spawner* spp = (Spawner*)lua_touserdata(L,1);
	spp->output = luaL_checkstring(L,2);
	return 0;
}

static int spawner_set_result(lua_State* L)
{
	Spawner* spp = (Spawner*)lua_touserdata(L,1);
	spp->result = luaL_checkstring(L,2);
	return 0;
}

static int spawner_use_shell(lua_State* L)
{
	Spawner* spp = (Spawner*)lua_touserdata(L,1);
	char* new_command;
	new_command = (char*)malloc(strlen(spp->command_line)+20);
	sprintf(new_command,"/bin/sh -c %s",spp->command_line);
	spp->command_line = new_command;
	return 0;
}

static int spawner_write(lua_State* L)
{
	Spawner* spp = (Spawner*)lua_touserdata(L,1);	
	const char* buff = luaL_checkstring(L,2);
    int res = fputs(buff,spp->child);
	lua_pushboolean(L,res > 0);
	return 1;
}

static int spawner_read(lua_State* L)
{
	Spawner* spp = (Spawner*)lua_touserdata(L,1);
    char* res = fgets(line,sizeof(line),spp->rdr);
    if (! res) {
        lua_pushnil(L);
    } else {
        lua_pushstring(L,res);
    }
	return 1;
}

void newfile_from_stream (lua_State *L, FILE *f);

static int spawner_run(lua_State* L)
{
	Spawner* spp = (Spawner*)lua_touserdata(L,1);
	if (! start_process(spp)) {
		lua_pushboolean(L,0);
		return 1;
	}
	if (spp->output != NULL) {
	/*
        spp->gdk_handle = gdk_input_add(spp->child_fd, GDK_INPUT_READ,
		                            (GdkInputFunction) read_callback, spp);
	*/
	}
	lua_pushboolean(L,1);
	return 1;
}

static int do_popen(lua_State* L)
{
    const char* cmd = lua_tostring(L,1);
    int inf,outf;
    int res = popen2(cmd,&inf,&outf);
    close(inf); 
    if (res == -1) {
        lua_pushboolean(L,0);
    } else {
        newfile_from_stream(L,fdopen(outf,"r"));
    }
	return 1;
}

static int do_popen2(lua_State* L)
{
    const char* cmd = lua_tostring(L,1);
    int inf,outf;
    int res = popen2(cmd,&inf,&outf);
    if (res == -1) {
        lua_pushboolean(L,0);
        return 1;
    } else {
        newfile_from_stream(L,fdopen(inf,"w"));
        newfile_from_stream(L,fdopen(outf,"r"));
        return 2;
    }
}

static int spawner_kill(lua_State* L)
{
	Spawner* spp = (Spawner*)lua_touserdata(L,1);
    kill(spp->pid,9);
	return 0;
}

/////// stuff from liolib

#define PIPE_FILEHANDLE "PIPE_FILE*"

// based on liolib.c
void newfile_from_stream (lua_State *L, FILE *f)
{
  FILE **pf = (FILE **)lua_newuserdata(L, sizeof(FILE *));
  *pf = NULL;
  luaL_getmetatable(L, PIPE_FILEHANDLE);
  lua_setmetatable(L, -2);
  *pf = f;
  // leaves the userdata on the lua stack!
}

static int pushresult (lua_State *L, int i, const char *filename) {
  int en = errno;  /* calls to Lua API may change this value */
  if (i) {
    lua_pushboolean(L, 1);
    return 1;
  }
  else {
    lua_pushnil(L);
    if (filename)
      lua_pushfstring(L, "%s: %s", filename, strerror(en));
    else
      lua_pushfstring(L, "%s", strerror(en));
    lua_pushinteger(L, en);
    return 3;
  }
}

static FILE *tofile (lua_State *L) {
  FILE **f = (FILE **)luaL_checkudata(L, 1, PIPE_FILEHANDLE);
  if (*f == NULL)
    luaL_error(L, "attempt to use a closed file");
  return *f;
}

static int f_close (lua_State *L) {
  FILE *f = tofile(L);
  fclose(f);
  return 0;
}

static int read_line (lua_State *L, FILE *f);

static int io_readline (lua_State *L) {
  FILE *f = *(FILE **)lua_touserdata(L, lua_upvalueindex(1));
  int sucess;
  if (f == NULL)  /* file is already closed? */
    luaL_error(L, "file is already closed");
  sucess = read_line(L, f);
  if (ferror(f))
    return luaL_error(L, "%s", strerror(errno));
  if (sucess) return 1;
  else {  /* EOF */
    return 0;
  }
}

static int f_lines (lua_State *L) {
  tofile(L);  /* check that it's a valid file handle */
  lua_pushvalue(L, 1);
  lua_pushcclosure(L, io_readline, 1);
  return 1;
}

static int read_number (lua_State *L, FILE *f) {
  lua_Number d;
  if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) {
    lua_pushnumber(L, d);
    return 1;
  }
  else return 0;  /* read fails */
}


static int test_eof (lua_State *L, FILE *f) {
  int c = getc(f);
  ungetc(c, f);
  lua_pushlstring(L, NULL, 0);
  return (c != EOF);
}

static int read_line (lua_State *L, FILE *f) {
  luaL_Buffer b;
  luaL_buffinit(L, &b);
  for (;;) {
    size_t l;
    char *p = luaL_prepbuffer(&b);
    if (fgets(p, LUAL_BUFFERSIZE, f) == NULL) {  /* eof? */
      luaL_pushresult(&b);  /* close buffer */
      return (lua_strlen(L, -1) > 0);  /* check whether read something */
    }
    l = strlen(p);
    if (l == 0 || p[l-1] != '\n')
      luaL_addsize(&b, l);
    else {
      luaL_addsize(&b, l - 1);  /* do not include `eol' */
      luaL_pushresult(&b);  /* close buffer */
      return 1;  /* read at least an `eol' */
    }
  }
}


static int read_chars (lua_State *L, FILE *f, size_t n) {
  size_t rlen;  /* how much to read */
  size_t nr;  /* number of chars actually read */
  luaL_Buffer b;
  luaL_buffinit(L, &b);
  rlen = LUAL_BUFFERSIZE;  /* try to read that much each time */
  do {
    char *p = luaL_prepbuffer(&b);
    if (rlen > n) rlen = n;  /* cannot read more than asked */
    nr = fread(p, sizeof(char), rlen, f);
    luaL_addsize(&b, nr);
    n -= nr;  /* still have to read `n' chars */
  } while (n > 0 && nr == rlen);  /* until end of count or eof */
  luaL_pushresult(&b);  /* close buffer */
  return (n == 0 || lua_strlen(L, -1) > 0);
}


static int f_read (lua_State *L) {
  FILE *f = tofile(L);
  int nargs = lua_gettop(L) - 1;
  int success;
  int n;
  clearerr(f);
  if (nargs == 0) {  /* no arguments? */
    success = read_line(L, f);
    n = 2+1;  /* to return 1 result */
  }
  else {  /* ensure stack space for all results and for auxlib's buffer */
    luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments");
    success = 1;
    for (n = 2; nargs-- && success; n++) {
      if (lua_type(L, n) == LUA_TNUMBER) {
        size_t l = (size_t)lua_tointeger(L, n);
        success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l);
      }
      else {
        const char *p = lua_tostring(L, n);
        luaL_argcheck(L, p && p[0] == '*', n, "invalid option");
        switch (p[1]) {
          case 'n':  /* number */
            success = read_number(L, f);
            break;
          case 'l':  /* line */
            success = read_line(L, f);
            break;
          case 'a':  /* file */
            read_chars(L, f, ~((size_t)0));  /* read MAX_SIZE_T chars */
            success = 1; /* always success */
            break;
          default:
            return luaL_argerror(L, n, "invalid format");
        }
      }
    }
  }
  if (ferror(f))
    return pushresult(L, 0, NULL);
  if (!success) {
    lua_pop(L, 1);  /* remove last result */
    lua_pushnil(L);  /* push nil instead */
  }
  return n - 2;
}

static int f_write (lua_State *L)
{
  FILE *f = tofile(L);
  int nargs = lua_gettop(L) - 1;
  int status = 1;
  int arg = 2;
  for (; nargs--; arg++) {
    if (lua_type(L, 2) == LUA_TNUMBER) {
      /* optimization: could be done exactly as for strings */
      status = status &&
          fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0;
    }
    else {
      size_t l;
      const char *s = luaL_checklstring(L, arg, &l);
      status = status && (fwrite(s, sizeof(char), l, f) == l);
    }
  }
  return pushresult(L, status, NULL);
}


static int f_flush (lua_State *L) {
  return pushresult(L, fflush(tofile(L)) == 0, NULL);
}

static const struct luaL_reg spawner[] = {
    {"verbose",do_verbose},
	{"new",new_spawner},
	{"popen",do_popen},
	{"popen2",do_popen2},
    {"foreground",do_foreground},
	{NULL, NULL}
};

static const struct luaL_reg spawner_methods[] = {
	{"set_output",spawner_set_output},
	{"set_result",spawner_set_result},
	{"use_shell",spawner_use_shell},
	{"write",spawner_write},
	{"read",spawner_read},	
	{"kill",spawner_kill},	
	{"run",spawner_run},	
	{NULL, NULL}
};

static const luaL_Reg file_methods[] = {
  {"close", f_close},
  {"flush", f_flush},
  {"lines", f_lines},
  {"read", f_read},
  {"write", f_write},
  {NULL, NULL}
};

static void createmeta (lua_State *L) {
  luaL_newmetatable(L, PIPE_FILEHANDLE);  /* create metatable for file handles */
  lua_pushvalue(L, -1);  /* push metatable */
  lua_setfield(L, -2, "__index");  /* metatable.__index = metatable */
  luaL_register(L, NULL, file_methods);  /* file methods */
}

int luaopen_spawner(lua_State *L)
{
	createmeta(L);
	luaL_register(L, "spawner", spawner);
	luaL_newmetatable(L, "SPAWNER");  // create metatable for spawner objects
	lua_pushvalue(L, -1);  // push metatable
	lua_setfield(L, -2, "__index");  // metatable.__index = metatable
	luaL_register(L, NULL, spawner_methods); 	
	return 1;	
}
#else // MINGW
//build@ gcc -shared -o spawner-ex.dll -I..\..\lua\include win32-spawner-ex.c scite.la 
#include <windows.h>
#include <assert.h>
#include <stdio.h>
#include <process.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <ctype.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

static char buffer[16384]; // buffer for reading from a process
static char linebuf[512];  // line buffer
static char outbuf[16384]; // buffer for constructing messages to SciTE
static int fulllines = 1;
static HWND hSciTE;
static HWND hSelf;
static HINSTANCE hInstance;

// Director interface to other SciTE instances.
static int SendToHandle(HWND hwnd, const char *Message)
{
  COPYDATASTRUCT cds;
  int size = strlen(Message);
  cds.dwData = 0;	
  cds.lpData = (void *)Message;
  cds.cbData = size;
  return SendMessage(hwnd, WM_COPYDATA,(WPARAM)NULL,(LPARAM)&cds);
}

static int size_of_array(HWND* results)
{
    int idx = 0;	
    // find our place in the output array	
    for (idx = 0; results[idx] != NULL; idx++) ;	
    return idx;
}

BOOL CALLBACK EnumWindowsProc(HWND  hwnd, LPARAM  lParam)
{
    HWND* results = (HWND*)lParam;
    int idx = size_of_array(results);	
    char buff[256];
    GetClassName(hwnd,buff,sizeof(buff));	
    if (strcmp(buff,"DirectorExtension") == 0) {
        if (GetWindowLongPtr(hwnd,GWLP_USERDATA) == hSciTE) {
            hSelf = hwnd;     
        } else {
            results[idx] = hwnd;
            results[idx+1] = NULL;
        } 
    }
    return TRUE;
}

// 0 refers to current instance, 1,2...etc to other instances.
static int do_perform(lua_State* L)
{
    char buff[128];
    const char* cmd = lua_tostring(L,1);
    int this_instance = lua_tointeger(L,2);
    HWND results[50];
    int nmsg;
    results[0] = 0;
    EnumWindows(EnumWindowsProc,(long)results);
    nmsg = size_of_array(results);	
    if (this_instance > nmsg) {
        lua_pushnil(L);
        lua_pushliteral(L,"no other instance available");
        return 2;
    } else {
        int res = SendToHandle(this_instance ? results[this_instance-1] : hSelf,cmd);
        lua_pushinteger(L,res);
        return 1;
    }
}

static int do_foreground(lua_State* L)
{
    SetForegroundWindow(hSciTE);
    return 0;
}

static int do_verbose(lua_State* L)
{
    return 0;
}

static int do_fulllines(lua_State* L)
{
    //fulllines = luaL_checkboolean(L,1);
    return 0;
}

const UINT STE_WM_ONEXECUTE = 2001;

static void exec_lua(lua_State* L, const char* fun, const char* args)
{
    lua_getglobal(L,fun);
    lua_pushstring(L,args);
    if (lua_pcall(L,1,0,0) != 0) {
        lua_getglobal(L,"print");
        lua_pcall(L,1,0,0);
    }
}


LRESULT PASCAL WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    WNDPROC lpPrevWndProc;
    if (uMsg == STE_WM_ONEXECUTE) {
        lua_State *L = (lua_State *)wParam;
        char* args = (char *)lParam;
        char* fun, *arg;
        char* ptr = strchr(args,' ');
        *ptr = '\0';
        fun = args;
        arg = ptr+1;
        exec_lua(L,fun,arg);
        free(args);
        return 0;
    }

    lpPrevWndProc = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    if (lpPrevWndProc)
        return CallWindowProc(lpPrevWndProc, hwnd, uMsg, wParam, lParam);

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

static HWND hwndDispatcher;

void create_thread_window(void)
{
    LONG_PTR subclassedProc;
    hwndDispatcher = CreateWindow(
        "STATIC", "SciTE_Spawner_Dispatcher",
        0, 0, 0, 0, 0, 0, 0, GetModuleHandle(NULL), 0
    );
    subclassedProc = SetWindowLongPtr(hwndDispatcher, GWLP_WNDPROC, (LONG_PTR)WndProc);
    SetWindowLongPtr(hwndDispatcher, GWLP_USERDATA, subclassedProc);
}

#define SPAWNER "SPAWNER"

typedef struct {
    const char* command_line;	
    const char* output;
    const char* result;
    lua_State* L;
    BOOL peeking;
    BOOL running;
    HANDLE hProcess;
    PROCESS_INFORMATION pi;
    HANDLE hWriteSubProcess;
    HANDLE hPipeRead;
    HANDLE hPipeWrite;
    FILE* inf;
} Spawner;


static BOOL start_process(Spawner* p)
{
    const char* prog = p->command_line;
    SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), 0, 0};
    SECURITY_DESCRIPTOR sd;
    STARTUPINFO si = {
                 sizeof(STARTUPINFO), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
             };	

    HANDLE hRead2;
    HANDLE hProcess = GetCurrentProcess();
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = NULL;
    InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = &sd;

    // Create pipe for output redirection
    // read handle, write handle, security attributes,  number of bytes reserved for pipe - 0 default
    CreatePipe(&p->hPipeRead, &p->hPipeWrite, &sa, 0);
    
    // Create pipe for input redirection. In this code, you do not
    // redirect the output of the child process, but you need a handle
    // to set the hStdInput field in the STARTUP_INFO struct. For safety,
    // you should not set the handles to an invalid handle.

    hRead2 = NULL;
    // read handle, write handle, security attributes,  number of bytes reserved for pipe - 0 default
    CreatePipe(&hRead2, &p->hWriteSubProcess, &sa, 0);
    
    SetHandleInformation(p->hPipeRead, HANDLE_FLAG_INHERIT, 0);
    SetHandleInformation(p->hWriteSubProcess, HANDLE_FLAG_INHERIT, 0);

    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.wShowWindow = SW_HIDE;
    si.hStdInput = hRead2;
    si.hStdOutput = p->hPipeWrite;
    si.hStdError = p->hPipeWrite;

    p->running = CreateProcess(
              NULL,
              (char*)prog,
              NULL, NULL,
              TRUE, CREATE_NEW_PROCESS_GROUP,
              NULL,
              NULL, // start directory
              &si, &p->pi);
              
    CloseHandle(p->pi.hThread);
    CloseHandle(hRead2);
    CloseHandle(p->hPipeWrite);
    return p->running;
}

static void send_to_scite(lua_State* L, const char* fun, const char* args)
{
    char* command = (char*)malloc(strlen(fun)+strlen(args)+1+1);
    sprintf(command,"%s %s",fun,args);
    SendMessage(hwndDispatcher, STE_WM_ONEXECUTE, (WPARAM)L, (LPARAM)command);
}

static void monitor_process(Spawner* p)
{
    DWORD exitcode;
    if (WAIT_OBJECT_0 == WaitForSingleObject(p->pi.hProcess, INFINITE)) {
        GetExitCodeProcess(p->pi.hProcess, &exitcode);	
        CloseHandle(p->pi.hProcess);
        if (p->result) {
            sprintf(buffer,"%d",exitcode);
            send_to_scite(p->L,p->result,buffer);
        }	
    }
}


static void run_process(Spawner* p)
{
    DWORD bytesRead = 0;
    BOOL bTest;
    DWORD exitcode;	
    //* FILE* reader = fdopen(p->hPipeRead,"r");
    Sleep(50);
    
    while (p->running) {
        *buffer = '\0';
        while (1) {
            bTest = ReadFile(p->hPipeRead, linebuf,
                           sizeof(linebuf), &bytesRead, NULL);
            if (bTest && bytesRead) {
                linebuf[bytesRead] = '\0';
                if (fulllines && linebuf[bytesRead-1] == '\n') {
                    strcat(buffer,linebuf);
                    break;
                } else {
                    strcat(buffer,linebuf);
                }				
            } else {
                p->running = FALSE;
                break;
            }
        }
        send_to_scite(p->L,p->output,buffer);
    }
    GetExitCodeProcess(p->pi.hProcess, &exitcode);	
    CloseHandle(p->pi.hProcess);
    if (p->result) {
        sprintf(buffer,"%d",exitcode);
        send_to_scite(p->L,p->result,buffer);
    }
    //* fclose(reader);
    CloseHandle(p->hPipeRead);
}


static int new_spawner(lua_State* L)
{
    Spawner* spp = (Spawner*)lua_newuserdata(L,sizeof(Spawner));
    memset(spp,0,sizeof(Spawner));
    spp->command_line = luaL_checkstring(L,1);
    spp->L = L;
    luaL_getmetatable(L,SPAWNER);
    lua_setmetatable(L,-2);
    return 1;
}

static int spawner_set_output(lua_State* L)
{
    Spawner* spp = (Spawner*)lua_touserdata(L,1);
    spp->output = luaL_checkstring(L,2);
    return 0;
}

static int spawner_set_result(lua_State* L)
{
    Spawner* spp = (Spawner*)lua_touserdata(L,1);
    spp->result = luaL_checkstring(L,2);
    return 0;
}

static int spawner_use_shell(lua_State* L)
{
    Spawner* spp = (Spawner*)lua_touserdata(L,1);
    char* new_command;
    const char* comspec = getenv("COMSPEC");
    if (! comspec) comspec = "CMD.EXE";
    new_command = (char*)malloc(strlen(spp->command_line)+20);
    sprintf(new_command,"%s /c %s",comspec,spp->command_line);
    spp->command_line = new_command;
    return 0;
}

static int spawner_write(lua_State* L)
{
    Spawner* spp = (Spawner*)lua_touserdata(L,1);	
    const char* buff = luaL_checkstring(L,2);
    DWORD bytesWrote;
    DWORD nReaded = strlen(buff);
    WriteFile(spp->hWriteSubProcess,buff,nReaded, &bytesWrote, NULL);		
    lua_pushboolean(L,nReaded == bytesWrote);
    return 1;
}

static int spawner_read(lua_State* L)
{
    Spawner* spp = (Spawner*)lua_touserdata(L,1);
    char* res = fgets(buffer,sizeof(buffer),spp->inf);
    if (res) {
        lua_pushstring(L,buffer);
        return 1;
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static void newfile_from_handle (lua_State *L, HANDLE handle, const char* mode);

static int spawner_run(lua_State* L)
{
    Spawner* spp = (Spawner*)lua_touserdata(L,1);
    if (! start_process(spp)) {
        lua_pushboolean(L,FALSE);
        return 1;
    }
    //_beginthread(monitor_process,0,spp);
    if (spp->output != NULL) {
        _beginthread(run_process, 1024 * 1024,spp);
    } else {
        int fd;
        fd = _open_osfhandle((long)spp->hPipeRead, _O_BINARY);
        spp->inf = _fdopen(fd,"r");
        //Sleep(50);
    }
    lua_pushboolean(L,TRUE);
    return 1;
}

static Spawner* start_popen(lua_State* L)
{
    Spawner* spp = (Spawner*)malloc(sizeof(Spawner));
    const char* comspec = getenv("COMSPEC");
    char* new_command;

    memset(spp,0,sizeof(Spawner));
    spp->command_line = luaL_checkstring(L,1);
    if (! comspec) comspec = "CMD.EXE";
    new_command = (char*)malloc(strlen(spp->command_line) + strlen(comspec) + 20);
    sprintf(new_command,"%s /c %s",comspec,spp->command_line);
    spp->command_line = new_command;
    if (! start_process(spp)) {
        lua_pushboolean(L,FALSE);
        return NULL;
    }
    //_beginthread(monitor_process,0,spp);
    return spp;
}

static int do_popen(lua_State* L)
{
    Spawner* spp = start_popen(L);
    newfile_from_handle(L,spp->hPipeRead,"rt");
    return 1;
}

static int do_popen2(lua_State* L)
{
    Spawner* spp = start_popen(L);
    newfile_from_handle(L,spp->hWriteSubProcess,"wt");
    newfile_from_handle(L,spp->hPipeRead,"rt");
    return 2;
}

static int spawner_kill(lua_State* L)
{
    Spawner* spp = (Spawner*)lua_touserdata(L,1);
    TerminateProcess(spp->pi.hProcess, 2);
    spp->pi.hProcess = NULL;
    return 0;
}


/////// stuff from liolib

#define PIPE_FILEHANDLE "PIPE_FILE*"

// based on liolib.c
static void newfile_from_handle (lua_State *L, HANDLE handle, const char* mode)
{
  FILE **pf = (FILE **)lua_newuserdata(L, sizeof(FILE *));
  int fd;
  *pf = NULL;
  luaL_getmetatable(L, PIPE_FILEHANDLE);
  lua_setmetatable(L, -2);
  fd = _open_osfhandle((long)handle, _O_TEXT);
  *pf = _fdopen(fd,mode);
  // leaves the userdata on the lua stack!
}

static int pushresult (lua_State *L, int i, const char *filename) {
  int en = errno;  /* calls to Lua API may change this value */
  if (i) {
    lua_pushboolean(L, 1);
    return 1;
  }
  else {
    lua_pushnil(L);
    if (filename)
      lua_pushfstring(L, "%s: %s", filename, strerror(en));
    else
      lua_pushfstring(L, "%s", strerror(en));
    lua_pushinteger(L, en);
    return 3;
  }
}

static FILE *tofile (lua_State *L) {
  FILE **f = (FILE **)luaL_checkudata(L, 1, PIPE_FILEHANDLE);
  if (*f == NULL)
    luaL_error(L, "attempt to use a closed file");
  return *f;
}

static int f_close (lua_State *L) {
  FILE *f = tofile(L);
  fclose(f);
  return 0;
}

static int read_line (lua_State *L, FILE *f);

static int io_readline (lua_State *L) {
  FILE *f = *(FILE **)lua_touserdata(L, lua_upvalueindex(1));
  int sucess;
  if (f == NULL)  /* file is already closed? */
    luaL_error(L, "file is already closed");
  sucess = read_line(L, f);
  if (ferror(f))
    return luaL_error(L, "%s", strerror(errno));
  if (sucess) return 1;
  else {  /* EOF */
    return 0;
  }
}

static int f_lines (lua_State *L) {
  tofile(L);  /* check that it's a valid file handle */
  lua_pushvalue(L, 1);
  lua_pushcclosure(L, io_readline, 1);
  return 1;
}

static int read_number (lua_State *L, FILE *f) {
  lua_Number d;
  if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) {
    lua_pushnumber(L, d);
    return 1;
  }
  else return 0;  /* read fails */
}


static int test_eof (lua_State *L, FILE *f) {
  int c = getc(f);
  ungetc(c, f);
  lua_pushlstring(L, NULL, 0);
  return (c != EOF);
}


static int read_line (lua_State *L, FILE *f) {
  luaL_Buffer b;
  luaL_buffinit(L, &b);
  for (;;) {
    size_t l;
    char *p = luaL_prepbuffer(&b);
    if (fgets(p, LUAL_BUFFERSIZE, f) == NULL) {  /* eof? */
      luaL_pushresult(&b);  /* close buffer */
      return (lua_strlen(L, -1) > 0);  /* check whether read something */
    }
    l = strlen(p);
    if (l == 0 || p[l-1] != '\n')
      luaL_addsize(&b, l);
    else {
      luaL_addsize(&b, l - 1);  /* do not include `eol' */
      luaL_pushresult(&b);  /* close buffer */
      return 1;  /* read at least an `eol' */
    }
  }
}


static int read_chars (lua_State *L, FILE *f, size_t n) {
  size_t rlen;  /* how much to read */
  size_t nr;  /* number of chars actually read */
  luaL_Buffer b;
  luaL_buffinit(L, &b);
  rlen = LUAL_BUFFERSIZE;  /* try to read that much each time */
  do {
    char *p = luaL_prepbuffer(&b);
    if (rlen > n) rlen = n;  /* cannot read more than asked */
    nr = fread(p, sizeof(char), rlen, f);
    luaL_addsize(&b, nr);
    n -= nr;  /* still have to read `n' chars */
  } while (n > 0 && nr == rlen);  /* until end of count or eof */
  luaL_pushresult(&b);  /* close buffer */
  return (n == 0 || lua_strlen(L, -1) > 0);
}


static int f_read (lua_State *L) {
  FILE *f = tofile(L);
  int nargs = lua_gettop(L) - 1;
  int success;
  int n;
  clearerr(f);
  if (nargs == 0) {  /* no arguments? */
    success = read_line(L, f);
    n = 2+1;  /* to return 1 result */
  }
  else {  /* ensure stack space for all results and for auxlib's buffer */
    luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments");
    success = 1;
    for (n = 2; nargs-- && success; n++) {
      if (lua_type(L, n) == LUA_TNUMBER) {
        size_t l = (size_t)lua_tointeger(L, n);
        success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l);
      }
      else {
        const char *p = lua_tostring(L, n);
        luaL_argcheck(L, p && p[0] == '*', n, "invalid option");
        switch (p[1]) {
          case 'n':  /* number */
            success = read_number(L, f);
            break;
          case 'l':  /* line */
            success = read_line(L, f);
            break;
          case 'a':  /* file */
            read_chars(L, f, ~((size_t)0));  /* read MAX_SIZE_T chars */
            success = 1; /* always success */
            break;
          default:
            return luaL_argerror(L, n, "invalid format");
        }
      }
    }
  }
  if (ferror(f))
    return pushresult(L, 0, NULL);
  if (!success) {
    lua_pop(L, 1);  /* remove last result */
    lua_pushnil(L);  /* push nil instead */
  }
  return n - 2;
}


static int f_write (lua_State *L)
{
  FILE *f = tofile(L);
  int nargs = lua_gettop(L) - 1;
  int status = 1;
  int arg = 2;
  for (; nargs--; arg++) {
    if (lua_type(L, 2) == LUA_TNUMBER) {
      /* optimization: could be done exactly as for strings */
      status = status &&
          fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0;
    }
    else {
      size_t l;
      const char *s = luaL_checklstring(L, arg, &l);
      status = status && (fwrite(s, sizeof(char), l, f) == l);
    }
  }
  return pushresult(L, status, NULL);
}


static int f_flush (lua_State *L) {
  return pushresult(L, fflush(tofile(L)) == 0, NULL);
}

static const struct luaL_reg spawner[] = {
    {"foreground",do_foreground},
    {"verbose",do_verbose},
    {"fulllines",do_fulllines},
    {"new",new_spawner},
    {"popen",do_popen},
    {"popen2",do_popen2},
    {"perform",do_perform},
    {NULL, NULL}
};

static const struct luaL_reg spawner_methods[] = {
    {"set_output",spawner_set_output},
    {"set_result",spawner_set_result},
    {"use_shell",spawner_use_shell},
    {"write",spawner_write},
    {"read",spawner_read},	
    {"kill",spawner_kill},	
    {"run",spawner_run},	
    {NULL, NULL}
};


static const luaL_Reg file_methods[] = {
  {"close", f_close},
  {"flush", f_flush},
  {"lines", f_lines},
  {"read", f_read},
  {"write", f_write},
  {NULL, NULL}
};

static void createmeta (lua_State *L) {
  luaL_newmetatable(L, PIPE_FILEHANDLE);  /* create metatable for file handles */
  lua_pushvalue(L, -1);  /* push metatable */
  lua_setfield(L, -2, "__index");  /* metatable.__index = metatable */
  luaL_register(L, NULL, file_methods);  /* file methods */
}

__declspec(dllexport)
int luaopen_spawner(lua_State *L)
{
    hSciTE = GetForegroundWindow();
    hInstance = GetWindowLong(hSciTE,GWL_HINSTANCE);
    create_thread_window();
    createmeta(L);

    luaL_register (L, "spawner", spawner);
    luaL_newmetatable(L, SPAWNER);  // create metatable for spawner objects
    lua_pushvalue(L, -1);  // push metatable
    lua_setfield(L, -2, "__index");  // metatable.__index = metatable
    luaL_register(L, NULL, spawner_methods); 	
    return 1;	
}
#endif // MINGW
