#include "parser.h"
#include "arena_alloc.h"
#include "context.h"
#include "interp.h"
#include "mprintf.h"
#include <arena.h>
#include <list.h>
#include <string.h>

static struct parse_rule parse_rules[] = {
    [TOKEN_CLASS_WORD] = {&word_nud, NULL, PREC_NONE},
    [TOKEN_CLASS_OPAREN] = {&oparen_nud, NULL, PREC_NONE},
    [TOKEN_CLASS_CPAREN] = {NULL, NULL, PREC_NONE},
    [TOKEN_CLASS_SEMICOLON] = {NULL, &semicolon_led, PREC_SEQ},
    [TOKEN_CLASS_REDIR] = {NULL, &redir_led, PREC_REDIR},
};

static struct token* get_token (struct list_node_link* link) {
  if (link == NULL)
    return NULL;

  return list_entry (link, struct token, tokens_link);
}

static struct token* advance (struct parser* parser) {
  struct token* token = parser->next;

  if (token != NULL) {
    parser->current = token;
    parser->next = get_token (token->tokens_link.next);
  } else {
    parser->current = NULL;
  }

  return token;
}

static struct ast_node* parse_precedence (struct parser* parser, int precedence) {
  struct token* token = advance (parser);

  if (token == NULL)
    return NULL;

  nud_func_t nud = parse_rules[token->class].nud;

  if (nud == NULL)
    return NULL;

  struct ast_node* left = nud (parser, token);

  while (parser->next && precedence < parse_rules[parser->next->class].precedence) {
    token = advance (parser);
    led_func_t led = parse_rules[token->class].led;

    if (led != NULL)
      left = led (parser, token, left);
  }

  return left;
}

struct ast_node* word_nud (struct parser* parser, struct token* token) {
  struct ast_node* node = arena_malloc (&arena, sizeof (*node));
  node->class = AST_NODE_CLASS_CMD;
  node->u.cmd.name = token->buffer;
  node->u.cmd.arg_count = 0;

  while (parser->next != NULL && parser->next->class == TOKEN_CLASS_WORD) {
    struct token* arg = advance (parser);

    if (node->u.cmd.arg_count < CMD_ARGS_MAX)
      node->u.cmd.args[node->u.cmd.arg_count++] = arg->buffer;
  }

  return node;
}

struct ast_node* oparen_nud (struct parser* parser, struct token* token) {
  (void)token;

  struct ast_node* node = arena_malloc (&arena, sizeof (*node));
  node->class = AST_NODE_CLASS_SUBSHELL;

  node->u.subshell.inner = parse_precedence (parser, PREC_LOWEST);

  if (parser->next != NULL && parser->next->class == TOKEN_CLASS_CPAREN)
    advance (parser);

  return node;
}

struct ast_node* semicolon_led (struct parser* parser, struct token* token, struct ast_node* left) {
  (void)token;

  struct ast_node* node = arena_malloc (&arena, sizeof (*node));
  node->class = AST_NODE_CLASS_SEQ;
  node->u.seq.left = left;
  node->u.seq.right = parse_precedence (parser, PREC_SEQ);

  return node;
}

struct ast_node* redir_led (struct parser* parser, struct token* token, struct ast_node* left) {
  (void)token;

  struct ast_node* node = arena_malloc (&arena, sizeof (*node));
  node->class = AST_NODE_CLASS_REDIR;
  node->u.redir.source = left;

  struct token* next_token = advance (parser);
  if (next_token != NULL && next_token->class == TOKEN_CLASS_WORD)
    node->u.redir.file_path = next_token->buffer;

  return node;
}

void tokenize (struct list_node_link** tokens, const char* text) {
  const char* p = text;

  while (*p) {
    if (isspace (*p)) {
      p++;
      continue;
    }

    if (*p == '(' || *p == ')' || *p == ';' || *p == '>') {
      struct token* token = arena_malloc (&arena, sizeof (*token));
      memset (token, 0, sizeof (*token));

      token->buffer[0] = *p;
      if (*p == '(')
        token->class = TOKEN_CLASS_OPAREN;
      else if (*p == ')')
        token->class = TOKEN_CLASS_CPAREN;
      else if (*p == ';')
        token->class = TOKEN_CLASS_SEMICOLON;
      else if (*p == '>')
        token->class = TOKEN_CLASS_REDIR;

      list_append (*tokens, &token->tokens_link);
      p++;
      continue;
    }

    if (isprint (*p)) {
      struct token* token = arena_malloc (&arena, sizeof (*token));
      memset (token, 0, sizeof (*token));
      size_t i = 0;

      while (*p && !isspace (*p) && *p != '(' && *p != ')' && *p != ';' && *p != '>') {
        if (i < TOKEN_MAX - 1)
          token->buffer[i++] = *p;
        p++;
      }

      token->class = TOKEN_CLASS_WORD;
      list_append (*tokens, &token->tokens_link);
      continue;
    }

    mprintf ("ERROR unknown character '%c'\n", *p);
    p++;
  }
}

void parse_and_execute (struct list_node_link* tokens) {
  struct parser parser;
  parser.current = NULL;
  parser.next = get_token (tokens);

  while (parser.next != NULL) {
    struct ast_node* root = parse_precedence (&parser, PREC_NONE);

    if (root != NULL) {
      struct context context;
      memset (&context, 0, sizeof (context));
      execute (root, &context);

      if (context.strbuf.items != NULL)
        mprintf ("%.*s", (int)context.strbuf.count, context.strbuf.items);
    }
  }
}
