#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#ifdef UNIX

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>

#else

#include <windows.h>

#endif

#include "command.h"
#include "util.h"
#include "yanling.h"
#include "jobs.h"

#ifndef UNIX
/* Windows-only */
/* This doesn't really belong in command.c, but for collaboration reasons,
   I'm leaving it here */

int ldir(int argc, char * const argv[]);
#endif

/*
struct command {
  const char *cmdLine;
  char *cmdCopy;
  int argc;
  char * const *argv;
  int runInForeground;
#ifdef UNIX
  pid_t pid;
#else
  PROCESS_INFORMATION pi;
#endif
  const char *outputfn,*inputfn;
};
*/

struct command *commandAlloc() {
  struct command *self=talloc(struct command,1);
  self->runInForeground=1;
  self->inputfn=NULL;
  self->outputfn=NULL;
  self->cmdLine=NULL;
  self->cmdCopy=NULL;
  self->argv=NULL;
  return self;
}

void commandDump(struct command *cc) {
  fprintf(stderr,"read command line '%s'\n",cc->cmdLine);
  fprintf(stderr,"argc:%d\n",cc->argc);
  fprintf(stderr,"argv:");
  { 
    char * const *arg=cc->argv;
    while (*arg) {
      fprintf(stderr,"'%s' ",*arg);
      arg++;
    }
  }
  fprintf(stderr,"\n");
  fprintf(stderr,"output filename: '%s'\n",cc->outputfn);
  fprintf(stderr,"input filename: '%s'\n",cc->inputfn);
}

void builtinCommandExec(enum builtin whichBuiltIn, struct command *cc, struct history *h) {
  /* FIXME: replace this with a table of function pointers */
  switch (whichBuiltIn) {
    case EXIT: doexit(cc->argc,cc->argv); break;
    case JOBS: jobs(cc->argc,cc->argv); break;
    case HISTORY: historyList(h); break;
    case CD: cd(cc->argc,cc->argv); break;
    case KILL: dokill(cc->argc,cc->argv); break;	    
    case HELP: help(cc->argc,cc->argv); break;
    case STOP: dostop(cc->argc,cc->argv); break;
    case FG: fg(cc->argc,cc->argv); break;
    case BG: bg(cc->argc,cc->argv); break;
#ifndef UNIX
    case LDIR: ldir(cc->argc,cc->argv); break;
#endif
    default:
      fprintf(stderr,"Strange built-in %d: internal error\n",whichBuiltIn);
  }
}

int commandSplit(struct command *cc) {
  /* metacharacters: space < > &
   * split on metacharacters, but keep < > & metachars
   */
  charpStack *argvs=charpStackNew();
  char *c;
  int inStr=0;
  int nextIsIn=0;
  int nextIsOut=0;

  assert (cc->cmdLine);

  cc->cmdCopy=(char*)strclone(cc->cmdLine);
  c=cc->cmdCopy;

  while (*c) {
    if (*c == ' ' || *c == '\t' || *c == '\n') {
      *c='\0';
      inStr=0;
    } else if (*c == '<') {
      if (nextIsOut || nextIsIn) { } /*  ERROR FIXME */
      *c='\0';
      inStr=0;
      nextIsIn=1;
    } else if (*c == '>') {
      if (nextIsOut || nextIsIn) { } /* ERROR FIXME */
      *c='\0';
      inStr=0;
      nextIsOut=1;
    } else if (*c == '&') {
      *c='\0';
      inStr=0;
      cc->runInForeground=0;
    } else {
      if (!inStr) {
        if (nextIsIn) cc->inputfn=c;
        else if (nextIsOut) cc->outputfn=c;
        else charpStackPush(argvs,c);
        inStr=1;
      }
      nextIsIn=0;
      nextIsOut=0;
    }
    c++;
  }
  charpStackPush(argvs,NULL);
  cc->argv=(char * const *)charpStackContents(argvs);
  cc->argc=charpStackLen(argvs)-1; /* remove one for the NULL termination */
  free(argvs);
  return 0;
}

enum builtin testBuiltin(struct command *cc) {
  /* FIXME: use a perfect hash or something */
#define TESTBUILTIN(str,enm) if (exactIsStr(cc->argv[0],str)) { return enm; }
  if (cc->argc>0) {
    TESTBUILTIN("exit",EXIT) else
      TESTBUILTIN("jobs",JOBS) else
      TESTBUILTIN("history",HISTORY) else
      TESTBUILTIN("cd",CD) else
      TESTBUILTIN("kill",KILL) else
      TESTBUILTIN("fg",FG) else
      TESTBUILTIN("bg",BG) else
      TESTBUILTIN("stop",STOP) else
#ifndef UNIX
/* windows only */
      TESTBUILTIN("ldir",LDIR) else
#endif
      TESTBUILTIN("help",HELP)
  }
  return NOT_BUILTIN;
}

