/*
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * https://opensource.org/license/bsd-3-clause
 *
 * Copyright (C) 2011, 2012, 2013 Citrix Systems
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "ns_turn_maps.h"

#include "ns_turn_ioalib.h"

#include "ns_turn_khash.h"

#include <assert.h> // for assert
#include <stdlib.h> // for size_t, free, malloc, NULL, realloc
#include <string.h> // for memset, strcmp, memcpy, strlen

KHASHL_MAP_INIT(KH_LOCAL, ur_map_hash, ur_map_hash, ur_map_key_type, ur_map_value_type, kh_hash_uint64, kh_eq_generic)

#define MAGIC_HASH ((uint64_t)(0x90ABCDEFL))

struct _ur_map {
  ur_map_hash *h;
  uint64_t magic;
};

static bool ur_map_init(ur_map *map) {
  if (map) {
    map->h = ur_map_hash_init();
    if (map->h) {
      map->magic = MAGIC_HASH;
      return true;
    }
  }
  return false;
}

#define ur_map_valid(map) ((map) && ((map)->h) && ((map)->magic == MAGIC_HASH))

ur_map *ur_map_create(void) {
  ur_map *map = (ur_map *)malloc(sizeof(ur_map));
  if (!ur_map_init(map)) {
    free(map);
    return NULL;
  }
  return map;
}

bool ur_map_put(ur_map *map, ur_map_key_type key, ur_map_value_type value) {
  if (!ur_map_valid(map)) {
    return false;
  }

  int ret = 0;
  khint_t k = ur_map_hash_put(map->h, key, &ret);

  if (ret < 0) {
    return false;
  }

  kh_val(map->h, k) = value;

  return true;
}

bool ur_map_get(const ur_map *map, ur_map_key_type key, ur_map_value_type *value) {
  if (!ur_map_valid(map)) {
    return false;
  }

  khint_t k = ur_map_hash_get(map->h, key);
  if ((k != kh_end(map->h)) && kh_exist(map->h, k)) {
    if (value) {
      *value = kh_val(map->h, k);
    }
    return true;
  }

  return false;
}

bool ur_map_del(ur_map *map, ur_map_key_type key, ur_map_del_func delfunc) {
  if (!ur_map_valid(map)) {
    return false;
  }

  khint_t k = ur_map_hash_get(map->h, key);
  if ((k != kh_end(map->h)) && kh_exist(map->h, k)) {
    if (delfunc) {
      delfunc(kh_val(map->h, k));
    }
    ur_map_hash_del(map->h, k);
    return true;
  }

  return false;
}

bool ur_map_exist(const ur_map *map, ur_map_key_type key) {
  if (!ur_map_valid(map)) {
    return false;
  }

  khint_t k = ur_map_hash_get(map->h, key);
  if ((k != kh_end(map->h)) && kh_exist(map->h, k)) {
    return true;
  }

  return 0;
}

void ur_map_free(ur_map **map) {
  if (map && ur_map_valid(*map)) {
    {
      static const int khctest = 0;
      if (khctest) {
        ur_map_hash_clear((*map)->h);
      }
    }
    ur_map_hash_destroy((*map)->h);
    (*map)->h = NULL;
    (*map)->magic = 0;
    free(*map);
    *map = NULL;
  }
}

size_t ur_map_size(const ur_map *map) {
  if (ur_map_valid(map)) {
    return kh_size(map->h);
  } else {
    return 0;
  }
}

bool ur_map_foreach(ur_map *map, foreachcb_type func) {
  if (map && func && ur_map_valid(map)) {
    for (khint_t k = 0; k != kh_end(map->h); ++k) {
      if (kh_exist(map->h, k)) {
        if (func((ur_map_key_type)(kh_key(map->h, k)), (ur_map_value_type)(kh_val(map->h, k)))) {
          return true;
        }
      }
    }
  }
  return false;
}

bool ur_map_foreach_arg(const ur_map *map, foreachcb_arg_type func, void *arg) {
  if (map && func && ur_map_valid(map)) {
    for (khint_t k = 0; k != kh_end(map->h); ++k) {
      if (kh_exist(map->h, k)) {
        if (func((ur_map_key_type)(kh_key(map->h, k)), (ur_map_value_type)(kh_val(map->h, k)), arg)) {
          return true;
        }
      }
    }
  }
  return false;
}

//////////////////// LOCAL MAPS ////////////////////////////////////

void lm_map_init(lm_map *map) {
  if (map) {
    memset(map, 0, sizeof(lm_map));
  }
}

bool lm_map_put(lm_map *map, ur_map_key_type key, ur_map_value_type value) {
  if (!map || !key || !value) {
    return false;
  }

  const size_t index = (size_t)(key & (LM_MAP_HASH_SIZE - 1));
  lm_map_array *a = &(map->table[index]);

  for (size_t i = 0; i < LM_MAP_ARRAY_SIZE; ++i) {
    const ur_map_key_type key0 = a->main_keys[i];
    const ur_map_value_type value0 = a->main_values[i];

    if (key0 == key) {
      return value0 == value;
    }

    if (!key0 || !value0) {
      a->main_keys[i] = key;
      a->main_values[i] = value;
      return true;
    }
  }

  const size_t esz = a->extra_sz;
  if (esz && a->extra_keys && a->extra_values) {
    for (size_t i = 0; i < esz; ++i) {
      ur_map_key_type *keyp = a->extra_keys[i];
      ur_map_value_type *valuep = a->extra_values[i];
      if (keyp && valuep) {
        if (!(*keyp) || !(*valuep)) {
          *keyp = key;
          *valuep = value;
          return true;
        }
      } else {
        if (!(*keyp)) {
          a->extra_keys[i] = (ur_map_key_type *)malloc(sizeof(ur_map_key_type));
          keyp = a->extra_keys[i];
        }
        if (!(*valuep)) {
          a->extra_values[i] = (ur_map_value_type *)malloc(sizeof(ur_map_value_type));
          valuep = a->extra_values[i];
        }
        assert(keyp);
        assert(valuep);
        *keyp = key;
        *valuep = value;
        return false;
      }
    }
  }

  const size_t old_sz = esz;
  size_t old_sz_mem = esz * sizeof(ur_map_key_type *);
  a->extra_keys = (ur_map_key_type **)realloc(a->extra_keys, old_sz_mem + sizeof(ur_map_key_type *));
  assert(a->extra_keys);
  a->extra_keys[old_sz] = (ur_map_key_type *)malloc(sizeof(ur_map_key_type));
  *(a->extra_keys[old_sz]) = key;

  old_sz_mem = esz * sizeof(ur_map_value_type *);
  a->extra_values = (ur_map_value_type **)realloc(a->extra_values, old_sz_mem + sizeof(ur_map_value_type *));
  assert(a->extra_values);
  a->extra_values[old_sz] = (ur_map_value_type *)malloc(sizeof(ur_map_value_type));
  if (a->extra_values[old_sz] != NULL) {
    *(a->extra_values[old_sz]) = value;
    a->extra_sz += 1;
    return true;
  } else {
    return false;
  }
}

bool lm_map_get(const lm_map *map, ur_map_key_type key, ur_map_value_type *value) {
  if (!map || !key) {
    return false;
  }

  const size_t index = (size_t)(key & (LM_MAP_HASH_SIZE - 1));
  const lm_map_array *a = &(map->table[index]);

  for (size_t i = 0; i < LM_MAP_ARRAY_SIZE; ++i) {
    const ur_map_key_type key0 = a->main_keys[i];
    if ((key0 == key) && a->main_values[i]) {
      if (value) {
        *value = a->main_values[i];
      }
      return true;
    }
  }

  const size_t esz = a->extra_sz;
  if (esz && a->extra_keys && a->extra_values) {
    for (size_t i = 0; i < esz; ++i) {
      ur_map_key_type *keyp = a->extra_keys[i];
      ur_map_value_type *valuep = a->extra_values[i];
      if (keyp && valuep) {
        if (*keyp == key) {
          if (value) {
            *value = *valuep;
          }
          return true;
        }
      }
    }
  }

  return false;
}

bool lm_map_del(lm_map *map, ur_map_key_type key, ur_map_del_func delfunc) {
  if (!map || !key) {
    return false;
  }

  const size_t index = (size_t)(key & (LM_MAP_HASH_SIZE - 1));
  lm_map_array *a = &(map->table[index]);

  for (size_t i = 0; i < LM_MAP_ARRAY_SIZE; ++i) {
    const ur_map_key_type key0 = a->main_keys[i];

    if ((key0 == key) && a->main_values[i]) {
      if (delfunc) {
        delfunc(a->main_values[i]);
      }
      a->main_keys[i] = 0;
      a->main_values[i] = 0;
      return true;
    }
  }

  const size_t esz = a->extra_sz;
  if (esz && a->extra_keys && a->extra_values) {
    for (size_t i = 0; i < esz; ++i) {
      ur_map_key_type *keyp = a->extra_keys[i];
      ur_map_value_type *valuep = a->extra_values[i];
      if (keyp && valuep) {
        if (*keyp == key) {
          if (delfunc) {
            delfunc(*valuep);
          }
          *keyp = 0;
          *valuep = 0;
          return true;
        }
      }
    }
  }

  return false;
}

bool lm_map_exist(const lm_map *map, ur_map_key_type key) { return lm_map_get(map, key, NULL); }

void lm_map_clean(lm_map *map) {
  for (size_t j = 0; j < LM_MAP_HASH_SIZE; ++j) {
    lm_map_array *a = &(map->table[j]);

    const size_t esz = a->extra_sz;
    if (esz) {
      if (a->extra_keys) {
        for (size_t i = 0; i < esz; ++i) {
          ur_map_key_type *keyp = a->extra_keys[i];
          if (keyp) {
            *keyp = 0;
            free(keyp);
          }
        }
        free(a->extra_keys);
        a->extra_keys = NULL;
      }
      if (a->extra_values) {
        for (size_t i = 0; i < esz; ++i) {
          ur_map_value_type *valuep = a->extra_values[i];
          if (valuep) {
            *valuep = 0;
            free(valuep);
          }
        }
        free(a->extra_values);
        a->extra_values = NULL;
      }
    }
  }

  lm_map_init(map);
}

size_t lm_map_size(const lm_map *map) {

  if (!map) {
    return 0;
  }

  size_t ret = 0;
  for (size_t i = 0; i < LM_MAP_HASH_SIZE; ++i) {
    const lm_map_array *a = &(map->table[i]);

    for (size_t j = 0; j < LM_MAP_ARRAY_SIZE; ++j) {
      if (a->main_keys[j] && a->main_values[j]) {
        ++ret;
      }
    }

    const size_t esz = a->extra_sz;
    if (esz && a->extra_values && a->extra_keys) {
      for (size_t j = 0; j < esz; ++j) {
        if (*(a->extra_keys[j]) && *(a->extra_values[j])) {
          ++ret;
        }
      }
    }
  }

  return ret;
}

bool lm_map_foreach(lm_map *map, foreachcb_type func) {
  if (!map) {
    return false;
  }

  for (size_t i = 0; i < LM_MAP_HASH_SIZE; ++i) {
    lm_map_array *a = &(map->table[i]);

    for (size_t j = 0; j < LM_MAP_ARRAY_SIZE; ++j) {
      if (a->main_keys[j] && a->main_values[j]) {
        if (func((ur_map_key_type)a->main_keys[j], (ur_map_value_type)a->main_values[j])) {
          return true;
        }
      }
    }

    const size_t esz = a->extra_sz;
    if (esz && a->extra_values && a->extra_keys) {
      for (size_t j = 0; j < esz; ++j) {
        if (*(a->extra_keys[j]) && *(a->extra_values[j])) {
          if (func((ur_map_key_type) * (a->extra_keys[j]), (ur_map_value_type) * (a->extra_values[j]))) {
            return true;
          }
        }
      }
    }
  }

  return false;
}

bool lm_map_foreach_arg(lm_map *map, foreachcb_arg_type func, void *arg) {
  if (!map) {
    return false;
  }

  for (size_t i = 0; i < LM_MAP_HASH_SIZE; ++i) {
    lm_map_array *a = &(map->table[i]);

    for (size_t j = 0; j < LM_MAP_ARRAY_SIZE; ++j) {
      if (a->main_keys[j] && a->main_values[j]) {
        if (func((ur_map_key_type)a->main_keys[j], (ur_map_value_type)a->main_values[j], arg)) {
          return true;
        }
      }
    }

    const size_t esz = a->extra_sz;
    if (esz && a->extra_values && a->extra_keys) {
      for (size_t j = 0; j < esz; ++j) {
        if (*(a->extra_keys[j]) && *(a->extra_values[j])) {
          if (func((ur_map_key_type) * (a->extra_keys[j]), (ur_map_value_type) * (a->extra_values[j]), arg)) {
            return true;
          }
        }
      }
    }
  }

  return false;
}

////////////////////  ADDR LISTS ///////////////////////////////////

static void addr_list_free(addr_list_header *slh) {
  if (slh) {
    if (slh->extra_list) {
      free(slh->extra_list);
    }
    memset(slh, 0, sizeof(addr_list_header));
  }
}

static void addr_list_add(addr_list_header *slh, const ioa_addr *key, ur_addr_map_value_type value) {
  if (!key || !value) {
    return;
  }

  addr_elem *elem = NULL;

  for (size_t i = 0; i < ADDR_ARRAY_SIZE; ++i) {
    if (!(slh->main_list[i].value)) {
      elem = &(slh->main_list[i]);
      break;
    }
  }

  if (!elem && slh->extra_list) {
    for (size_t i = 0; i < slh->extra_sz; ++i) {
      if (!(slh->extra_list[i].value)) {
        elem = &(slh->extra_list[i]);
        break;
      }
    }
  }

  if (!elem) {
    const size_t old_sz = slh->extra_sz;
    const size_t old_sz_mem = old_sz * sizeof(addr_elem);
    slh->extra_list = (addr_elem *)realloc(slh->extra_list, old_sz_mem + sizeof(addr_elem));
    assert(slh->extra_list);
    elem = &(slh->extra_list[old_sz]);
    slh->extra_sz += 1;
  }

  addr_cpy(&(elem->key), key);
  elem->value = value;
}

static void addr_list_remove(addr_list_header *slh, const ioa_addr *key, ur_addr_map_func delfunc, int *counter) {
  if (!slh || !key) {
    return;
  }

  if (counter) {
    *counter = 0;
  }

  for (size_t i = 0; i < ADDR_ARRAY_SIZE; ++i) {
    addr_elem *elem = &(slh->main_list[i]);
    if (elem->value) {
      if (addr_eq(&(elem->key), key)) {
        if (delfunc && elem->value) {
          delfunc(elem->value);
        }
        elem->value = 0;
        if (counter) {
          *counter += 1;
        }
      }
    }
  }

  if (slh->extra_list) {
    for (size_t i = 0; i < slh->extra_sz; ++i) {
      addr_elem *elem = &(slh->extra_list[i]);
      if (elem->value) {
        if (addr_eq(&(elem->key), key)) {
          if (delfunc && elem->value) {
            delfunc(elem->value);
          }
          elem->value = 0;
          if (counter) {
            *counter += 1;
          }
        }
      }
    }
  }
}

static void addr_list_foreach(addr_list_header *slh, ur_addr_map_func func) {
  if (slh && func) {

    for (size_t i = 0; i < ADDR_ARRAY_SIZE; ++i) {
      addr_elem *elem = &(slh->main_list[i]);
      if (elem->value) {
        func(elem->value);
      }
    }

    if (slh->extra_list) {
      for (size_t i = 0; i < slh->extra_sz; ++i) {
        addr_elem *elem = &(slh->extra_list[i]);
        if (elem->value) {
          func(elem->value);
        }
      }
    }
  }
}

/* Returns false as soon as the callback returns false (early exit). */
static bool addr_list_foreach_arg(addr_list_header *slh, ur_addr_map_func_arg func, void *arg) {
  if (slh && func) {
    for (size_t i = 0; i < ADDR_ARRAY_SIZE; ++i) {
      addr_elem *elem = &(slh->main_list[i]);
      if (elem->value) {
        if (!func(elem->value, arg)) {
          return false;
        }
      }
    }
    if (slh->extra_list) {
      for (size_t i = 0; i < slh->extra_sz; ++i) {
        addr_elem *elem = &(slh->extra_list[i]);
        if (elem->value) {
          if (!func(elem->value, arg)) {
            return false;
          }
        }
      }
    }
  }
  return true;
}

