#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <iostream.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include "mystring.h"
#include "array.h"
#include "list.h"
#include "cs697lsh.h"
#include "history.h"

void cmd::cmdexec()
{
  if(is_internal(args[0]) && !pipeto)
  {
    do_internal();
    return;
  }

  pid_t pid;
  if((pid = fork()) == -1) {
    cerr << "Forking error.\n";
    return;
  } else if(pid != 0) {
    if(!background)
      waitpid(pid, 0, 0);
    cout.flush();
    return;
  }

  signal(SIGINT,  SIG_DFL);
  signal(SIGCHLD, SIG_DFL);
  signal(SIGPIPE, SIG_DFL);

  cmd *c = this;
  while(c->pipeto) c=c->pipeto;

  c->doexec();
  exit(0);
}

void cmd::doexec()
{
  if(pipefrom)
  {
    int pipes[2];
    if(pipe(pipes))
    {
      cerr << "Error generating pipe.\n";
      return;
    }

    pid_t p;
    if((p = fork()) == -1)
    {
      cerr << "Fork error.\n";
      close(pipes[0]);close(pipes[1]);
      return;
    } else if(p == 0) {
      if(dup2(pipes[1], STDOUT_FILENO)==-1)
      {
        cerr << "Error piping stdout.\n";
        close(pipes[0]);close(pipes[1]);
        exit(0);
      }
      close(pipes[0]);close(pipes[1]);
      pipefrom->doexec();
      exit(0);
    } else {
      if(dup2(pipes[0], STDIN_FILENO)==-1)
      {
        cerr << "Error piping stdin.\n";
        close(pipes[0]);close(pipes[1]);
        exit(0);
      }
      close(pipes[0]);close(pipes[1]);
    }
  }

  cerr.flush(); cout.flush();

  if(stdin_redir_type) {
    int newin;
    if((newin = open(stdin_redir.getstring(), O_RDONLY)) < 0)
    {
      cerr << "Error redirecting stdin.\n";
      exit(1);
    }
    if(dup2(newin, STDIN_FILENO) < 0)
    {
      cerr << "Error redirecting stdin.\n";
      exit(1);
    }
    close(newin);
  }

  if(stdout_redir_type) {
    int newout;
    if((newout = open(stdout_redir.getstring(), O_CREAT | O_WRONLY | ((stdout_redir_type == 2) ? O_APPEND : 0), 0644)) < 0)
    {
      cerr << "Error redirecting stdout.\n";
      exit(1);
    }
    if(dup2(newout, STDOUT_FILENO) < 0)
    {
      cerr << "Error redirecting stdout.\n";
      exit(1);
    }
    close(newout);
  }

  if(is_internal(args[0]))
  {
    do_internal();
    return;
  }

  char **argv = buildargv();
  execvp(args[0].getstring(), argv);
  free(argv);
  cerr << args[0] << ": ";
  switch (errno) {
    case ENOENT:
      cerr << "Command not found.\n";
      break;
    case EACCES:
    case EPERM:
      cerr << "Permission denied.\n";
      break;
    default:
      cerr << "Unable to execute.\n";
  }
  exit(1);
}

void cmd::do_internal()
{
  if( args[0] == "logout" || args[0] == "exit" )
    do_exit();

  if( args[0] == "history" )
  {
    extern History cmdhist;
    cmdhist.printhistory();
    return;
  }
  if( args[0] == "cd" )
  {
    do_chdir();
    return;
  }
  if( args[0] == "mkdir" )
  {
    do_mkdir();
    return;
  }
  if( args[0] == "which" )
  {
    do_which();
    return;
  }
  if( args[0] == "kill" )
  {
    do_kill();
    return;
  }
  if( args[0] == "setenv" )
  {
    do_setenv();
    return;
  }
}

void cmd::do_exit()
{
  cout << "exit\n";
  exit(0);
}

void cmd::do_which()
{
  char *p;
  for(int i = 1 ; i<args.len(); i++)
  {
    if(is_internal(args[i]))
      cout << args[i] << ": Builtin shell command.\n";
    else if(p = findpath(args[i].getstring()))
      cout << p << "\n";
    else
      cout << args[i] << ": Command not found.\n";
  }
}

void cmd::do_chdir()
{
  if(args.len() > 2) {
    cerr << "cd: Too many arguments.\n";
  } else if(args.len() == 1) {
    char *path;
    if(!(path=getenv("HOME"))) {
      cerr << "cd: Unable to determine where your home is.\n";
    } else if(chdir(path)) {
      cerr << "cd: ";
      switch (errno) {
        case ENOENT:
          cerr << "Directory not found.\n";
          break;
        case EPERM:
          cerr << "Permission denied.\n";
          break;
        default:
          cerr << "Error changing directories.\n";
      }
    }
  } else {
    if(chdir(args[1].getstring())) {
      cerr << "cd: ";
      switch (errno) {
        case ENOENT:
          cerr << "Directory not found.\n";
          break;
        case EPERM:
          cerr << "Permission denied.\n";
          break;
        default:
          cerr << "Error changing directories.\n";
      }
    }
  }
}

