#if 0
/*ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â To the extent possible under law, Justine Tunney has waived      â
â all copyright and related or neighboring rights to this file,    â
â as it is written in the following disclaimers:                   â
â   â¢ http://unlicense.org/                                        â
â   â¢ http://creativecommons.org/publicdomain/zero/1.0/            â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ*/
#endif
#include "libc/calls/calls.h"
#include "libc/calls/struct/dirent.h"
#include "libc/calls/struct/rusage.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/timespec.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/fmt/itoa.h"
#include "libc/log/appendresourcereport.internal.h"
#include "libc/log/internal.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/append.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/clock.h"
#include "libc/sysv/consts/dt.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sig.h"
#include "libc/time/time.h"
#include "libc/x/x.h"
#include "third_party/linenoise/linenoise.h"

/**
 * @fileoverview Cosmopolitan Shell
 *
 * This doesn't have script language features like UNBOURNE.COM but it
 * works on Windows and, unlike CMD.EXE, has CTRL-P, CTRL-R, and other
 * GNU Emacs / Readline keyboard shortcuts.
 *
 * One day we'll have UNBOURNE.COM working on Windows but the code isn't
 * very maintainable sadly.
 */

volatile int gotint;

static void OnInterrupt(int sig) {
  gotint = sig;
}

static void AddUniqueCompletion(linenoiseCompletions *c, char *s) {
  size_t i;
  if (!s) return;
  for (i = 0; i < c->len; ++i) {
    if (!strcmp(s, c->cvec[i])) {
      return;
    }
  }
  c->cvec = realloc(c->cvec, ++c->len * sizeof(*c->cvec));
  c->cvec[c->len - 1] = s;
}

static void CompleteFilename(const char *p, const char *q, const char *b,
                             linenoiseCompletions *c) {
  DIR *d;
  char *buf;
  const char *g;
  struct dirent *e;
  if ((buf = malloc(512))) {
    if ((g = memrchr(p, '/', q - p))) {
      *(char *)mempcpy(buf, p, MIN(g - p, 511)) = 0;
      p = ++g;
    } else {
      strcpy(buf, ".");
    }
    if ((d = opendir(buf))) {
      while ((e = readdir(d))) {
        if (!strcmp(e->d_name, ".")) continue;
        if (!strcmp(e->d_name, "..")) continue;
        if (!strncmp(e->d_name, p, q - p)) {
          snprintf(buf, 512, "%.*s%s%s", p - b, b, e->d_name,
                   e->d_type == DT_DIR ? "/" : "");
          AddUniqueCompletion(c, strdup(buf));
        }
      }
      closedir(d);
    }
    free(buf);
  }
}

static void ShellCompletion(const char *p, linenoiseCompletions *c) {
  bool slashed;
  const char *q, *b;
  for (slashed = false, b = p, q = (p += strlen(p)); p > b; --p) {
    if (p[-1] == '/' && p[-1] == '\\') slashed = true;
    if (!isalnum(p[-1]) &&
        (p[-1] != '.' && p[-1] != '_' && p[-1] != '-' && p[-1] != '+' &&
         p[-1] != '[' && p[-1] != '/' && p[-1] != '\\')) {
      break;
    }
  }
  CompleteFilename(p, q, b, c);
}

static char *ShellHint(const char *p, const char **ansi1, const char **ansi2) {
  char *h = 0;
  linenoiseCompletions c = {0};
  ShellCompletion(p, &c);
  if (c.len == 1) {
    h = strdup(c.cvec[0] + strlen(p));
  }
  linenoiseFreeCompletions(&c);
  return h;
}

static char *MakePrompt(char *p) {
  char *s, buf[256];
  if (!gethostname(buf, sizeof(buf))) {
    p = stpcpy(p, "\e[95m");
    if ((s = getenv("USER"))) {
      p = stpcpy(p, s);
      *p++ = '@';
    }
    p = stpcpy(p, buf);
    *p++ = ':';
  }
  p = stpcpy(p, "\e[96m");
  if ((s = getcwd(buf, sizeof(buf)))) {
    p = stpcpy(p, s);
  }
  return stpcpy(p, "\e[0m>: ");
}