/* Returns false as soon as the callback returns false (early exit). */
static bool addr_list_foreach_key_arg(addr_list_header *slh, ur_addr_map_key_func_arg func, void *arg) {
  if (slh && func) {
    for (size_t i = 0; i < ADDR_ARRAY_SIZE; ++i) {
      addr_elem *elem = &(slh->main_list[i]);
      if (elem->value) {
        if (!func(&(elem->key), elem->value, arg)) {
          return false;
        }
      }
    }
    if (slh->extra_list) {
      for (size_t i = 0; i < slh->extra_sz; ++i) {
        addr_elem *elem = &(slh->extra_list[i]);
        if (elem->value) {
          if (!func(&(elem->key), elem->value, arg)) {
            return false;
          }
        }
      }
    }
  }
  return true;
}

static size_t addr_list_num_elements(const addr_list_header *slh) {
  size_t ret = 0;

  if (slh) {
    for (size_t i = 0; i < ADDR_ARRAY_SIZE; ++i) {
      const addr_elem *elem = &(slh->main_list[i]);
      if (elem->value) {
        ++ret;
      }
    }

    if (slh->extra_list) {
      for (size_t i = 0; i < slh->extra_sz; ++i) {
        addr_elem *elem = &(slh->extra_list[i]);
        if (elem->value) {
          ++ret;
        }
      }
    }
  }

  return ret;
}

