#include "./language.h"
#include "./atomic.h"
#include "./wasm_store.h"
#include "tree_sitter/api.h"
#include <stddef.h>
#include <string.h>

#ifdef __wasm__

typedef struct {
  TSLanguage language;
  volatile uint32_t ref_count;
} TSUnparseableLanguage;

// Linear memory can be shared by multiple WebAssembly instances, but a
// module-defined global belongs to one instance. Assign each instance an ID
// from a counter in shared memory so trees can identify the function table
// that owns their language callbacks.
static volatile uint32_t ts_language_next_context_id;

__asm__(
  ".globaltype ts_language_context_id, i32\n"
  "ts_language_context_id:\n"
);

static inline TSUnparseableLanguage *ts_language__unparseable(const TSLanguage *self) {
  return (TSUnparseableLanguage *)((char *)self - offsetof(TSUnparseableLanguage, language));
}

static inline bool ts_language__is_unparseable(const TSLanguage *self) {
  return (
    self &&
    !self->lex_fn &&
    self->external_scanner.states == (const bool *)self
  );
}

uint32_t ts_language_current_context_id(void) {
  uint32_t result;
  __asm__(
    "global.get ts_language_context_id\n"
    "local.set %0\n"
    : "=r"(result)
  );
  if (!result) {
    result = atomic_inc(&ts_language_next_context_id);
    __asm__(
      "local.get %0\n"
      "global.set ts_language_context_id\n"
      :
      : "r"(result)
    );
  }
  return result;
}

#endif

const TSLanguage *ts_language_copy(const TSLanguage *self) {
#ifdef __wasm__
  if (ts_language__is_unparseable(self)) {
    atomic_inc(&ts_language__unparseable(self)->ref_count);
  } else
#endif
  if (self && ts_language_is_wasm(self)) {
    ts_wasm_language_retain(self);
  }
  return self;
}

void ts_language_delete(const TSLanguage *self) {
#ifdef __wasm__
  if (ts_language__is_unparseable(self)) {
    TSUnparseableLanguage *language = ts_language__unparseable(self);
    if (atomic_dec(&language->ref_count) == 0) {
      ts_free(language);
    }
  } else
#endif
  if (self && ts_language_is_wasm(self)) {
    ts_wasm_language_release(self);
  }
}

bool ts_language_is_parseable(const TSLanguage *self) {
  return self && self->lex_fn;
}

const TSLanguage *ts_language_copy_without_callbacks(const TSLanguage *self) {
#ifdef __wasm__
  if (self && ts_language_is_parseable(self)) {
    TSUnparseableLanguage *result = ts_malloc(sizeof(TSUnparseableLanguage));
    result->language = *self;
    result->language.lex_fn = NULL;
    result->language.keyword_lex_fn = NULL;
    result->language.external_scanner.states = (const bool *)&result->language;
    result->language.external_scanner.create = NULL;
    result->language.external_scanner.destroy = NULL;
    result->language.external_scanner.scan = NULL;
    result->language.external_scanner.serialize = NULL;
    result->language.external_scanner.deserialize = NULL;
    result->ref_count = 1;
    return &result->language;
  }
#endif
  return ts_language_copy(self);
}

uint32_t ts_language_symbol_count(const TSLanguage *self) {
  return self->symbol_count + self->alias_count;
}

uint32_t ts_language_state_count(const TSLanguage *self) {
  return self->state_count;
}

const TSSymbol *ts_language_supertypes(const TSLanguage *self, uint32_t *length) {
  if (self->abi_version >= LANGUAGE_VERSION_WITH_RESERVED_WORDS) {
    *length = self->supertype_count;
    return self->supertype_symbols;
  } else {
    *length = 0;
    return NULL;
  }
}

const TSSymbol *ts_language_subtypes(
  const TSLanguage *self,
  TSSymbol supertype,
  uint32_t *length
) {
  if (self->abi_version < LANGUAGE_VERSION_WITH_RESERVED_WORDS || !ts_language_symbol_metadata(self, supertype).supertype) {
    *length = 0;
    return NULL;
  }

  TSMapSlice slice = self->supertype_map_slices[supertype];
  *length = slice.length;
  return &self->supertype_map_entries[slice.index];
}

