#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

/* Run the command line in ARGV as an external program using fork/exec. */
void run_command_file_and_args (char **argv) {
  char command_copy[128];
  pid_t childpid;
  strcpy (command_copy, argv[0]);
  childpid = fork ();
  if (childpid == 0) {
    if (execvp (command_copy, argv) != 0) {
      perror (command_copy);
      exit (1);
    }
  } else {
    int status;
    waitpid (childpid, &status, 0);
    if (WIFSIGNALED (status)) {
      int signal = WTERMSIG (status);
      printf ("%d (%s)", childpid, command_copy);
      psignal (signal, "");
    }
  }
}

/* Change directory, to the first argument or to the HOME directory if no
 * args.
 */
int builtin_cd (int argc, char **argv) {
  char *dir = 0;
  if (argc > 2) {
    fprintf (stderr, "cd: Too many arguments.\n");
    return 1;
  } else if (argc == 2) {
    dir = argv[1];
  } else if (argc == 1) {
    dir = getenv("HOME");
  }
  if (chdir (dir) < 0)
    perror (dir);
  return 1;
}

/* Type of C functions corresponding to built-in function implementations. */
typedef int (*builtin_funcptr) (int argc, char **argv);

/* Table of built-in functions. */
struct builtin {
  char *name;
  builtin_funcptr impl;
} builtins[] = {
  { "cd", builtin_cd },
  { 0, 0 }
};

/* Returns the "struct builtin" corresponding to COMMAND, or zero if
 * it is not a recognizable built-in function.
 */
struct builtin *find_builtin_command (char *command) {
  struct builtin *p;
  for (p = &builtins[0]; p->name; ++p)
    if (strcmp (p->name, command) == 0)
      return p;
  return 0;
}

/* Command-line parser states. */
static const int FINDWORD = 0;
static const int FINDDELIM = 1;

/* Parse ORIG_COMMAND_LINE into argc/argv-style structure and run it,
 * either as a built-in function or as an external program.
 */
void parse_and_run_command (char *orig_command_line) {
  char *newargv[128], parser_tmp[128];
  int i, l, state, newargc = 0;
  struct builtin *b;
  /* Copy command line into parser_tmp and parse it into newargv.
   * Keep count of the arguments in newargc.
   */
  strcpy (parser_tmp, orig_command_line);
  l = strlen (parser_tmp);
  state = FINDWORD;
  for (i = 0; i < l; ++i) {
    if (state == FINDWORD && !isspace (parser_tmp[i])) {
      state = FINDDELIM;
      newargv[newargc++] = &parser_tmp[i];
    } else if (state == FINDWORD && isspace (parser_tmp[i])) {
      parser_tmp[i] = '\0';
    } else if (state == FINDDELIM && !isspace (parser_tmp[i])) { 
      /* nothing */
    } else if (state == FINDDELIM && isspace (parser_tmp[i])) {
      state = FINDWORD;
      parser_tmp[i] = '\0';
    }
  }
  /* Null-terminate the list. */
  newargv[newargc] = 0;
  /* If command line was empty, quit now. */
  if (newargc < 1)
    return;
  /* Run the built-in or the command. */
  if ((b = find_builtin_command (newargv[0])) != 0)
    b->impl (newargc, newargv);
  else 
    run_command_file_and_args (newargv);
}

int main (int argc, char **argv) {
  char buf[128];
  char cwd[128];
  int interactive = 1;
  FILE *fp = stdin;

  /* Are we running a script? If so, open it and set FP and INTERACTIVE. */
  if (argc > 1) {
    fp = fopen (argv[1], "r");
    if (!fp) {
      perror (argv[1]);
      return 1;
    }
    interactive = 0;
  }
  /* Process commands from FP until it is exhausted. */
  do {
    if (interactive)
      printf ("%s> ", getcwd (cwd, 128));
    if (fgets (buf, 128, fp))
      parse_and_run_command (buf);
  } while (!feof (fp));
  return 0;
}