static size_t addr_list_size(const addr_list_header *slh) {
  if (slh) {
    return ADDR_ARRAY_SIZE + slh->extra_sz;
  }

  return 0;
}

static addr_elem *addr_list_get(addr_list_header *slh, const ioa_addr *key) {
  if (!slh || !key) {
    return NULL;
  }

  for (size_t i = 0; i < ADDR_ARRAY_SIZE; ++i) {
    addr_elem *elem = &(slh->main_list[i]);
    if (elem->value) {
      if (addr_eq(&(elem->key), key)) {
        return elem;
      }
    }
  }

  if (slh->extra_list) {
    for (size_t i = 0; i < slh->extra_sz; ++i) {
      addr_elem *elem = &(slh->extra_list[i]);
      if (elem->value) {
        if (addr_eq(&(elem->key), key)) {
          return elem;
        }
      }
    }
  }

  return NULL;
}

static const addr_elem *addr_list_get_const(const addr_list_header *slh, const ioa_addr *key) {
  if (!slh || !key) {
    return NULL;
  }

  for (size_t i = 0; i < ADDR_ARRAY_SIZE; ++i) {
    const addr_elem *elem = &(slh->main_list[i]);
    if (elem->value) {
      if (addr_eq(&(elem->key), key)) {
        return elem;
      }
    }
  }

  if (slh->extra_list) {
    for (size_t i = 0; i < slh->extra_sz; ++i) {
      const addr_elem *elem = &(slh->extra_list[i]);
      if (elem->value) {
        if (addr_eq(&(elem->key), key)) {
          return elem;
        }
      }
    }
  }

  return NULL;
}

