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

RtStringV *RTSTRINGV_STACK = NULL;

void rtstringv_stackpushcopy(char *s, size_t len) {
  char *c = umalloc(len+1);
  string_memcpy(c, s, len);
  c[len] = '\0';
  RtStringV *v = umalloc(sizeof(*v));
  v->data = c;
  LL_APPEND(RTSTRINGV_STACK, v);
}

char *rtstringv_stackpop(void) {
  RtStringV *v;
  LL_BACK(RTSTRINGV_STACK, v);
  if (v == NULL) {
    return NULL;
  }
  LL_REMOVE(RTSTRINGV_STACK, v);
  char *d = v->data;
  ufree(v);
  return d;
}

char *rtstringv_stackpeek(void) {
  RtStringV *v;
  LL_BACK(RTSTRINGV_STACK, v);
  if (v == NULL) {
    return NULL;
  }
  return v->data;
}

extern PID_t PID;

RtCmd *RTCMDS = NULL;
RtAlias *RTALIASES = NULL;

#define RTCMD(name, _fn) \
  do { \
    RtCmd *_cmd = umalloc(sizeof(*_cmd)); \
    _cmd->cmdname = (name); \
    _cmd->fn = (_fn); \
    LL_APPEND(RTCMDS, _cmd); \
  } while(0)

bool rt_print(Token *tks) {
  Token *tk, *tktmp;
  LL_FOREACH_SAFE(tks, tk, tktmp) {
    uprintf("%s", tk->str);
    if (tk->next != NULL) {
      uprintf(" ");
    }
  }
  return true;
}

bool rt_mkalias(Token *tks) {
  RtAlias *alias = umalloc(sizeof(*alias));
  string_memset(alias, 0, sizeof(*alias));

  size_t i;
  Token *tk, *tktmp;
  LL_FOREACH_SAFE_IDX(tks, tk, tktmp, i) {
    if (i == 0) {
      string_memcpy(alias->namebuf, tk->str, MIN(string_len(tk->str), RTALIAS_NAMEBUF_MAX));
    } else if (i == 1) {
      string_memcpy(alias->valbuf, tk->str, MIN(string_len(tk->str), RTALIAS_VALBUF_MAX));
    }
  }

  LL_APPEND(RTALIASES, alias);
  return true;
}

bool rt_PID(Token *tks) {
  (void)tks;
  uprintf("%lu\n", PID);
  return true;
}

bool rt_do(Token *tks) {
  bool ok = true;

  char *prepended_args[] = { "-m", "runstring", "-rs" };

  #define SUBSH_MAX_STRING PROC_ARG_MAX
  char *s = umalloc(SUBSH_MAX_STRING);
  string_memset(s, 0, SUBSH_MAX_STRING);
  size_t cursor = 0;
  Token *argtk, *argtktmp;
  LL_FOREACH_SAFE(tks, argtk, argtktmp) {
    size_t len = string_len(argtk->str);
    string_memcpy(&s[cursor], argtk->str, len);
    cursor += len;
    s[cursor] = ' ';
    cursor++;
  }
  
  char **args1 = umalloc(sizeof(char *) * (ARRLEN(prepended_args) + 1));
  for (size_t i = 0; i < ARRLEN(prepended_args); i++) {
    args1[i] = umalloc(PROC_ARG_MAX);
    string_memset(args1[i], 0, PROC_ARG_MAX);
    string_strcpy(args1[i], prepended_args[i]);
  }

  args1[ARRLEN(prepended_args)] = umalloc(PROC_ARG_MAX);
  string_strcpy(args1[ARRLEN(prepended_args)], s);

  int32_t app = proc_spawn("base:/bin/tb", args1, ARRLEN(prepended_args)+1);
  if (app < 0) {
    ok = false;
    goto done;
  }

  StringBuffer outbuf;
  stringbuffer_init(&outbuf);

  ipc_pipemake(10);
  ipc_pipeconnect(app, 0, PID, 10);

  proc_run(app);

  while (proc_pollstate(app) != 4) {
    int32_t r;
    char buf[100];
    string_memset(buf, 0, sizeof(buf));
   
    r = ipc_piperead(app, 0, (uint8_t *const)buf, sizeof(buf)-1);
    if (r > 0) {
      stringbuffer_appendcstr(&outbuf, buf);
    }
  }

  ipc_pipedelete(10);

  rtstringv_stackpushcopy(outbuf.data, outbuf.count);

  stringbuffer_free(&outbuf);

done:
  for (size_t i = 0; i < ARRLEN(prepended_args)+1; i++) {
    ufree(args1[i]);
  }
  ufree(s);

  return ok;
}

bool rt_stackpush(Token *tks) {
  Token *tk, *tktmp;
  LL_FOREACH_SAFE(tks, tk, tktmp) {
    rtstringv_stackpushcopy(tk->str, string_len(tk->str));
  }
  return true;
}

