#include <rlang.h>
#include "parse.h"


const struct r_op_precedence r_ops_precedence[R_OP_MAX] = {
  [R_OP_NONE]           = { .power =   0,  .assoc =  0,  .unary = false,  .delimited = false },
  [R_OP_BREAK]          = { .power =   1,  .assoc =  0,  .unary = false,  .delimited =  true },
  [R_OP_NEXT]           = { .power =   1,  .assoc =  0,  .unary = false,  .delimited =  true },
  [R_OP_FUNCTION]       = { .power =   5,  .assoc =  1,  .unary =  true,  .delimited = false },
  [R_OP_QUESTION]       = { .power =  10,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_QUESTION_UNARY] = { .power =  10,  .assoc = -1,  .unary =  true,  .delimited = false },
  [R_OP_WHILE]          = { .power =  20,  .assoc = -1,  .unary = false,  .delimited =  true },
  [R_OP_FOR]            = { .power =  20,  .assoc = -1,  .unary = false,  .delimited =  true },
  [R_OP_REPEAT]         = { .power =  20,  .assoc = -1,  .unary = false,  .delimited =  true },
  [R_OP_IF]             = { .power =  30,  .assoc =  1,  .unary = false,  .delimited =  true },
  [R_OP_ASSIGN1]        = { .power =  40,  .assoc =  1,  .unary = false,  .delimited = false },
  [R_OP_ASSIGN2]        = { .power =  40,  .assoc =  1,  .unary = false,  .delimited = false },
  [R_OP_COLON_EQUAL]    = { .power =  40,  .assoc =  1,  .unary = false,  .delimited = false },
  [R_OP_ASSIGN_EQUAL]   = { .power =  50,  .assoc =  1,  .unary = false,  .delimited = false },
  [R_OP_TILDE]          = { .power =  60,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_TILDE_UNARY]    = { .power =  60,  .assoc = -1,  .unary =  true,  .delimited = false },
  [R_OP_OR1]            = { .power =  70,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_OR2]            = { .power =  70,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_AND1]           = { .power =  80,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_AND2]           = { .power =  80,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_BANG1]          = { .power =  90,  .assoc = -1,  .unary =  true,  .delimited = false },
  [R_OP_BANG3]          = { .power =  90,  .assoc = -1,  .unary =  true,  .delimited = false },
  [R_OP_GREATER]        = { .power = 100,  .assoc =  0,  .unary = false,  .delimited = false },
  [R_OP_GREATER_EQUAL]  = { .power = 100,  .assoc =  0,  .unary = false,  .delimited = false },
  [R_OP_LESS]           = { .power = 100,  .assoc =  0,  .unary = false,  .delimited = false },
  [R_OP_LESS_EQUAL]     = { .power = 100,  .assoc =  0,  .unary = false,  .delimited = false },
  [R_OP_EQUAL]          = { .power = 100,  .assoc =  0,  .unary = false,  .delimited = false },
  [R_OP_NOT_EQUAL]      = { .power = 100,  .assoc =  0,  .unary = false,  .delimited = false },
  [R_OP_PLUS]           = { .power = 110,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_MINUS]          = { .power = 110,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_TIMES]          = { .power = 120,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_RATIO]          = { .power = 120,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_MODULO]         = { .power = 130,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_SPECIAL]        = { .power = 130,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_COLON1]         = { .power = 140,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_BANG2]          = { .power = 150,  .assoc = -1,  .unary =  true,  .delimited = false },
  [R_OP_PLUS_UNARY]     = { .power = 150,  .assoc = -1,  .unary =  true,  .delimited = false },
  [R_OP_MINUS_UNARY]    = { .power = 150,  .assoc = -1,  .unary =  true,  .delimited = false },
  [R_OP_HAT]            = { .power = 160,  .assoc =  1,  .unary = false,  .delimited = false },
  [R_OP_DOLLAR]         = { .power = 170,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_AT]             = { .power = 170,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_COLON2]         = { .power = 180,  .assoc =  0,  .unary = false,  .delimited = false },
  [R_OP_COLON3]         = { .power = 180,  .assoc =  0,  .unary = false,  .delimited = false },
  [R_OP_PARENTHESES]    = { .power = 190,  .assoc =  0,  .unary =  true,  .delimited =  true },
  [R_OP_BRACKETS1]      = { .power = 190,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_BRACKETS2]      = { .power = 190,  .assoc = -1,  .unary = false,  .delimited = false },
  [R_OP_BRACES]         = { .power = 200,  .assoc =  0,  .unary = false,  .delimited =  true },
  [R_OP_EMBRACE]        = { .power = 200,  .assoc =  0,  .unary = false,  .delimited =  true }
};

enum r_operator r_which_operator(r_obj* call) {
  if (r_typeof(call) != R_TYPE_call) {
    return R_OP_NONE;
  }

  r_obj* head = r_node_car(call);
  if (r_typeof(head) != R_TYPE_symbol) {
    return R_OP_NONE;
  }

  const char* name = r_sym_c_string(head);
  int len = strlen(name);
  bool is_unary = r_node_cddr(call) == r_null;

