#include "tree_sitter/array.h"

#include <string.h>

typedef enum {
  AREA,
  BASE,
  BASEFONT,
  BGSOUND,
  BR,
  COL,
  COMMAND,
  EMBED,
  FRAME,
  HR,
  IMAGE,
  IMG,
  INPUT,
  ISINDEX,
  KEYGEN,
  LINK,
  MENUITEM,
  META,
  NEXTID,
  PARAM,
  SOURCE,
  TRACK,
  WBR,
  END_OF_VOID_TAGS,

  A,
  ABBR,
  ADDRESS,
  ARTICLE,
  ASIDE,
  AUDIO,
  B,
  BDI,
  BDO,
  BLOCKQUOTE,
  BODY,
  BUTTON,
  CANVAS,
  CAPTION,
  CITE,
  CODE,
  COLGROUP,
  DATA,
  DATALIST,
  DD,
  DEL,
  DETAILS,
  DFN,
  DIALOG,
  DIV,
  DL,
  DT,
  EM,
  FIELDSET,
  FIGCAPTION,
  FIGURE,
  FOOTER,
  FORM,
  H1,
  H2,
  H3,
  H4,
  H5,
  H6,
  HEAD,
  HEADER,
  HGROUP,
  HTML,
  I,
  IFRAME,
  INS,
  KBD,
  LABEL,
  LEGEND,
  LI,
  MAIN,
  MAP,
  MARK,
  MATH,
  MENU,
  METER,
  NAV,
  NOSCRIPT,
  OBJECT,
  OL,
  OPTGROUP,
  OPTION,
  OUTPUT,
  P,
  PICTURE,
  PRE,
  PROGRESS,
  Q,
  RB,
  RP,
  RT,
  RTC,
  RUBY,
  S,
  SAMP,
  SCRIPT,
  SECTION,
  SELECT,
  SLOT,
  SMALL,
  SPAN,
  STRONG,
  STYLE,
  SUB,
  SUMMARY,
  SUP,
  SVG,
  TABLE,
  TBODY,
  TD,
  TEMPLATE,
  TEXTAREA,
  TFOOT,
  TH,
  THEAD,
  TIME,
  TITLE,
  TR,
  U,
  UL,
  VAR,
  VIDEO,
  // Angular's interpolation syntax
  INTERPOLATION,
  CONTROL_FLOW,

  CUSTOM,

  END_,
} TagType;

typedef Array(char) String;

typedef struct {
  char tag_name[16];
  TagType tag_type;
} TagMapEntry;

typedef struct {
  TagType type;
  String custom_tag_name;
} Tag;

const TagMapEntry TAG_TYPES_BY_TAG_NAME[126] = {
    {"AREA", AREA},
    {"BASE", BASE},
    {"BASEFONT", BASEFONT},
    {"BGSOUND", BGSOUND},
    {"BR", BR},
    {"COL", COL},
    {"COMMAND", COMMAND},
    {"EMBED", EMBED},
    {"FRAME", FRAME},
    {"HR", HR},
    {"IMAGE", IMAGE},
    {"IMG", IMG},
    {"INPUT", INPUT},
    {"ISINDEX", ISINDEX},
    {"KEYGEN", KEYGEN},
    {"LINK", LINK},
    {"MENUITEM", MENUITEM},
    {"META", META},
    {"NEXTID", NEXTID},
    {"PARAM", PARAM},
    {"SOURCE", SOURCE},
    {"TRACK", TRACK},
    {"WBR", WBR},
    {"A", A},
    {"ABBR", ABBR},
    {"ADDRESS", ADDRESS},
    {"ARTICLE", ARTICLE},
    {"ASIDE", ASIDE},
    {"AUDIO", AUDIO},
    {"B", B},
    {"BDI", BDI},
    {"BDO", BDO},
    {"BLOCKQUOTE", BLOCKQUOTE},
    {"BODY", BODY},
    {"BUTTON", BUTTON},
    {"CANVAS", CANVAS},
    {"CAPTION", CAPTION},
    {"CITE", CITE},
    {"CODE", CODE},
    {"COLGROUP", COLGROUP},
    {"DATA", DATA},
    {"DATALIST", DATALIST},
    {"DD", DD},
    {"DEL", DEL},
    {"DETAILS", DETAILS},
    {"DFN", DFN},
    {"DIALOG", DIALOG},
    {"DIV", DIV},
    {"DL", DL},
    {"DT", DT},
    {"EM", EM},
    {"FIELDSET", FIELDSET},
    {"FIGCAPTION", FIGCAPTION},
    {"FIGURE", FIGURE},
    {"FOOTER", FOOTER},
    {"FORM", FORM},
    {"H1", H1},
    {"H2", H2},
    {"H3", H3},
    {"H4", H4},
    {"H5", H5},
    {"H6", H6},
    {"HEAD", HEAD},
    {"HEADER", HEADER},
    {"HGROUP", HGROUP},
    {"HTML", HTML},
    {"I", I},
    {"IFRAME", IFRAME},
    {"INS", INS},
    {"KBD", KBD},
    {"LABEL", LABEL},
    {"LEGEND", LEGEND},
    {"LI", LI},
    {"MAIN", MAIN},
    {"MAP", MAP},
    {"MARK", MARK},
    {"MATH", MATH},
    {"MENU", MENU},
    {"METER", METER},
    {"NAV", NAV},
    {"NOSCRIPT", NOSCRIPT},
    {"OBJECT", OBJECT},
    {"OL", OL},
    {"OPTGROUP", OPTGROUP},
    {"OPTION", OPTION},
    {"OUTPUT", OUTPUT},
    {"P", P},
    {"PICTURE", PICTURE},
    {"PRE", PRE},
    {"PROGRESS", PROGRESS},
    {"Q", Q},
    {"RB", RB},
    {"RP", RP},
    {"RT", RT},
    {"RTC", RTC},
    {"RUBY", RUBY},
    {"S", S},
    {"SAMP", SAMP},
    {"SCRIPT", SCRIPT},
    {"SECTION", SECTION},
    {"SELECT", SELECT},
    {"SLOT", SLOT},
    {"SMALL", SMALL},
    {"SPAN", SPAN},
    {"STRONG", STRONG},
    {"STYLE", STYLE},
    {"SUB", SUB},
    {"SUMMARY", SUMMARY},
    {"SUP", SUP},
    {"SVG", SVG},
    {"TABLE", TABLE},
    {"TBODY", TBODY},
    {"TD", TD},
    {"TEMPLATE", TEMPLATE},
    {"TEXTAREA", TEXTAREA},
    {"TFOOT", TFOOT},
    {"TH", TH},
    {"THEAD", THEAD},
    {"TIME", TIME},
    {"TITLE", TITLE},
    {"TR", TR},
    {"U", U},
    {"UL", UL},
    {"VAR", VAR},
    {"VIDEO", VIDEO},
    {"CUSTOM", CUSTOM},
};

