/*
 *	BIRD Internet Routing Daemon -- Semi-global index of nets
 *
 *	(c) 2023       Maria Matejka <mq@jmq.cz>
 *
 *	Can be freely distributed and used under the terms of the GNU GPL.
 */

#include "lib/birdlib.h"
#include "lib/netindex_private.h"

#define NETINDEX_KEY(n)		(n)->hash, (n)->addr
#define NETINDEX_NEXT(n)	(n)->next
#define NETINDEX_EQ(h,n,i,o)	((h == i) && net_equal(n,o))
#define NETINDEX_FN(h,n)	(h)
#define NETINDEX_ORDER		4 /* Initial */

#define NETINDEX_REHASH		netindex_rehash
#define NETINDEX_PARAMS		/8, *1, 2, 2, 4, 28

HASH_DEFINE_REHASH_FN(NETINDEX, struct netindex);

static void netindex_hash_cleanup(void *netindex_hash);

static struct netindex *
net_lock_revive_unlock(struct netindex_hash_private *hp, struct netindex *i)
{
  if (!i)
    return NULL;

  lfuc_lock_revive(&i->uc);
  lfuc_unlock(&i->uc, hp->cleanup_list, &hp->cleanup_event);
  return i;
}

void
netindex_hash_consistency_check(struct netindex_hash_private *nh)
{
  uint count = 0;
  HASH_WALK(nh->hash, next, i)
  {
    ASSERT_DIE(count < nh->hash.count);
    ASSERT_DIE(nh->block[i->index] == i);
    count++;
  }
  HASH_WALK_END;

  ASSERT_DIE(count == nh->hash.count);
}

/*
 * Index initialization
 */
netindex_hash *
netindex_hash_new(pool *sp, event_list *cleanup_target, u8 type)
{
  DOMAIN(attrs) dom = DOMAIN_NEW(attrs);
  LOCK_DOMAIN(attrs, dom);

  pool *p = rp_new(sp, dom.attrs, "Network index");

  struct netindex_hash_private *nh = mb_allocz(p, sizeof *nh);
  nh->lock = dom;
  nh->pool = p;
  nh->net_type = type;

  nh->slab = net_addr_length[type] ? sl_new(nh->pool, sizeof (struct netindex) + net_addr_length[type]) : NULL;

  HASH_INIT(nh->hash, nh->pool, NETINDEX_ORDER);
  nh->block_size = 128;
  nh->block = mb_allocz(nh->pool, nh->block_size * sizeof (struct netindex *));

  hmap_init(&nh->id_map, nh->pool, 128);

  nh->cleanup_list = cleanup_target;
  nh->cleanup_event = (event) { .hook = netindex_hash_cleanup, nh };

  UNLOCK_DOMAIN(attrs, dom);
  return SKIP_BACK(netindex_hash, priv, nh);
}

static void
netindex_hash_cleanup(void *_nh)
{
  struct netindex_hash_private *nh = _nh;

  DOMAIN(attrs) dom = nh->lock;
  LOCK_DOMAIN(attrs, dom);

  EXPENSIVE_CHECK(netindex_hash_consistency_check(nh));

  uint kept = 0;

  for (uint i = 0; i < nh->block_size; i++)
  {
    struct netindex *ni = nh->block[i];
    if (!ni)
      continue;

    ASSERT_DIE(i == ni->index);

    if (lfuc_finished(&ni->uc))
    {
      HASH_REMOVE2(nh->hash, NETINDEX, nh->pool, ni);
      hmap_clear(&nh->id_map, ni->index);
      nh->block[i] = NULL;

      if (nh->slab)
	sl_free(ni);
      else
	mb_free(ni);
    }
    else
      kept++;
  }

  EXPENSIVE_CHECK(netindex_hash_consistency_check(nh));

  if (kept || !nh->deleted_event)
  {
    UNLOCK_DOMAIN(attrs, dom);
    return;
  }

  ev_postpone(&nh->cleanup_event);

  event *e = nh->deleted_event;
  event_list *t = nh->deleted_target;

  /* Check cleanliness */
  HASH_WALK(nh->hash, next, i)
    bug("Stray netindex in deleted hash");
  HASH_WALK_END;

  /* Pool free is enough to drop everything */
  rp_free(nh->pool);

  /* And only the lock remains */
  UNLOCK_DOMAIN(attrs, dom);
  DOMAIN_FREE(attrs, dom);

  /* Notify the requestor */
  ev_send(t, e);
}