  switch (name[0]) {
  case 'b':
    if (strcmp(name, "break") == 0) {
      return R_OP_BREAK;
    } else {
      goto none;
    }
  case 'f':
    if (strcmp(name, "for") == 0) {
      return R_OP_FOR;
    } else if (strcmp(name, "function") == 0) {
      return R_OP_FUNCTION;
    } else {
      goto none;
    }
  case 'i':
    if (strcmp(name, "if") == 0) {
      return R_OP_IF;
    } else {
      goto none;
    }
  case 'n':
    if (strcmp(name, "next") == 0) {
      return R_OP_NEXT;
    } else {
      goto none;
    }
  case 'r':
    if (strcmp(name, "repeat") == 0) {
      return R_OP_REPEAT;
    } else {
      goto none;
    }
  case 'w':
    if (strcmp(name, "while") == 0) {
      return R_OP_WHILE;
    } else {
      goto none;
    }

  case '?':
    if (len == 1) {
      if (is_unary) {
        return R_OP_QUESTION_UNARY;
      } else {
        return R_OP_QUESTION;
      }
    } else {
      goto none;
    }

  case '<':
    switch (len) {
    case 1:
      return R_OP_LESS;
    case 2:
      switch (name[1]) {
      case '-': return R_OP_ASSIGN1;
      case '=': return R_OP_LESS_EQUAL;
      default: goto none;
      }
    case 3:
      if (name[1] == '<' && name[2] == '-') {
        return R_OP_ASSIGN2;
      } else {
        goto none;
      }
    default:
      goto none;
    }

  case '>':
    switch (len) {
    case 1:
      return R_OP_GREATER;
    case 2:
      if (name[1] == '=') {
        return R_OP_GREATER_EQUAL;
      } else {
        goto none;
      }
    default:
      goto none;
    }

  case '=':
    switch (len) {
    case 1:
      return R_OP_ASSIGN_EQUAL;
    case 2:
      if (name[1] == '=') {
        return R_OP_EQUAL;
      } else {
        goto none;
      }
    default:
      goto none;
    }

  case ':':
    switch (len) {
    case 1:
      return R_OP_COLON1;
    case 2:
      switch (name[1]) {
      case '=': return R_OP_COLON_EQUAL;
      case ':': return R_OP_COLON2;
      default: goto none;
      }
    case 3:
      if (name[1] == ':' && name[2] == ':') {
        return R_OP_COLON3;
      } else {
        goto none;
      }
    default: goto none;
    }

  case '~':
    if (len == 1) {
      if (is_unary) {
        return R_OP_TILDE_UNARY;
      } else {
        return R_OP_TILDE;
      }
    } else {
      goto none;
    }

  case '|':
    switch (len) {
    case 1:
      return R_OP_OR1;
    case 2:
      if (name[1] == '|') {
        return R_OP_OR2;
      } else {
        goto none;
      }
    default:
      goto none;
    }

  case '&':
    switch (len) {
    case 1:
      return R_OP_AND1;
    case 2:
      if (name[1] == '&') {
        return R_OP_AND2;
      } else {
        goto none;
      }
    default:
      goto none;
    }

  case '!':
    switch (len) {
    case 1:
      return R_OP_BANG1;
    case 2:
      switch (name[1]) {
      case '!': return R_OP_BANG2;
      case '=': return R_OP_NOT_EQUAL;
      default: goto none;
      }
    case 3:
      if (name[1] == '!' && name[2] == '!') {
        return R_OP_BANG3;
      } else {
        goto none;
      }
    default:
      goto none;
    }

  case '+':
    if (len == 1) {
      if (is_unary) {
        return R_OP_PLUS_UNARY;
      } else {
        return R_OP_PLUS;
      }
    } else {
      goto none;
    }

  case '-':
    if (len == 1) {
      if (is_unary) {
        return R_OP_MINUS_UNARY;
      } else {
        return R_OP_MINUS;
      }
    } else {
      goto none;
    }

  case '*':
    if (len == 1) {
      return R_OP_TIMES;
    } else {
      goto none;
    }

  case '/':
    if (len == 1) {
      return R_OP_RATIO;
    } else {
      goto none;
    }

  case '%':
    switch (len) {
    case 1:
      goto none;
    case 2:
      if (name[1] == '%') {
        return R_OP_MODULO;
      } else {
        goto none;
      }
    default:
      if (name[len - 1] == '%') {
        return R_OP_SPECIAL;
      } else {
        goto none;
      }
    }

  case '^':
    if (len == 1) {
      return R_OP_HAT;
    } else {
      goto none;
    }
  case '$':
    if (len == 1) {
      return R_OP_DOLLAR;
    } else {
      goto none;
    }
  case '@':
    if (len == 1) {
      return R_OP_AT;
    } else {
      goto none;
    }
  case '(':
    if (len == 1) {
      return R_OP_PARENTHESES;
    } else {
      goto none;
    }

  case '[':
    switch (len) {
    case 1:
      return R_OP_BRACKETS1;
    case 2:
      if (name[1] == '[') {
        return R_OP_BRACKETS2;
      } else {
        goto none;
      }
    default:
      goto none;
    }

  case '{':
    if (len == 1) {
      r_obj* cadr = r_node_cadr(call);

      if (r_length(call) == 2 &&
          r_is_call(cadr, "{") &&
          r_length(cadr) == 2 &&
          r_typeof(r_node_cadr(cadr)) == R_TYPE_symbol) {
        return R_OP_EMBRACE;
      } else {
        return R_OP_BRACES;
      }
    } else {
      goto none;
    }

  none:
  default:
    return R_OP_NONE;
  }
}

