#include "alias.h"
#include "util/dict.h"

AliasTable *AliasTable_g = NULL;

AliasTable *AliasTable_New(void) {
  AliasTable *t = rm_calloc(1, sizeof(*t));
  t->d = dictCreate(&dictTypeHeapStrings, NULL);
  return t;
}

void IndexAlias_InitGlobal(void) {
  AliasTable_g = AliasTable_New();
}

void IndexAlias_DestroyGlobal(void) {
  if (!AliasTable_g) {
    return;
  }
  dictRelease(AliasTable_g->d);
  rm_free(AliasTable_g);
  AliasTable_g = NULL;
}

int AliasTable_Add(AliasTable *table, const char *alias, IndexSpec *spec, int options,
                   QueryError *error) {
  // look up and see if it exists:
  dictEntry *e, *existing = NULL;
  e = dictAddRaw(table->d, (void *)alias, &existing);
  if (existing) {
    QueryError_SetError(error, QUERY_EINDEXEXISTS, "Alias already exists");
    return REDISMODULE_ERR;
  }
  assert(e->key != alias);
  e->v.val = spec;
  if (!(options & INDEXALIAS_NO_BACKREF)) {
    char *duped = rm_strdup(alias);
    spec->aliases = array_ensure_append(spec->aliases, &duped, 1, char *);
  }
  if (table->on_add) {
    table->on_add(alias, spec);
  }
  return REDISMODULE_OK;
}

int AliasTable_Del(AliasTable *table, const char *alias, IndexSpec *spec, int options,
                   QueryError *error) {
  char *toFree = NULL;
  // ensure that the item exists in the list
  if (!spec->aliases) {
    QueryError_SetError(error, QUERY_ENOINDEX, "Alias does not belong to provided spec");
    return REDISMODULE_ERR;
  }

  ssize_t idx = -1;
  for (size_t ii = 0; ii < array_len(spec->aliases); ++ii) {
    // note, NULL might be here if we're clearing the spec's aliases
    if (spec->aliases[ii] && !strcasecmp(spec->aliases[ii], alias)) {
      idx = ii;
      break;
    }
  }
  if (idx == -1) {
    QueryError_SetError(error, QUERY_ENOINDEX, "Alias does not belong to provided spec");
    return REDISMODULE_ERR;
  }

  if (!(options & INDEXALIAS_NO_BACKREF)) {
    toFree = spec->aliases[idx];
    spec->aliases = array_del_fast(spec->aliases, idx);
  }
  int rc = dictDelete(table->d, alias);
  assert(rc == DICT_OK);
  if (table->on_del) {
    table->on_del(alias, spec);
  }

  if (toFree) {
    rm_free(toFree);
  }
  return REDISMODULE_OK;
}

IndexSpec *AliasTable_Get(AliasTable *tbl, const char *alias) {
  dictEntry *e = dictFind(tbl->d, alias);
  if (e) {
    return e->v.val;
  } else {
    return NULL;
  }
}

int IndexAlias_Add(const char *alias, IndexSpec *spec, int options, QueryError *status) {
  return AliasTable_Add(AliasTable_g, alias, spec, options, status);
}

int IndexAlias_Del(const char *alias, IndexSpec *spec, int options, QueryError *status) {
  return AliasTable_Del(AliasTable_g, alias, spec, options, status);
}

IndexSpec *IndexAlias_Get(const char *alias) {
  return AliasTable_Get(AliasTable_g, alias);
}

void IndexSpec_ClearAliases(IndexSpec *sp) {
  if (!sp->aliases) {
    return;
  }
  for (size_t ii = 0; ii < array_len(sp->aliases); ++ii) {
    char **pp = sp->aliases + ii;
    QueryError e = {0};
    int rc = IndexAlias_Del(*pp, sp, INDEXALIAS_NO_BACKREF, &e);
    assert(rc == REDISMODULE_OK);
    rm_free(*pp);
    // set to NULL so IndexAlias_Del skips over this
    *pp = NULL;
  }
  array_free(sp->aliases);
}