////////// ADDR MAPS ////////////////////////////////////////////

#define addr_map_index(key) (addr_hash((key)) & (ADDR_MAP_SIZE - 1))

#define get_addr_list_header(map, key) (&((map)->lists[addr_map_index((key))]))

#define ur_addr_map_valid(map) ((map) && ((map)->magic == MAGIC_HASH))

void ur_addr_map_init(ur_addr_map *map) {
  if (map) {
    memset(map, 0, sizeof(ur_addr_map));
    map->magic = MAGIC_HASH;
  }
}

void ur_addr_map_clean(ur_addr_map *map) {
  if (map && ur_addr_map_valid(map)) {
    for (size_t i = 0; i < ADDR_MAP_SIZE; i++) {
      addr_list_free(&(map->lists[i]));
    }
    memset(map, 0, sizeof(ur_addr_map));
  }
}

bool ur_addr_map_put(ur_addr_map *map, ioa_addr *key, ur_addr_map_value_type value) {
  if (!ur_addr_map_valid(map)) {
    return false;
  }

  else {

    addr_list_header *slh = get_addr_list_header(map, key);

    addr_elem *elem = addr_list_get(slh, key);
    if (elem) {
      elem->value = value;
    } else {
      addr_list_add(slh, key, value);
    }

    return true;
  }
}