const char* r_op_as_c_string(enum r_operator op) {
  switch (op) {
  case R_OP_NONE:           return "";
  case R_OP_BREAK:          return "break";
  case R_OP_NEXT:           return "next";
  case R_OP_WHILE:          return "while";
  case R_OP_FOR:            return "for";
  case R_OP_REPEAT:         return "repeat";
  case R_OP_IF:             return "if";
  case R_OP_FUNCTION:       return "function";
  case R_OP_QUESTION:       return "?";
  case R_OP_QUESTION_UNARY: return "?unary";
  case R_OP_ASSIGN1:        return "<-";
  case R_OP_ASSIGN2:        return "<<-";
  case R_OP_ASSIGN_EQUAL:   return "=";
  case R_OP_COLON_EQUAL:    return ":=";
  case R_OP_TILDE:          return "~";
  case R_OP_TILDE_UNARY:    return "~unary";
  case R_OP_OR1:            return "|";
  case R_OP_OR2:            return "||";
  case R_OP_AND1:           return "&";
  case R_OP_AND2:           return "&&";
  case R_OP_BANG1:          return "!";
  case R_OP_BANG3:          return "!!!";
  case R_OP_GREATER:        return ">";
  case R_OP_GREATER_EQUAL:  return ">=";
  case R_OP_LESS:           return "<";
  case R_OP_LESS_EQUAL:     return "<=";
  case R_OP_EQUAL:          return "==";
  case R_OP_NOT_EQUAL:      return "!=";
  case R_OP_PLUS:           return "+";
  case R_OP_MINUS:          return "-";
  case R_OP_TIMES:          return "*";
  case R_OP_RATIO:          return "/";
  case R_OP_MODULO:         return "%%";
  case R_OP_SPECIAL:        return "special";
  case R_OP_COLON1:         return ":";
  case R_OP_BANG2:          return "!!";
  case R_OP_PLUS_UNARY:     return "+unary";
  case R_OP_MINUS_UNARY:    return "-unary";
  case R_OP_HAT:            return "^";
  case R_OP_DOLLAR:         return "$";
  case R_OP_AT:             return "@";
  case R_OP_COLON2:         return "::";
  case R_OP_COLON3:         return ":::";
  case R_OP_PARENTHESES:    return "(";
  case R_OP_BRACKETS1:      return "[";
  case R_OP_BRACKETS2:      return "[[";
  case R_OP_BRACES:         return "{";
  case R_OP_EMBRACE:        return "{{";
  case R_OP_MAX:            r_abort("Unexpected `enum r_operator` value");
  }

  // Silence mistaken noreturn warning on GCC
  r_abort("Never reached");
}

bool op_has_precedence_impl(enum r_operator x, enum r_operator parent, int side) {
  if (x > R_OP_MAX || parent > R_OP_MAX) {
    r_abort("Internal error: `enum r_operator` out of bounds");
  }
  if (x == R_OP_NONE) {
    return true;
  }
  if (parent == R_OP_NONE) {
    return true;
  }

  struct r_op_precedence x_info = r_ops_precedence[x];
  struct r_op_precedence y_info = r_ops_precedence[parent];

  if (x_info.delimited) {
    return true;
  }
  if (y_info.delimited) {
    return false;
  }

  uint8_t x_power = x_info.power;
  uint8_t y_power = y_info.power;

  if (x_power == y_power) {
    if (side == 0) {
      r_abort("Internal error: Unspecified direction of associativity");
    }
    return r_ops_precedence[x].assoc == side;
  } else {
    return x_power > y_power;
  }
}

bool r_op_has_precedence(enum r_operator x, enum r_operator parent) {
  return op_has_precedence_impl(x, parent, 0);
}
bool r_lhs_op_has_precedence(enum r_operator lhs, enum r_operator parent) {
  return op_has_precedence_impl(lhs, parent, -1);
}
bool r_rhs_op_has_precedence(enum r_operator rhs, enum r_operator parent) {
  return op_has_precedence_impl(rhs, parent, 1);
}


void init_parse(r_obj* ns) {
  RLANG_ASSERT((sizeof(r_ops_precedence) / sizeof(struct r_op_precedence)) == R_OP_MAX);

  for (int i = R_OP_NONE + 1; i < R_OP_MAX; ++i) {
    if (r_ops_precedence[i].power == 0) {
      Rf_error("Internal error: `r_ops_precedence` is not fully initialised");
    }
  }
}
