#include <stdint.h>
#include <stddef.h>
#include <ulib.h>
#include "interp.h"
#include "macros.h"
#include "config.h"

#define LINEBUF_MAX 1024

PID_t PID;
Dev_t ps2kbdev;

Config CONFIG;

static Arg ARGS[] = {
  ARG("-m", ARG_STRING, &CONFIG.modestr),
  ARG("-f", ARG_STRING, &CONFIG.filepath),
  ARG("-logcmds", ARG_BOOL, &CONFIG.logcmds),
  ARG("-rs", ARG_STRING, &CONFIG.runstring),
  ARG_END(),
};

void do_file(char *filepath);

void set_config(void) {
  int32_t ret;
  if ((ret = parse_args(args(), argslen(), ARGS)) < 0) {
    uprintf("Could not parse args: %d\n", ret);
  }

  if (CONFIG.modestr != NULL) {
    if (string_strcmp(CONFIG.modestr, "interactive") == 0) {
      CONFIG.mode = MODE_INTERACTIVE;
    } else if (string_strcmp(CONFIG.modestr, "runfile") == 0) {
      CONFIG.mode = MODE_RUNFILE;
    } else if (string_strcmp(CONFIG.modestr, "runstring") == 0) {
      CONFIG.mode = MODE_RUNSTRING;
    } else {
      LOG(LOG_ERR, "Unknown mode %s\n", CONFIG.modestr);
    }
  } else {
    CONFIG.mode = MODE_RUNFILE;
  }
}

void do_file(char *filepath) {
  fsh_t h = fs_openf(filepath, FS_OF_READ);
  if (h < 0) {
    LOG(LOG_ERR, "Could not open %s: %s\n", filepath, ERRSTRING(h));
    return;
  }

  FsStat statbuf; ZERO(&statbuf);

  fs_stat(filepath, &statbuf);
  if (statbuf.type != FSSTAT_FILE) {
    LOG(LOG_ERR, "%s is not a file (%d)\n", filepath, statbuf.type);
    return;
  }
  uint8_t *buf = umalloc(statbuf.size+1);
  string_memset(buf, 0, statbuf.size+1);

  if (fs_read(h, buf, statbuf.size, 0) < 0) {
    LOG(LOG_ERR, "Could not read %s (%d): %s\n", filepath, h, ERRSTRING(h));
    goto done;
  }

  InterpResult *res;
  bool ok = interp_runstring((char *)buf, &res, CONFIG.mode == MODE_INTERACTIVE);
  if (!ok) {
    uprintf("Interpreter error:\n");
    uprintf("%s\n", res->errmsg);
    goto done;
  }

done:
  ufree(buf);
  fs_closef(h);
}

void do_mode_interactive(void) {
  char linebuf[LINEBUF_MAX];
  size_t cursor;
  for(;;) {
  begin:
    uprintf("tb# ");

    cursor = 0;
    string_memset(linebuf, 0, LINEBUF_MAX);

    uint8_t b = 0;
    for (;;) {
      int32_t key = dev_cmd(&ps2kbdev, DEV_PS2KBDEV_READCH, NULL, 0);
      if (key > 0) {
        b = (uint8_t)key;
        switch (b) {
          case C('C'):
          case 0xE9:
            uprintf("\n");
            goto begin;
            break;
          case C('L'):
            uprintf(ANSIQ_CUR_SET(0, 0));
            uprintf(ANSIQ_SCR_CLR_ALL);
            goto begin;
            break;
        }

        if (b == '\n') {
          break;
        }

        if (string_chr_isascii(b) && b != 0 && cursor < LINEBUF_MAX) {
          linebuf[cursor++] = b;
          uprintf("%c", b);
        }
      } else {
        schedrelease();
      }
    }

    if (cursor < LINEBUF_MAX) {
      linebuf[cursor] = '\0';
    }
    uprintf(ANSIQ_GR_RESET);
    uprintf("\n");
    InterpResult *res;
    if (!interp_runstring(linebuf, &res, CONFIG.mode == MODE_INTERACTIVE)) {
      LOG(LOG_ERR, "%s\n", res->errmsg);
    }
  }
}

void main(void) {
  PID = proc_getpid();

  // short command-like script was supplied
  if (argslen() > 0 && args()[0][0] == '.') {
    do_file("base:/scripts/rc.tb");
    char *path = umalloc(4096);
    usprintf(path, "base:/scripts/%s.tb", args()[1]);
    do_file(path);
    ufree(path);
    return;
  }

  set_config();

  do_file("base:/scripts/rc.tb");

  if (CONFIG.mode == MODE_INTERACTIVE) {
    dev_gethandle(&ps2kbdev, "ps2kbdev");
    dev_cmd(&ps2kbdev, DEV_PS2KBDEV_ATTCHCONS, NULL, 0);
    do_mode_interactive();
  } else if (CONFIG.mode == MODE_RUNFILE) {
    if (CONFIG.filepath == NULL) {
      uprintf("No file provided\n");
      return;
    }
    do_file(CONFIG.filepath);
  } else if (CONFIG.mode == MODE_RUNSTRING) {
    if (CONFIG.runstring == NULL) {
      uprintf("Run string is empty\n");
      return;
    }
    InterpResult *res;
    if (CONFIG.runstring[0] == '\'') {
      CONFIG.runstring[0] = ' ';
    }
    size_t len = string_len(CONFIG.runstring);
    if (CONFIG.runstring[len] == '\'') {
      CONFIG.runstring[len] = ' ';
    }
    if (!interp_runstring(CONFIG.runstring, &res, false)) {
      LOG(LOG_ERR, "%s\n", res->errmsg);
    }
  }
}