void cmd::do_kill()
{
  if(args.len() < 3) {
    cerr << "kill: Too few arguments.\n";
    return;
  }
  int sig = SIGTERM;
  pid_t pid;
  if(args[1].len() < 2 || args[1].getstring()[0] != '-')
  {
    cerr << "Usage: kill -signal pid [pid ...]\n       <signal> must be an integer value.\n";
    return;
  }
  sig = atoi(args[1].getstring()+1);
  if(!sig)
  {
    cerr << "kill: Invalid signal.\n";
    return;
  }
  pid = atoi(args[2].getstring());
  if(kill(pid, sig))
    switch (errno) {
      case EINVAL:
        cerr << "kill: Invalid signal.\n";
        break;
      case ESRCH:
        cerr << "kill: No such process.\n";
        break;
      case EPERM:
        cerr << "kill: Permission denied..\n";
        break;
      default:
        cerr << "kill: Error sending signal.\n";
        break;
    }
}

void cmd::do_setenv()
{
  if(args.len() == 1) {
    extern char **environ;
    char **cur=environ;
    while(*cur)
    {
      cout << *cur << "\n";
      cur++;
    }
    return;
  } else if(args.len() < 3) {
    cerr << "setenv: Too few arguments.\n";
    return;
  }
  // Ok. Here I'm a little annoyed. setenv is a NICE function. I like it.
  // Emcity doesn't have it. Grrr.
  // thus, my ONE line of code has become 5.
  //  if(setenv(args[1].getstring(), args[2].getstring(), 1))

  char *newstr = (char*)malloc(args[1].len()+1+args[2].len()+1);
  strcpy(newstr, args[1].getstring());
  strcat(newstr, "=");
  strcat(newstr, args[2].getstring());
  if(putenv(newstr))
    cerr << "setenv: Unable to modify environment.\n";
}

void cmd::do_mkdir()
{
  if(args.len() != 2)
  {
    cerr << "Usage: mkdir <directory name>\n";
    return;
  }
  if(mkdir(args[1].getstring(),  0777))
    switch (errno) {
      case EEXIST:
        cerr << "mkdir: Directory already exists.\n";
        break;
      case EFAULT:
        cerr << "mkdir: Directory already exists.\n";
        break;
      case EACCES:
      case EROFS:
        cerr << "mkdir: Permission denied.\n";
        break;
      case ENAMETOOLONG:
        cerr << "mkdir: Directory name too long.\n";
        break;
      case ENOSPC:
        cerr << "mkdir: No space available.\n";
        break;
      case ENOENT:
      case ENOTDIR:
        cerr << "mkdir: Directory not found.\n";
        break;
      default:
        cerr << "mkdir: Error creating directory.\n";
        break;
    }
}

int cmd::is_internal(String command)
{
  return command == "logout" ||
         command == "exit" ||
         command == "history" ||
         command == "cd" ||
         command == "mkdir" ||
         command == "which" ||
         command == "kill" ||
         command == "setenv" ;
}

char *cmd::findpath(const char *file)
{
  static char sbuf[MAXPATHLEN];
  char *s, *path;

  if((file[0] == '.') || (file[0] == '/'))
  {
    strcpy(sbuf, file);
    return sbuf;
  }
  path = strdup(getenv("PATH"));
  if(!(s=strtok(path,":")))
  {
    free(path);
    return 0;
  }

  strcat(strcat(strcpy(sbuf, s), "/"), file);
  if(is_plusx(sbuf))
  {
    free(path);
    return sbuf;
  }

  while(s=strtok(NULL, ":"))
  {
    strcat(strcat(strcpy(sbuf, s), "/"), file);
    if(is_plusx(sbuf))
    {
      free(path);
      return sbuf;
    }
  }

  return 0;
}

int cmd::is_plusx(const char *file)
{
  struct stat st;
  uid_t uid = geteuid();
  gid_t gid = getegid();

  if(stat(file, &st) == -1)
    return 0;

  if(uid == st.st_uid)
  {  return st.st_mode & S_IXUSR; }
  else if (gid == st.st_gid)
  {  return st.st_mode & S_IXGRP; }
  else
  {  return st.st_mode & S_IXOTH; }
}

char **cmd::buildargv()
{
  char **argv;

  argv = (char**)malloc(sizeof(char*) * (args.len() + 1));

  for(int i = 0; i < args.len(); i++)
    argv[i] = strdup(args[i].getstring());

  argv[args.len()] = 0;

  return argv;
}