bool ur_addr_map_get(const ur_addr_map *map, ioa_addr *key, ur_addr_map_value_type *value) {
  if (!ur_addr_map_valid(map)) {
    return false;
  }

  const addr_list_header *slh = get_addr_list_header(map, key);
  const addr_elem *elem = addr_list_get_const(slh, key);
  if (elem) {
    if (value) {
      *value = elem->value;
    }
    return true;
  }
  return false;
}

bool ur_addr_map_del(ur_addr_map *map, ioa_addr *key, ur_addr_map_func delfunc) {
  if (!ur_addr_map_valid(map)) {
    return false;
  }

  addr_list_header *slh = get_addr_list_header(map, key);

  int counter = 0;
  addr_list_remove(slh, key, delfunc, &counter);

  return (counter > 0);
}

void ur_addr_map_foreach(ur_addr_map *map, ur_addr_map_func func) {
  if (ur_addr_map_valid(map)) {
    for (size_t i = 0; i < ADDR_MAP_SIZE; i++) {
      addr_list_header *slh = &(map->lists[i]);
      addr_list_foreach(slh, func);
    }
  }
}

bool ur_addr_map_foreach_arg(ur_addr_map *map, ur_addr_map_func_arg func, void *arg) {
  if (ur_addr_map_valid(map)) {
    for (size_t i = 0; i < ADDR_MAP_SIZE; i++) {
      addr_list_header *slh = &(map->lists[i]);
      if (!addr_list_foreach_arg(slh, func, arg)) {
        return false;
      }
    }
  }
  return true;
}

