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

extern PID_t PID;
extern Dev_t ps2kbdev;

InterpResult RES;

void tz_init(Tokenizer *tz, char *str) {
  tz->str = str;
  tz->tokens = NULL;
}

void tz_free(Tokenizer *tz) {
  Token *tk, *tktmp;
  LL_FOREACH_SAFE(tz->tokens, tk, tktmp) {
    ufree(tk->str);
    ufree(tk);
  }
  tz->tokens = NULL;
}

#define TZ_MAX_TK 1024

void tz_tokenize(Tokenizer *tz) {
  size_t len = string_len(tz->str);
  size_t i = 0;

  while (i < len && string_chr_isspace(tz->str[i])) {
    i++;
  }

  for (; i < len; i++) {
    if (tz->str[i] == '\'') {
      char *str = umalloc(TZ_MAX_TK);
      string_memset(str, 0, TZ_MAX_TK);
      i++;
      size_t j = 0;
      while (i < len && tz->str[i] != '\'') {
        if (tz->str[i] == '\\') {
          if (i + 1 < len) {
            i++;
            char c;
            switch (tz->str[i]) {
              case 'n':   c = '\n'; break;
              case 't':   c = '\t'; break;
              case 'r':   c = '\r'; break;
              case '\\':  c = '\\'; break;
              case '\'':  c = '\''; break;
              case '"':   c = '"'; break;
              case 'x': {
                if (i + 2 < len) {
                  char buf[3];
                  buf[0] = tz->str[i + 1];
                  buf[1] = tz->str[i + 2];
                  buf[2] = '\0';

                  char *endp;
                  uint8_t b = (uint8_t)string_conv_strtoul(buf, &endp, 16);
                  c = *(char *)&b;

                  i += 2;
                }
              } break;
              default:    c = tz->str[i]; break;
            }
            if (j + 1 < TZ_MAX_TK) {
              str[j++] = c;
            }
          } else {
            if (j + 1 < TZ_MAX_TK) {
              str[j++] = '\\';
            }
          }
        } else {
          if (j + 1 < TZ_MAX_TK) {
            str[j++] = tz->str[i];
          }
        }
        i++;
      }
      Token *tk = umalloc(sizeof(*tk));
      tk->str = str;
      tk->next = NULL;
      LL_APPEND(tz->tokens, tk);
    } else {
      char *tkstr = umalloc(TZ_MAX_TK);
      string_memset(tkstr, 0, TZ_MAX_TK);
      size_t j = 0;
      while (i < len && !string_chr_isspace(tz->str[i]) && tz->str[i] != '\'') {
        if (j + 1 < TZ_MAX_TK) {
          tkstr[j++] = tz->str[i];
        }
        i++;
      }
      Token *tk = umalloc(sizeof(*tk));
      tk->str = tkstr;
      tk->next = NULL;
      LL_APPEND(tz->tokens, tk);
    }
  }
}

void tz_classify(Tokenizer *tz) {
  #define IF_RTCMD(name) if (string_strcmp(tk->str, #name) == 0) { \
      tk->type = TOK_CMD; \
      tk->cmd = &rt_ ## name; \
    }

  Token *tk, *tktmp;
  LL_FOREACH_SAFE(tz->tokens, tk, tktmp) {
    if (tk->str[0] == '\'') {
      tk->type = TOK_STRING;
    }
    else IF_RTCMD(print)
    else IF_RTCMD(mkalias)
    else IF_RTCMD(PID)
    else IF_RTCMD(do)
    else IF_RTCMD(stackpush)
    else IF_RTCMD(stackpop)
    else IF_RTCMD(eachfile)
    else IF_RTCMD(mkaliasbn)
    else IF_RTCMD(setlogcmds)
    else {
      tk->type = TOK_MISC;
    }
  }
}