int main(int argc, char *argv[]) {
  bool timeit;
  int64_t nanos;
  struct rusage ru;
  struct timespec ts1, ts2;
  char *prog, path[PATH_MAX];
  sigset_t chldmask, savemask;
  int stdoutflags, stderrflags;
  const char *stdoutpath, *stderrpath;
  int n, rc, ws, pid, child, killcount;
  struct sigaction sa, saveint, savequit;
  char *p, *line, **args, *arg, *start, *state, prompt[1024];
  linenoiseSetFreeHintsCallback(free);
  linenoiseSetHintsCallback(ShellHint);
  linenoiseSetCompletionCallback(ShellCompletion);
  MakePrompt(prompt);
  while ((line = linenoiseWithHistory(prompt, "cmd"))) {
    n = 0;
    start = line;
    if (_startswith(start, "time ")) {
      timeit = true;
      start += 5;
    } else {
      timeit = false;
    }
    stdoutpath = 0;
    stderrpath = 0;
    stdoutflags = 0;
    stderrflags = 0;
    args = xcalloc(1, sizeof(*args));
    while ((arg = strtok_r(start, " \t\r\n", &state))) {
      // cmd >>stdout.txt
      if (arg[0] == '>' && arg[1] == '>') {
        stdoutflags = O_WRONLY | O_APPEND | O_CREAT;
        stdoutpath = arg + 2;
      } else if (arg[0] == '>') {
        // cmd >stdout.txt
        stdoutflags = O_WRONLY | O_CREAT | O_TRUNC;
        stdoutpath = arg + 1;
      } else if (arg[0] == '2' && arg[1] == '>' && arg[2] == '>') {
        // cmd 2>>stderr.txt
        stderrflags = O_WRONLY | O_APPEND | O_CREAT;
        stderrpath = arg + 3;
      } else if (arg[0] == '2' && arg[1] == '>') {
        // cmd 2>stderr.txt
        stderrflags = O_WRONLY | O_CREAT | O_TRUNC;
        stderrpath = arg + 2;
      } else {
        // arg
        args = xrealloc(args, (++n + 1) * sizeof(*args));
        args[n - 1] = arg;
        args[n - 0] = 0;
      }
      start = 0;
    }
    if (n > 0) {
      if ((prog = commandv(args[0], path, sizeof(path)))) {

        // let keyboard interrupts kill child and not shell
        gotint = 0;
        killcount = 0;
        sa.sa_flags = 0;
        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sigaction(SIGQUIT, &sa, &savequit);
        sa.sa_handler = OnInterrupt;
        sigaction(SIGINT, &sa, &saveint);
        sigemptyset(&chldmask);
        sigaddset(&chldmask, SIGCHLD);
        sigprocmask(SIG_BLOCK, &chldmask, &savemask);

        // record timestamp
        if (timeit) {
          clock_gettime(CLOCK_REALTIME, &ts1);
        }

        // launch process
        if (!(child = vfork())) {
          if (stdoutpath) {
            close(1);
            open(stdoutpath, stdoutflags, 0644);
          }
          if (stderrpath) {
            close(2);
            open(stderrpath, stderrflags, 0644);
          }
          sigaction(SIGINT, &saveint, 0);
          sigaction(SIGQUIT, &savequit, 0);
          sigprocmask(SIG_SETMASK, &savemask, 0);
          execv(prog, args);
          _Exit(127);
        }

        // wait for process
        for (;;) {
          if (gotint) {
            switch (killcount) {
              case 0:
                // ctrl-c
                // we do nothing
                // terminals broadcast sigint to process group
                rc = 0;
                break;
              case 1:
                // ctrl-c ctrl-c
                // we try sending sigterm
                rc = kill(child, SIGTERM);
                break;
              default:
                // ctrl-c ctrl-c ctrl-c ...
                // we use kill -9 as our last resort
                rc = kill(child, SIGKILL);
                break;
            }
            if (rc == -1) {
              fprintf(stderr, "kill failed: %m\n");
              exit(1);
            }
            ++killcount;
            gotint = 0;
          }
          rc = wait4(0, &ws, 0, &ru);
          if (rc != -1) {
            break;
          } else if (errno == EINTR) {
            errno = 0;
          } else {
            fprintf(stderr, "wait failed: %m\n");
            exit(1);
          }
        }

        // print resource consumption for `time` pseudocommand
        if (timeit) {
          clock_gettime(CLOCK_REALTIME, &ts2);
          if (ts2.tv_sec == ts1.tv_sec) {
            nanos = ts2.tv_nsec - ts1.tv_nsec;
          } else {
            nanos = (ts2.tv_sec - ts1.tv_sec) * 1000000000LL;
            nanos += 1000000000LL - ts1.tv_nsec;
            nanos += ts2.tv_nsec;
          }
          printf("took %,ldÂµs wall time\n", nanos / 1000);
          p = 0;
          AppendResourceReport(&p, &ru, "\n");
          fputs(p, stdout);
          free(p);
        }

        // update prompt to reflect exit status
        p = prompt;
        if (WIFEXITED(ws)) {
          if (WEXITSTATUS(ws)) {
            if (!__nocolor) p = stpcpy(p, "\e[1;31m");
            p = stpcpy(p, "rc=");
            p = FormatInt32(p, WEXITSTATUS(ws));
            if (128 < WEXITSTATUS(ws) && WEXITSTATUS(ws) <= 128 + 32) {
              *p++ = ' ';
              p = stpcpy(p, strsignal(WEXITSTATUS(ws) - 128));
            }
            if (!__nocolor) p = stpcpy(p, "\e[0m");
            *p++ = ' ';
          }
        } else {
          if (!__nocolor) p = stpcpy(p, "\e[1;31m");
          p = stpcpy(p, strsignal(WTERMSIG(ws)));
          if (!__nocolor) p = stpcpy(p, "\e[0m");
          *p++ = ' ';
        }
        MakePrompt(p);

        sigprocmask(SIG_SETMASK, &savemask, 0);
        sigaction(SIGINT, &saveint, 0);
        sigaction(SIGQUIT, &savequit, 0);
      } else {
        fprintf(stderr, "%s: %s: command not found\n", argv[0], args[0]);
      }
    }
    free(line);
    free(args);
  }
}