bool ur_addr_map_foreach_key_arg(ur_addr_map *map, ur_addr_map_key_func_arg func, void *arg) {
  if (ur_addr_map_valid(map)) {
    for (size_t i = 0; i < ADDR_MAP_SIZE; i++) {
      addr_list_header *slh = &(map->lists[i]);
      if (!addr_list_foreach_key_arg(slh, func, arg)) {
        return false;
      }
    }
  }
  return true;
}

size_t ur_addr_map_num_elements(const ur_addr_map *map) {
  if (!ur_addr_map_valid(map)) {
    return 0;
  }

  size_t ret = 0;
  for (size_t i = 0; i < ADDR_MAP_SIZE; i++) {
    const addr_list_header *slh = &(map->lists[i]);
    ret += addr_list_num_elements(slh);
  }

  return ret;
}

size_t ur_addr_map_size(const ur_addr_map *map) {
  if (!ur_addr_map_valid(map)) {
    return 0;
  }

  size_t ret = 0;
  for (size_t i = 0; i < ADDR_MAP_SIZE; i++) {
    const addr_list_header *slh = &(map->lists[i]);
    ret += addr_list_size(slh);
  }

  return ret;
}

////////////////////  STRING LISTS ///////////////////////////////////

typedef struct _string_list {
  struct _string_list *next;
} string_list;