static const TagType TAG_TYPES_NOT_ALLOWED_IN_PARAGRAPHS[] = {
    ADDRESS,  ARTICLE,    ASIDE,  BLOCKQUOTE, DETAILS, DIV, DL,
    FIELDSET, FIGCAPTION, FIGURE, FOOTER,     FORM,    H1,  H2,
    H3,       H4,         H5,     H6,         HEADER,  HR,  MAIN,
    NAV,      OL,         P,      PRE,        SECTION,
};

static TagType tag_type_for_name(const String *tag_name) {
  for (int i = 0; i < 126; i++) {
    const TagMapEntry *entry = &TAG_TYPES_BY_TAG_NAME[i];
    if (
        strlen(entry->tag_name) == tag_name->size &&
        memcmp(tag_name->contents, entry->tag_name, tag_name->size) == 0
       ) {
      return entry->tag_type;
    }
  }
  return CUSTOM;
}

static inline Tag tag_new() {
  Tag tag;
  tag.type = END_;
  tag.custom_tag_name = (String) array_new();
  return tag;
}

static inline Tag tag_for_name(String name) {
  Tag tag = tag_new();
  tag.type = tag_type_for_name(&name);

  if (tag.type == CUSTOM) {
    tag.custom_tag_name = name;
  } else {
    array_delete(&name);
  }

  return tag;
}

static inline void tag_free(Tag *tag) {
  if (tag->type == CUSTOM) {
    array_delete(&tag->custom_tag_name);
  }
}

static inline bool tag_is_void(const Tag *self) {
  return self->type < END_OF_VOID_TAGS;
}

static bool tag_eq(const Tag *self, const Tag *other) {
  if (self->type != other->type) return false;

  if (self->type == CUSTOM) {
    if (self->custom_tag_name.size != other->custom_tag_name.size) {
      return false;
    }

    if (memcmp(
          self->custom_tag_name.contents,
          other->custom_tag_name.contents,
          self->custom_tag_name.size
          ) != 0) {
      return false;
    }
  }

  return true;
}

static bool tag_can_contain(Tag *self, const Tag *other) {
  TagType child = other->type;

  if (child == INTERPOLATION) {
    // can be contained in any tag
    return true;
  }

  switch (self->type) {
  case LI:
    return child != LI;

  case DT:
  case DD:
    return child != DT && child != DD;

  case P:
    for (int i = 0; i < 26; i++) {
      if (child == TAG_TYPES_NOT_ALLOWED_IN_PARAGRAPHS[i]) {
        return false;
      }
    }
    return true;

  case COLGROUP:
    return child == COL;

  case RB:
  case RT:
  case RP:
    return child != RB && child != RT && child != RP;

  case OPTGROUP:
    return child != OPTGROUP;

  case TR:
    return child != TR;

  case TD:
  case TH:
    return child != TD && child != TH && child != TR;

  default:
    return true;
  }
}