void tz_expandspecial(Tokenizer *tz) {
  Token *tk, *tktmp;
  LL_FOREACH_SAFE(tz->tokens, tk, tktmp) {
    if (tk->str[0] == '$' && string_len(tk->str) > 1) {
      bool isnum;
      STRING_CHECK_ALL(tk->str + 1, string_chr_isdigit, isnum);
      if (isnum) {
         // this is a special case for command-like scripts
        char *endp;
        unsigned long idx = string_conv_strtoul(tk->str + 1, &endp, 10);
        idx += 2; // skip command itself and indicator / '.'

        ufree(tk->str);
        char *s = idx < argslen() ? args()[idx] : "";
        size_t len = string_len(s);
        char *copy = umalloc(len+1);
        string_memcpy(copy, s, len);
        copy[len] = '\0';
        tk->str = copy;
      } else {
        if (string_strcmp(tk->str, "$stack") == 0) {
          char *s = rtstringv_stackpeek();
          ufree(tk->str);
          if (s == NULL) {
            tk->str = umalloc(1);
            tk->str[0] = '\0';
          } else {
            size_t len = string_len(s);
            char *copy = umalloc(len+1);
            string_memcpy(copy, s, len);
            copy[len] = '\0';
            tk->str = copy;
          }
        } else {
          RtAlias *alias, *aliastmp;
          LL_FOREACH_SAFE(RTALIASES, alias, aliastmp) {
            if (string_strcmp(alias->namebuf, tk->str+1) == 0) {
              ufree(tk->str);
              size_t len = string_len(alias->valbuf)+1;
              tk->str = umalloc(len);
              string_memset(tk->str, 0, len);
              string_memcpy(tk->str, alias->valbuf, string_len(alias->valbuf));
              break;
            }
          }
        }
      }
    }
  }
}

#define LINE_MAX 1024

bool interp_runstring(char *string, InterpResult **res, bool interactive) {
  *res = &RES;
  string_memset(RES.errmsg, 0, sizeof(RES.errmsg));

  rt_init();
  bool ok = true;
  
  char *line = string_tokenizealloc_linecontinue(string, "\n");
  while (line != NULL) {
    if (CONFIG.logcmds) {
      uprintf("+%s\n", line);
    }

    bool skip;
    STRING_CHECK_ALL(line, string_chr_isspace, skip);
    if (skip) {
      ufree(line);
      line = string_tokenizealloc_linecontinue(NULL, "\n");
      continue;
    }

    Tokenizer tz; ZERO(&tz);
    tz_init(&tz, line);

    tz_tokenize(&tz);
    
    tz_classify(&tz);
    tz_expandspecial(&tz);

    bool wait = true;

    if (tz.tokens->str[0] == '&') {
      Token *tk = tz.tokens;
      LL_REMOVE(tz.tokens, tk);
      ufree(tk->str);
      ufree(tk);

      wait = false;
    }

    Token *cmdtk = tz.tokens;
    if (cmdtk->type == TOK_CMD) {
      ok = cmdtk->cmd(cmdtk->next);
      if (!ok) {
        usprintf(RES.errmsg, "cmd %s failed", cmdtk->str);
        goto next;
      }
    } else if (cmdtk->type == TOK_MISC) {
      size_t argslen1;
      Token *argtk, *argtktmp;
      LL_FOREACH_SAFE_IDX(cmdtk->next, argtk, argtktmp, argslen1);
      
      size_t i;
      char **args1 = (char **)umalloc(sizeof(char *) * argslen1);
      LL_FOREACH_SAFE_IDX(cmdtk->next, argtk, argtktmp, i) {
        args1[i] = (char *)umalloc(PROC_ARG_MAX);
        string_memset(args1[i], 0, PROC_ARG_MAX);
        string_memcpy(args1[i], argtk->str, MIN(string_len(argtk->str), PROC_ARG_MAX));
      }

      int32_t app = proc_spawn(cmdtk->str, args1, argslen1);
      if (app < 0) {
        usprintf(RES.errmsg, "Could not run %s: %s\n", cmdtk->str, ERRSTRING(app));
        ok = false;
        goto cleanup;
      }

      proc_run(app);

      if (wait) {
        while(proc_pollstate(app) != 4) {
          if (interactive) {
            int32_t key = dev_cmd(&ps2kbdev, DEV_PS2KBDEV_READCH, NULL, 0);
            if (key > 0 && (uint8_t)key == C('S')) {
              proc_kill(app);
              goto cleanup;
            }
          }
          schedrelease();
        }
      }

      cleanup: {
        for (size_t j = 0; j < argslen1; j++) ufree(args1[j]);
        ufree(args1);
      }
    }

next:
    tz_free(&tz);
    ufree(line);
    line = string_tokenizealloc_linecontinue(NULL, "\n");
  }

  return ok;
}