typedef struct _string_elem {
  string_list list;
  ur_string_map_key_type key;
  uint32_t key_size;
  ur_string_map_value_type value;
} string_elem;

typedef struct _string_list_header {
  string_list *list;
} string_list_header;

static size_t string_list_size(const string_list *sl) {
  if (!sl) {
    return 0;
  }
  return 1 + string_list_size(sl->next);
}

static void string_list_free(string_list_header *slh, ur_string_map_func del_value_func) {
  if (slh) {
    string_list *list = slh->list;
    while (list) {
      string_elem *elem = (string_elem *)list;
      string_list *tail = elem->list.next;
      if (elem->key) {
        free(elem->key);
      }
      if (del_value_func && elem->value) {
        del_value_func(elem->value);
      }
      free(elem);
      list = tail;
    }
    slh->list = NULL;
  }
}

static string_list *string_list_add(string_list *sl, const ur_string_map_key_type key, ur_string_map_value_type value) {
  if (!key) {
    return sl;
  }
  string_elem *elem = (string_elem *)malloc(sizeof(string_elem));
  if (!elem) {
    return sl;
  }
  elem->list.next = sl;
  elem->key_size = strlen(key) + 1;
  elem->key = (char *)malloc(elem->key_size);
  if (!elem->key) {
    free(elem);
    return sl;
  }
  memcpy(elem->key, key, elem->key_size);
  elem->value = value;
  return &(elem->list);
}

static string_list *string_list_remove(string_list *sl, const ur_string_map_key_type key,
                                       ur_string_map_func del_value_func, int *counter) {
  if (!sl || !key) {
    return sl;
  }
  string_elem *elem = (string_elem *)sl;
  string_list *tail = elem->list.next;
  if (strcmp(elem->key, key) == 0) {
    free(elem->key);
    if (del_value_func) {
      del_value_func(elem->value);
    }
    free(elem);
    if (counter) {
      *counter += 1;
    }
    sl = string_list_remove(tail, key, del_value_func, counter);
  } else {
    elem->list.next = string_list_remove(tail, key, del_value_func, counter);
  }
  return sl;
}

static string_elem *string_list_get(string_list *sl, const ur_string_map_key_type key) {
  if (!sl || !key) {
    return NULL;
  }

  string_elem *elem = (string_elem *)sl;
  if (strcmp(elem->key, key) == 0) {
    return elem;
  } else {
    return string_list_get(elem->list.next, key);
  }
}

////////// STRING MAPS ////////////////////////////////////////////

#define STRING_MAP_SIZE (1024)

struct _ur_string_map {
  string_list_header lists[STRING_MAP_SIZE];
  uint64_t magic;
  ur_string_map_func del_value_func;
  TURN_MUTEX_DECLARE(mutex)
};

static uint32_t string_hash(const ur_string_map_key_type key) {

  uint8_t *str = (uint8_t *)key;

  uint32_t hash = 0;
  int c = 0;

  while ((c = *str++)) {
    hash = c + (hash << 6) + (hash << 16) - hash;
  }

  return hash;
}

