#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "newt.h"
#include "log.h"
#include "run.h"
#include "windows.h"

int runProgramRoot(enum runType runType, char * root, char * name,
                  char ** args) {
   return runProgramIORoot(runType, root, name, args, NULL, NULL);
}

int runProgramIO(enum runType runType, char * name, char ** args, char * in,
                char ** out) {
   return runProgramIORoot(runType, NULL, name, args, in, out);
}

int runProgramIORoot(enum runType runType, char * root, char * name,
               char ** args, char * in, char ** out) {

   char * buf;
   int infd, outfd, errfd, status, i;
   int resultfd = 0;
   pid_t pid;
   char ** currarg;
   int inputPipe[2], outputPipe[2];
   char resultbuf[200];
   char * result;
   int resultSize;
   char fullname[200];

   /* if "in" is set, it better be short. We assume one write() call is
      enough */

   if (root) {
       sprintf(fullname, "%s/%s", root, name);
   } else
       strcpy(fullname, name);

   i = 0;
   i = strlen(name) + 50;

   currarg = args;
   while (*currarg) {
       i += strlen(*currarg) + 1;
       currarg++;
   }

   buf = alloca(i);

   if (testing)
       strcpy(buf, "if I weren't testing I would run:\n\n");
   else
       strcpy(buf, "running: ");

   strcat(buf, name);
   strcat(buf, " ");

   currarg = args;
   while (*currarg) {
       strcat(buf, *currarg);
       strcat(buf, " ");
       currarg++;
   }

   if (testing) {
       newtComponent t, f, succeed, fail;

       newtOpenWindow(17, 4, 45, 15, "Running");

       succeed = newtButton(8, 10, "Succeed");
       fail = newtButton(28, 10, "Fail");
       t = newtTextbox(2, 1, 40, 13, NEWT_TEXTBOX_WRAP | NEWT_TEXTBOX_SCROLL);
       newtTextboxSetText(t, buf);
       f = newtForm(NULL, NULL, 0);

       newtFormAddComponents(f, t, succeed, fail, NULL);

       t = newtRunForm(f);

       newtFormDestroy(f);
       newtPopWindow();

       return t == fail;
   }

   if (access(fullname,  X_OK))  {
       logMessage("cannot run %s: %s", fullname, strerror(errno));
       messageWindow("Error", "I cannot run %s: %s", fullname,
                           strerror(errno));
       return -1;
   }


   logMessage(buf);

   if (root)
       logMessage("    root is %s", root);

   if (in) {
       pipe(inputPipe);
       write(inputPipe[1], in, strlen(in));
       close(inputPipe[1]);
       infd = inputPipe[0];
   } else {
       infd = open("/dev/null", O_RDONLY);
   }

   errfd = open("/dev/tty5", O_APPEND | O_CREAT);
   if (errfd < 0)
       errfd = open("/tmp/exec.log", O_APPEND | O_CREAT);

   if (out) {
       pipe(outputPipe);
       outfd = outputPipe[1];
       resultfd = outputPipe[0];
   } else if (runType & RUN_LOG) {
       outfd = open("/dev/tty5", O_RDWR);
       if (outfd < 0)
           outfd = open("/tmp/exec.log", O_APPEND | O_CREAT);
   } else
       outfd = open("/dev/null", O_RDWR);

   if (!(pid = fork())) {
       close(0);
       close(1);
       close(2);

       if (root) {
           chroot(root);
           chdir("/");
       }

       dup2(infd, 0);
       dup2(outfd, 1);
       dup2(errfd, 2);

       close(infd);
       close(outfd);
       close(errfd);
       if (out) close(resultfd);

       execv(name, args);
       logMessage("exec of %s failed: %s", name, strerror(errno));
       exit(-1);
   }

   close(infd);
   close(outfd);
   close(errfd);

   if (out) {
       resultSize = 0;
       result = NULL;

       do {
           i = read(resultfd, resultbuf, sizeof(resultbuf));

           if (!result) {
               result = malloc(i + 1);
           } else
               result = realloc(result, resultSize + i + 1);
           memcpy(result + resultSize, resultbuf, i);
           resultSize += i;
           result[resultSize] = '\0';
       } while (i > 0);

       close(resultfd);
       *out = result;
   }

   waitpid(pid, &status, 0);

   if (WIFEXITED(status))
       return WEXITSTATUS(status);

   return -1;
}

int runProgram(enum runType runType, char * name, char ** args) {
   return runProgramIO(runType, name, args, NULL, NULL);
}