bool rt_stackpop(Token *tks) {
  (void)tks;
  char *s = rtstringv_stackpop();
  if (s) ufree(s);
  return true;
}

typedef struct EachFileSkip {
  struct EachFileSkip *next;
  char *filename;
} EachFileSkip;

bool rt_eachfile(Token *tks) {
  EachFileSkip *skips = NULL;

  Token *tk = tks;
  char *dirpath;

  while (tk && tk->str[0] == '!') {
    EachFileSkip *skip = (EachFileSkip *)umalloc(sizeof(*skip));
    skip->filename = tk->str + 1;
    LL_APPEND(skips, skip);
    tk = tk->next;
  }

  if (tk != NULL) {
    dirpath = tk->str;
    tk = tk->next;
  }

  FsStat statbuf; ZERO(&statbuf);
  if (fs_stat(dirpath, &statbuf) != E_OK || statbuf.type != FSSTAT_DIR) {
    return false;
  }

  FsDirent *dirents = (FsDirent *)umalloc(statbuf.size * sizeof(*dirents));

  for (size_t i = 0; i < statbuf.size; i++) {
    fs_fetchdirent(dirpath, &dirents[i], i);
    FsDirent *dirent = &dirents[i];

    if (string_strcmp(dirent->name, ".") == 0 || string_strcmp(dirent->name, "..") == 0) {
      continue;
    }
    
    EachFileSkip *skip1, *skip1tmp;
    LL_FOREACH_SAFE(skips, skip1, skip1tmp) {
      if (string_strcmp(skip1->filename, dirent->name) == 0) {
        goto nextiter;
      }
    }

    char tmpbuf[1024]; ZERO(tmpbuf);
    string_strcpy(tmpbuf, dirpath);
    if (dirpath[string_len(dirpath) - 1] != '/') {
      string_combine(tmpbuf, "/");
    }
    string_combine(tmpbuf, dirent->name);
    
    #define EACHFILE_MAX_STRING 1024
    char *s = umalloc(EACHFILE_MAX_STRING);
    string_memset(s, 0, EACHFILE_MAX_STRING);
    size_t cursor = 0;
    Token *argtk, *argtktmp;
    LL_FOREACH_SAFE(tk, argtk, argtktmp) {
      if (string_strcmp(argtk->str, "&EF-ELEM") == 0) {
        size_t len = string_len(tmpbuf);
        string_memcpy(&s[cursor], tmpbuf, len);
        cursor += len;
        s[cursor] = ' ';
        cursor++;
      } else {
        size_t len = string_len(argtk->str);
        string_memcpy(&s[cursor], argtk->str, len);
        cursor += len;
        s[cursor] = ' ';
        cursor++;
      }
    }

    InterpResult save; string_memcpy(&save, &RES, sizeof(save));
    InterpResult *res;
    interp_runstring(s, &res, false);
    string_memcpy(&RES, &save, sizeof(save));
    
    /* ufree(s); */

nextiter:
  }

  ufree(dirents);

  EachFileSkip *skip1, *skip1tmp;
  LL_FOREACH_SAFE(skips, skip1, skip1tmp) {
    ufree(skip1);
  }

  return true;
}

bool rt_mkaliasbn(Token *tks) {
  Token *tk = tks;

  if (tk == NULL) {
    return false;
  }

  RtAlias *alias = umalloc(sizeof(*alias));
  string_memset(alias, 0, sizeof(*alias));

  char *basename = (char *)path_basename(tk->str);
  string_memcpy(alias->namebuf, basename, MIN(string_len(basename), RTALIAS_NAMEBUF_MAX));
  string_memcpy(alias->valbuf, tk->str, MIN(string_len(tk->str), RTALIAS_VALBUF_MAX));

  LL_APPEND(RTALIASES, alias);
  return true;
}

bool rt_setlogcmds(Token *tks) {
  Token *tk = tks;

  if (tk == NULL) {
    return false;
  }

  bool set;

  if (string_strcmp(tk->str, "yes") == 0) {
    set = true;
  } else if (string_strcmp(tk->str, "no") == 0) {
    set = false;
  } else {
    return false;
  }

  CONFIG.logcmds = set;

  return true;
}

void rt_init(void) {
  RTCMD("print", &rt_print);
  RTCMD("mkalias", &rt_mkalias);
  RTCMD("PID", &rt_PID);
  RTCMD("do", &rt_do);
  RTCMD("stackpush", &rt_stackpush);
  RTCMD("stackpop", &rt_stackpop);
  RTCMD("eachfile", &rt_eachfile);
  RTCMD("mkaliasbn", &rt_mkaliasbn);
  RTCMD("setlogcmds", &rt_setlogcmds);
}