static size_t string_map_index(const ur_string_map_key_type key) { return string_hash(key) % STRING_MAP_SIZE; }

static string_list_header *get_string_list_header(ur_string_map *map, const ur_string_map_key_type key) {
  return &(map->lists[string_map_index(key)]);
}

static bool ur_string_map_init(ur_string_map *map) {
  if (map) {
    memset(map, 0, sizeof(ur_string_map));
    map->magic = MAGIC_HASH;

    TURN_MUTEX_INIT_RECURSIVE(&(map->mutex));

    return true;
  }
  return false;
}

#define ur_string_map_valid(map) ((map) && ((map)->magic == MAGIC_HASH))

ur_string_map *ur_string_map_create(ur_string_map_func del_value_func) {
  ur_string_map *map = (ur_string_map *)malloc(sizeof(ur_string_map));
  if (map == NULL) {
    return NULL;
  } else if (!ur_string_map_init(map)) {
    free(map);
    return NULL;
  } else {
    map->del_value_func = del_value_func;
    return map;
  }
}

bool ur_string_map_put(ur_string_map *map, const ur_string_map_key_type key, ur_string_map_value_type value) {

  if (!ur_string_map_valid(map)) {
    return false;
  }

  string_list_header *slh = get_string_list_header(map, key);
  string_elem *elem = string_list_get(slh->list, key);
  if (elem) {
    if (elem->value != value) {
      if (map->del_value_func) {
        map->del_value_func(elem->value);
      }
      elem->value = value;
    }
    return true;
  }

  slh->list = string_list_add(slh->list, key, value);
  return true;
}

bool ur_string_map_get(ur_string_map *map, const ur_string_map_key_type key, ur_string_map_value_type *value) {

  if (!ur_string_map_valid(map)) {
    return false;
  }

  string_list_header *slh = get_string_list_header(map, key);
  string_elem *elem = string_list_get(slh->list, key);
  if (elem) {
    if (value) {
      *value = elem->value;
    }
    return true;
  } else {
    return false;
  }
}

bool ur_string_map_del(ur_string_map *map, const ur_string_map_key_type key) {

  if (!ur_string_map_valid(map)) {
    return false;
  }

  string_list_header *slh = get_string_list_header(map, key);

  int counter = 0;
  slh->list = string_list_remove(slh->list, key, map->del_value_func, &counter);
  return (counter > 0);
}

void ur_string_map_clean(ur_string_map *map) {
  if (ur_string_map_valid(map)) {
    for (size_t i = 0; i < STRING_MAP_SIZE; ++i) {
      string_list_free(&(map->lists[i]), map->del_value_func);
    }
  }
}

void ur_string_map_free(ur_string_map **map) {
  if (map && ur_string_map_valid(*map)) {
    for (size_t i = 0; i < STRING_MAP_SIZE; ++i) {
      string_list_free(&((*map)->lists[i]), (*map)->del_value_func);
    }
    (*map)->magic = 0;
    TURN_MUTEX_DESTROY(&((*map)->mutex));
    free(*map);
    *map = NULL;
  }
}

size_t ur_string_map_size(const ur_string_map *map) {
  if (!ur_string_map_valid(map)) {
    return 0;
  }

  size_t ret = 0;
  for (size_t i = 0; i < STRING_MAP_SIZE; ++i) {
    ret += string_list_size(map->lists[i].list);
  }
  return ret;
}

bool ur_string_map_lock(const ur_string_map *map) {
  if (ur_string_map_valid(map)) {
    TURN_MUTEX_LOCK((const turn_mutex *)&(map->mutex));
    return true;
  }
  return false;
}

bool ur_string_map_unlock(const ur_string_map *map) {
  if (ur_string_map_valid(map)) {
    TURN_MUTEX_UNLOCK((const turn_mutex *)&(map->mutex));
    return true;
  }
  return false;
}

////////////////////////////////////////////////////////////////