void commandFree(struct command *self) {
  if (self->cmdLine) free((char*)self->cmdLine);
  if (self->cmdCopy) free((char*)self->cmdCopy);
  if (self->argv) free((char**)self->argv);
  free(self);
}

#ifdef UNIX
void redirectIO(struct command *cc, const char *argv0) {
  if (cc->inputfn) {
    int fd=open(cc->inputfn,O_RDONLY);
    if (fd==-1) {
      fprintf(stderr,"%s: failed input redirect from '%s' of '%s': ",argv0,cc->inputfn,cc->cmdLine);
      perror("");
    } else {
      dup2(fd,STDIN_FILENO);
      /*close(fd)? */
    }
  }
  if (cc->outputfn) {
    int fd=creat(cc->outputfn,S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
    if (fd==-1) {
      fprintf(stderr,"%s: failed output redirect to '%s' of '%s': ",argv0,cc->outputfn,cc->cmdLine);
      perror("");
    } else {
      dup2(fd,STDOUT_FILENO);
      /*close(fd)? */
    }
  }
}
#endif

const char *commandGetCookedCmdLine(struct command *cc) {
  /* return command-line, less metacharacters */
  char *ret=malloc(strlen(cc->cmdLine)+1); /* plus one for null-term */
  int i;
  assert(ret);
  ret[0]='\0';
  for (i=0; i< cc->argc-1; i++) {
    strcat(ret,cc->argv[i]);
    strcat(ret," ");
  }
  strcat(ret,cc->argv[i]);
  return ret;
}

#ifdef UNIX

int commandExecChild(struct command *cc,const char *argv0) {
  cc->pid = fork();
  if (cc->pid == 0) { /* the child  */
    redirectIO(cc,argv0);
    execvp(cc->argv[0],cc->argv);
    fprintf(stderr,"%s: failed exec of '%s': ",argv0,cc->cmdLine);
    perror("");
    exit(1);
  }
  return 0;
}

int commandExecBuiltinChild(struct command *cc,enum builtin b,
    struct history *h,const char *argv0) {
  cc->pid = fork();
  if (cc->pid == 0) { /* the child  */
    redirectIO(cc,argv0);
    builtinCommandExec(b,cc,h);
    exit(0);
  }
  return 0;
}

#else
/* Windows */

int commandExecChild(struct command *cc,const char *argv0) {
  STARTUPINFO si;
  const char *cmdLine;
  HANDLE infh=INVALID_HANDLE_VALUE, outfh=INVALID_HANDLE_VALUE;
  SECURITY_ATTRIBUTES sa;

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

  ZeroMemory(&si,sizeof(si));
  si.cb=sizeof(si);

  ZeroMemory(&(cc->pi),sizeof(cc->pi));

  cmdLine = commandGetCookedCmdLine(cc);

  si.dwFlags=STARTF_USESTDHANDLES;
  si.hStdInput=GetStdHandle(STD_INPUT_HANDLE);
  si.hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
  si.hStdError=GetStdHandle(STD_ERROR_HANDLE);

  if (cc->inputfn) {
    infh=CreateFile(cc->inputfn,GENERIC_READ,FILE_SHARE_READ,
        &sa,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if (infh==INVALID_HANDLE_VALUE) {
      fprintf(stderr,"%s: error opening input file '%s' for <\n",argv0,
          cc->inputfn);
      wperror();
    } else {
      si.hStdInput=infh;
    }
  }

  if (cc->outputfn) {
    outfh=CreateFile(cc->outputfn,GENERIC_WRITE,0,
        &sa,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
    if (outfh==INVALID_HANDLE_VALUE) {
      fprintf(stderr,"%s: error opening output file '%s' for >\n",argv0,
          cc->outputfn);
      wperror();
    } else {
      si.hStdOutput=outfh;
    }
  }

  if (!CreateProcess(NULL,(char*)cmdLine,NULL,NULL,TRUE,0,NULL,NULL,&si,
        &(cc->pi))) {
    fprintf(stderr,"%s: failed create process of '%s': ",argv0,cmdLine);
    wperror();
    /* free((char*) cmdLine); */
    return -1;
  } else {
    /* close parent process' handle to the redirected files */
    if (infh != INVALID_HANDLE_VALUE) CloseHandle(infh);
    if (outfh != INVALID_HANDLE_VALUE) CloseHandle(outfh);
  }
  return 0;
}

int commandExecBuiltinChild(struct command *cc,enum builtin b,
    struct history *h,const char *argv0) {
  builtinCommandExec(b,cc,h);
  return -1; /* indicate failed create-thread so it won't wait */
}
#endif

void commandExec(struct command *cc,const char *argv0,struct history *h)  {
  enum builtin b;
  int ret;

#ifndef UNIX
  /* Windows only  */
  /* save console mode.
     Cygwin processes change the console mode to something else (not ENABLE_LINE_INPUT),
     so the next time the shell goes to read from std input, unexpected things happen. */

  DWORD consoleMode;

  if (!GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),&consoleMode)) {
          fprintf(stderr,"%s: Error in GetConsoleMode\n",argv0);
          wperror();
  }
#endif

  b=testBuiltin(cc);
  /* you can't run exit or cd or bg in a different process :) */
  if (b==EXIT || b==CD || b==BG || b==FG || b==JOBS) builtinCommandExec(b,cc,h);
  else {
    if (b != NOT_BUILTIN){
      ret=commandExecBuiltinChild(cc,b,h,argv0);
    } else {
      ret=commandExecChild(cc,argv0);
    }
    if (ret==0) { /* succesful exec */
      if (cc->runInForeground) {
#ifdef UNIX
        ccForeground = cc;
        waitpid (cc->pid,NULL,WUNTRACED);
        ccForeground = NULL;
#else
        if (b == NOT_BUILTIN) {
          if (WaitForSingleObject(cc->pi.hProcess,INFINITE)==WAIT_FAILED) wperror();
          if (!CloseHandle( cc->pi.hProcess )) wperror();
          if (!CloseHandle( cc->pi.hThread )) wperror();

          /* restore saved console mode, just in case.  See above note */
          if (!SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),consoleMode)) {
                  fprintf(stderr,"%s: Error in SetConsoleMode\n",argv0);
                  wperror();
          }
        } else {
          if (WaitForSingleObject(cc->pi.hThread,INFINITE)==WAIT_FAILED) wperror();
          if (!CloseHandle( cc->pi.hThread )) wperror();
        }
#endif
      } else {
#ifdef UNIX
        addJob(cc->pid,strclone(cc->cmdLine));
#else
        addJob(cc->pi,strclone(cc->cmdLine));
#endif
      }
    }
  }
}