uint32_t ts_language_abi_version(const TSLanguage *self) {
  return self->abi_version;
}

const TSLanguageMetadata *ts_language_metadata(const TSLanguage *self) {
    return self->abi_version >= LANGUAGE_VERSION_WITH_RESERVED_WORDS ? &self->metadata : NULL;
}

const char *ts_language_name(const TSLanguage *self) {
  return self->abi_version >= LANGUAGE_VERSION_WITH_RESERVED_WORDS ? self->name : NULL;
}

uint32_t ts_language_field_count(const TSLanguage *self) {
  return self->field_count;
}

void ts_language_table_entry(
  const TSLanguage *self,
  TSStateId state,
  TSSymbol symbol,
  TableEntry *result
) {
  if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) {
    result->action_count = 0;
    result->is_reusable = false;
    result->actions = NULL;
  } else {
    ts_assert(symbol < self->token_count);
    uint32_t action_index = ts_language_lookup(self, state, symbol);
    const TSParseActionEntry *entry = &self->parse_actions[action_index];
    result->action_count = entry->entry.count;
    result->is_reusable = entry->entry.reusable;
    result->actions = (const TSParseAction *)(entry + 1);
  }
}

TSLexerMode ts_language_lex_mode_for_state(
   const TSLanguage *self,
   TSStateId state
) {
  if (self->abi_version < 15) {
    TSLexMode mode = ((const TSLexMode *)self->lex_modes)[state];
    return (TSLexerMode) {
      .lex_state = mode.lex_state,
      .external_lex_state = mode.external_lex_state,
      .reserved_word_set_id = 0,
    };
  } else {
    return self->lex_modes[state];
  }
}

bool ts_language_is_reserved_word(
  const TSLanguage *self,
  TSStateId state,
  TSSymbol symbol
) {
  TSLexerMode lex_mode = ts_language_lex_mode_for_state(self, state);
  if (lex_mode.reserved_word_set_id > 0) {
    unsigned start = lex_mode.reserved_word_set_id * self->max_reserved_word_set_size;
    unsigned end = start + self->max_reserved_word_set_size;
    for (unsigned i = start; i < end; i++) {
      if (self->reserved_words[i] == symbol) return true;
      if (self->reserved_words[i] == 0) break;
    }
  }
  return false;
}

TSSymbolMetadata ts_language_symbol_metadata(
  const TSLanguage *self,
  TSSymbol symbol
) {
  if (symbol == ts_builtin_sym_error)  {
    return (TSSymbolMetadata) {.visible = true, .named = true};
  } else if (symbol == ts_builtin_sym_error_repeat) {
    return (TSSymbolMetadata) {.visible = false, .named = false};
  } else {
    return self->symbol_metadata[symbol];
  }
}

TSSymbol ts_language_public_symbol(
  const TSLanguage *self,
  TSSymbol symbol
) {
  if (symbol == ts_builtin_sym_error) return symbol;
  return self->public_symbol_map[symbol];
}

TSStateId ts_language_next_state(
  const TSLanguage *self,
  TSStateId state,
  TSSymbol symbol
) {
  if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) {
    return 0;
  } else if (symbol < self->token_count) {
    uint32_t count;
    const TSParseAction *actions = ts_language_actions(self, state, symbol, &count);
    if (count > 0) {
      TSParseAction action = actions[count - 1];
      if (action.type == TSParseActionTypeShift) {
        return action.shift.extra ? state : action.shift.state;
      }
    }
    return 0;
  } else {
    return ts_language_lookup(self, state, symbol);
  }
}

const char *ts_language_symbol_name(
  const TSLanguage *self,
  TSSymbol symbol
) {
  if (symbol == ts_builtin_sym_error) {
    return "ERROR";
  } else if (symbol == ts_builtin_sym_error_repeat) {
    return "_ERROR";
  } else if (symbol < ts_language_symbol_count(self)) {
    return self->symbol_names[symbol];
  } else {
    return NULL;
  }
}

