/*
  Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
  See LICENSE.txt for license information.
*/

/** \file \internal
    Internal netcdf-4 functions.

    This file contains functions for manipulating NCxcache objects.

    Warning: This code depends critically on the assumption that
    |void*| == |uintptr_t|

*/

/* 0 => no debug */
#define DEBUG 0
#define CATCH

/* Define this for debug so that table sizes are small */
#define SMALLTABLE

#ifdef CATCH
#define THROW(x) throw(x)
static void breakpoint(void) {}
static int ignore[] = {0};
static int throw(int x)
{
    int* p;
    if(x != 0) {
	for(p=ignore;*p;p++) {if(x == *p) break;}
	if(*p == 0) breakpoint();
    }
    return x;
}
#else
#define THROW(x) (x)
#endif

#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include <assert.h>

#include "nc4internal.h"
#include "ncexhash.h"
#include "ncxcache.h"

#ifdef SMALLTABLE
/* Keep the table sizes small initially */
#define DFALTTABLESIZE 4
#define DFALTLEAFLEN 4
#else
#define DFALTTABLESIZE 32
#define DFALTLEAFLEN 12
#endif

static void insertafter(NCxnode* current, NCxnode* node);
static void unlinknode(NCxnode* node);

#if DEBUG > 0
void verifylru(NCxcache* cache);

static void
xverify(NCxcache* cache)
{
    verifylru(cache);
}

void
verifylru(NCxcache* cache)
{
    NCxnode* p;
    for(p=cache->lru.next;p != &cache->lru;p=p->next) {
        if(p->next == NULL || p->prev == NULL) {
	    xverify(cache);
	}
    }
}
#endif

/* Locate object by hashkey in an NCxcache */
int
ncxcachelookup(NCxcache* NCxcache, ncexhashkey_t hkey, void** op)
{
    int stat = NC_NOERR;
    uintptr_t inode = 0;
    NCxnode* node = NULL;

    if(NCxcache == NULL) return THROW(NC_EINVAL);
    assert(NCxcache->map != NULL);
    if((stat=ncexhashget(NCxcache->map,hkey,&inode)))
        {stat = THROW(NC_ENOOBJECT); goto done;} /* not present */
    node = (void*)inode;
    if(op) *op = node->content;

done:
    return stat;
}

/* Move object to the front of the LRU list */
int
ncxcachetouch(NCxcache* cache, ncexhashkey_t hkey)
{
    int stat = NC_NOERR;
    uintptr_t inode = 0;
    NCxnode* node = NULL;

    if(cache == NULL) return THROW(NC_EINVAL);
    if((stat=ncexhashget(cache->map,hkey,&inode)))
        {stat = THROW(NC_ENOOBJECT); goto done;} /* not present */
    node = (void*)inode;
    /* unlink */
    unlinknode(node);
    /* Relink into front of chain */
    insertafter(&cache->lru,node);
#if DEBUG > 0
verifylru(cache);
#endif

done:
    return stat;
}

/* Add object to the cache */
int
ncxcacheinsert(NCxcache* cache, const ncexhashkey_t hkey, void* o)
{
    int stat = NC_NOERR;
    uintptr_t inode = 0;
    NCxnode* node = NULL;

    if(cache == NULL) return THROW(NC_EINVAL);
    
#ifndef NCXUSER
    node = calloc(1,sizeof(NCxnode));
#else
    node = (NCxnode*)o;
#endif
    node->content = o; /* Cheat and make content point to the node part*/
    inode = (uintptr_t)node;
    stat = ncexhashput(cache->map,hkey,inode);
    if(stat)
	goto done;
    /* link into the LRU chain at front */
    insertafter(&cache->lru,node);
#if DEBUG > 0
verifylru(cache);
#endif
    node = NULL;
done:
#ifndef NCXUSER
    if(node) nullfree(node);
#endif
    return THROW(stat);
}

/* Remove object from the index;*/
int
ncxcacheremove(NCxcache* cache, ncexhashkey_t hkey, void** op)
{
    int stat = NC_NOERR;
    uintptr_t inode = 0;
    NCxnode* node = NULL;

    if(cache == NULL) return THROW(NC_EINVAL);

    /* Remove from the hash map */
    if((stat=ncexhashremove(cache->map,hkey,&inode)))
        {stat = NC_ENOOBJECT; goto done;} /* not present */
    node = (NCxnode*)inode;
    /* unlink */
    unlinknode(node);
#if DEBUG > 0
verifylru(cache);
#endif
    if(op) {
        *op = node->content;
    }
#ifndef NCXUSER
    nullfree(node);
#endif

done:
    return THROW(stat);
}

/* Free a cache */
void
ncxcachefree(NCxcache* cache)
{
    NCxnode* lru = NULL;

    if(cache == NULL) return;
    lru = &cache->lru;

#ifndef NCXUSER
    {
    NCxnode* p = NULL;
    NCxnode* next = NULL;
    /* walk the lru chain */
    next = NULL;
    for(p=lru->next;p != lru;) {
	next = p->next;
	nullfree(p);
	p = next;
    }
    }
#endif

    lru->next = (lru->prev = lru); /*reset*/
    ncexhashmapfree(cache->map);
    free(cache);
}

/* Create a new cache holding at least size objects */
int
ncxcachenew(size_t leaflen, NCxcache** cachep)
{
    int stat = NC_NOERR;
    NCxcache* cache = NULL;

    if(leaflen == 0) leaflen = DFALTLEAFLEN;

    cache = calloc(1,sizeof(NCxcache));
    if(cache == NULL)
        {stat = NC_ENOMEM; goto done;}
    cache->map = ncexhashnew(leaflen);
    if(cache->map == NULL)
        {stat = NC_ENOMEM; goto done;}
    cache->lru.next = &cache->lru;
    cache->lru.prev = &cache->lru;
    if(cachep) {*cachep = cache; cache = NULL;}

done:
    ncxcachefree(cache);
    return THROW(stat);
}

void
ncxcacheprint(NCxcache* cache)
{
    int i;
    NCxnode* p = NULL;

    fprintf(stderr,"NCxcache: lru=");
    fprintf(stderr,"{");
    for(i=0,p=cache->lru.next;p != &cache->lru;p=p->next,i++) {
	if(i>0) fprintf(stderr,",");
	fprintf(stderr,"%p:%p",p,p->content);
    }
    fprintf(stderr,"}\n");
    ncexhashprint(cache->map);
}

void*
ncxcachefirst(NCxcache* cache)
{
    if(cache == NULL) return NULL;
    if(ncexhashcount(cache->map) == 0)
        return NULL;
    return cache->lru.next->content;
}

void*
ncxcachelast(NCxcache* cache)
{
    if(cache == NULL) return NULL;
    if(ncexhashcount(cache->map) == 0)
        return NULL;
    return cache->lru.prev->content;
}

/* Insert node after current */
static void
insertafter(NCxnode* current, NCxnode* node)
{
    NCxnode* curnext = current->next;
    current->next = node;
    node->prev = current;
    node->next = curnext;
    curnext->prev = node;
}

/* Remove node from chain */
static void
unlinknode(NCxnode* node)
{
    NCxnode* next;
    NCxnode* prev;
    assert(node != NULL);
    next = node->next;
    prev = node->prev;
    /* repair the chain */
    next->prev = prev;
    prev->next = next;
    node->next = node->prev = NULL;
}


ncexhashkey_t
ncxcachekey(const void* key, size_t size)
{
    return ncexhashkey((unsigned char*)key,size);
}