#ifndef UNIX
/* Windows-only */
/* This doesn't really belong in command.c, but for collaboration reasons,
   I'm leaving it here */

struct ldirdata {
  DWORD totalsize;
  int numfiles;
  int numdirs;
};

/* FIXME: deal with big (>MAXDWORD) files and directories */

void printFile(WIN32_FIND_DATA *ffd,struct ldirdata *lddp) {
  /* this code based on the MSDN example "Retrieving the Last-Write Time" */ 
  SYSTEMTIME stUTC,stLocal;
  FileTimeToSystemTime(&(ffd->ftLastWriteTime),&stUTC); 
  SystemTimeToTzSpecificLocalTime(NULL,&stUTC,&stLocal);
  printf ("%02d/%02d/%04d  %02d:%02d",
        stLocal.wMonth, stLocal.wDay, stLocal.wYear,
        stLocal.wHour, stLocal.wMinute);
  if (ffd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
    printf(" <DIR>      ");
    lddp->numdirs++;
  } else {
    lddp->numfiles++;
    if (ffd->nFileSizeHigh) 
      printf(" (big file) ");
    else {
      printf ("%12d",ffd->nFileSizeLow);
      lddp->totalsize+=ffd->nFileSizeLow;
    }
  }
  printf (" %s\n", ffd->cFileName);
}

int ldir(int argc, char * const argv[]) {
  /* this code based on the MSDN example "Listing the Files in a Directory" */
  /* no option-processing at the moment */
/* #define _WIN32_WINNT 0x0501 */

  WIN32_FIND_DATA FindFileData;
  HANDLE hFind = NULL;
  char lpDirSpec[MAX_PATH];  /* directory specification */
  char * dir;
  struct ldirdata ldd = {0,0,0};

  if (argc==1) {
    dir=".";
  } else {
    dir=argv[1];
  }
  printf(" Directory of %s\n\n",dir);
  sprintf(lpDirSpec,"%s\\***",dir);

  hFind = FindFirstFile(lpDirSpec, &FindFileData);

  if (hFind == INVALID_HANDLE_VALUE) {
    wperror();
    return -1;
  } else {
    printFile(&FindFileData,&ldd);
    while (FindNextFile(hFind, &FindFileData) != 0) {
      printFile(&FindFileData,&ldd);
    }
    if (GetLastError() == ERROR_NO_MORE_FILES) {
       FindClose(hFind);
    } else {
      wperror();
      return -1;
    }
  }
  printf("%8d File(s) %12d bytes\n",ldd.numfiles,ldd.totalsize);
  printf("%8d Dir(s)\n",ldd.numdirs);
  return 0;
}
#endif