TSSymbol ts_language_symbol_for_name(
  const TSLanguage *self,
  const char *string,
  uint32_t length,
  bool is_named
) {
  if (is_named && !strncmp(string, "ERROR", length)) return ts_builtin_sym_error;
  uint16_t count = (uint16_t)ts_language_symbol_count(self);
  for (TSSymbol i = 0; i < count; i++) {
    TSSymbolMetadata metadata = ts_language_symbol_metadata(self, i);
    if ((!metadata.visible && !metadata.supertype) || metadata.named != is_named) continue;
    const char *symbol_name = self->symbol_names[i];
    if (!strncmp(symbol_name, string, length) && !symbol_name[length]) {
      return self->public_symbol_map[i];
    }
  }
  return 0;
}

TSSymbolType ts_language_symbol_type(
  const TSLanguage *self,
  TSSymbol symbol
) {
  TSSymbolMetadata metadata = ts_language_symbol_metadata(self, symbol);
  if (metadata.named && metadata.visible) {
    return TSSymbolTypeRegular;
  } else if (metadata.visible) {
    return TSSymbolTypeAnonymous;
  } else if (metadata.supertype) {
    return TSSymbolTypeSupertype;
  } else {
    return TSSymbolTypeAuxiliary;
  }
}

const char *ts_language_field_name_for_id(
  const TSLanguage *self,
  TSFieldId id
) {
  uint32_t count = ts_language_field_count(self);
  if (count && id <= count) {
    return self->field_names[id];
  } else {
    return NULL;
  }
}

TSFieldId ts_language_field_id_for_name(
  const TSLanguage *self,
  const char *name,
  uint32_t name_length
) {
  uint16_t count = (uint16_t)ts_language_field_count(self);
  for (TSSymbol i = 1; i < count + 1; i++) {
    switch (strncmp(name, self->field_names[i], name_length)) {
      case 0:
        if (self->field_names[i][name_length] == 0) return i;
        break;
      case -1:
        return 0;
      default:
        break;
    }
  }
  return 0;
}

TSLookaheadIterator *ts_lookahead_iterator_new(const TSLanguage *self, TSStateId state) {
  if (state >= self->state_count) return NULL;
  LookaheadIterator *iterator = ts_malloc(sizeof(LookaheadIterator));
  *iterator = ts_language_lookaheads(ts_language_copy(self), state);
  return (TSLookaheadIterator *)iterator;
}

void ts_lookahead_iterator_delete(TSLookaheadIterator *self) {
  if (!self) return;
  LookaheadIterator *iterator = (LookaheadIterator *)self;
  ts_language_delete(iterator->language);
  ts_free(iterator);
}

bool ts_lookahead_iterator_reset_state(TSLookaheadIterator * self, TSStateId state) {
  LookaheadIterator *iterator = (LookaheadIterator *)self;
  if (state >= iterator->language->state_count) return false;
  *iterator = ts_language_lookaheads(iterator->language, state);
  return true;
}

const TSLanguage *ts_lookahead_iterator_language(const TSLookaheadIterator *self) {
  const LookaheadIterator *iterator = (const LookaheadIterator *)self;
  return iterator->language;
}

bool ts_lookahead_iterator_reset(TSLookaheadIterator *self, const TSLanguage *language, TSStateId state) {
  if (state >= language->state_count) return false;
  LookaheadIterator *iterator = (LookaheadIterator *)self;
  const TSLanguage *previous = iterator->language;
  *iterator = ts_language_lookaheads(ts_language_copy(language), state);
  ts_language_delete(previous);
  return true;
}

bool ts_lookahead_iterator_next(TSLookaheadIterator *self) {
  LookaheadIterator *iterator = (LookaheadIterator *)self;
  return ts_lookahead_iterator__next(iterator);
}

TSSymbol ts_lookahead_iterator_current_symbol(const TSLookaheadIterator *self) {
  const LookaheadIterator *iterator = (const LookaheadIterator *)self;
  return iterator->symbol;
}

const char *ts_lookahead_iterator_current_symbol_name(const TSLookaheadIterator *self) {
  const LookaheadIterator *iterator = (const LookaheadIterator *)self;
  if (iterator->phase != LookaheadPositioned) return NULL;
  return ts_language_symbol_name(iterator->language, iterator->symbol);
}