void
netindex_hash_delete(netindex_hash *h, event *e, event_list *t)
{
  NH_LOCK(h, hp);

  EXPENSIVE_CHECK(netindex_hash_consistency_check(nh));

  hp->deleted_event = e;
  hp->deleted_target = t;

  ev_send(hp->cleanup_list, &hp->cleanup_event);
}

/*
 * Private index manipulation
 */
struct netindex *
net_find_index_fragile_chain(struct netindex_hash_private *hp, const net_addr *n)
{
  ASSERT_DIE(n->type == hp->net_type);
  u32 h = net_hash(n);
  return HASH_FIND_CHAIN(hp->hash, NETINDEX, h, n);
}

struct netindex *
net_find_index_fragile(struct netindex_hash_private *hp, const net_addr *n)
{
  ASSERT_DIE(n->type == hp->net_type);

  EXPENSIVE_CHECK(netindex_hash_consistency_check(hp));

  u32 h = net_hash(n);
  return HASH_FIND(hp->hash, NETINDEX, h, n);
}

static struct netindex *
net_find_index_locked(struct netindex_hash_private *hp, const net_addr *n)
{
  return net_lock_revive_unlock(hp, net_find_index_fragile(hp, n));
}

static struct netindex *
net_new_index_locked(struct netindex_hash_private *hp, const net_addr *n)
{
  ASSERT_DIE(!hp->deleted_event);

  u32 i = hmap_first_zero(&hp->id_map);
  hmap_set(&hp->id_map, i);

  struct netindex *ni = hp->slab ?
    sl_alloc(hp->slab) :
    mb_alloc(hp->pool, n->length + sizeof *ni);

  *ni = (struct netindex) {
    .hash = net_hash(n),
    .index = i,
  };
  net_copy(ni->addr, n);

  HASH_INSERT2(hp->hash, NETINDEX, hp->pool, ni);
  while (hp->block_size <= i)
  {
    u32 bs = hp->block_size;
    struct netindex **nb = mb_alloc(hp->pool, bs * 2 * sizeof *nb);
    memcpy(nb, hp->block, bs * sizeof *nb);
    memset(&nb[bs], 0, bs * sizeof *nb);

    mb_free(hp->block);
    hp->block = nb;

    hp->block_size *= 2;
  }

  hp->block[i] = ni;

  return net_lock_revive_unlock(hp, ni);
}


/*
 * Public entry points
 */

void net_lock_index(netindex_hash *h UNUSED, struct netindex *i)
{
//  log(L_TRACE "Lock index %p", i);
  lfuc_lock(&i->uc);
}

void net_unlock_index(netindex_hash *h, struct netindex *i)
{
//  log(L_TRACE "Unlock index %p", i);
  lfuc_unlock(&i->uc, h->cleanup_list, &h->cleanup_event);
}

struct netindex *
net_find_index(netindex_hash *h, const net_addr *n)
{
  NH_LOCK(h, hp);
  return net_find_index_locked(hp, n);
}

struct netindex *
net_get_index(netindex_hash *h, const net_addr *n)
{
  NH_LOCK(h, hp);
  return
    net_find_index_locked(hp, n) ?:
    net_new_index_locked(hp, n);
}

struct netindex *
net_resolve_index(netindex_hash *h, u32 i)
{
  NH_LOCK(h, hp);

  struct netindex *ni = hp->block[i];
  return net_lock_revive_unlock(hp, ni);
}