/* $Id$ 
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more 
 * contributor license agreements. See the NOTICE file distributed with  
 * this work for additional information regarding copyright ownership. 
 * The ASF licenses this file to you under the Apache License, Version  
 * 2.0 (the "License"); you may not use this file except in compliance  
 * with the License. You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */ 

/* 
   hashtable.c -- implementation of an underlying hashtable.
   provided herein are implementations for the jenkins hashtable APIs.
*/

#include "etchhash.h"
#include "etchmem.h"
#include "etchrun.h"
#include "etchmutex.h"
#include "jenkhtab.h"
#include "jenklook.h"


/**
 * implementation of etch_hashtable.insert() for the jenkins hashtable. 
 * key and data are pointers to memory owned by the caller. The hashtable does 
 * not make copies of this data. The caller is responsible for freeing said 
 * memory, however note that etch_hashtable.clear() frees this memory.
 * key cannot be null but data can be null.  
 * datalen parameter is ignored for this implementation.
 * if out is non_null, *out is assumed to point at a etch_hashitem struct,
 * and this struct is populated with pointers to the inserted item.
 * result is zero if OK, otherwise -1;
 */
int jenkins_insert(void* realtable, void* key, const int keylen, 
    void* data, const int datalen, void* in, void** out) 
{
    int  result = 0;
    etch_hashtable* ht = (etch_hashtable*) in; 
    mapcallback sync = ht && ht->synchook && ht->synclock? ht->synchook: NULL;
    if (!realtable || !key || !keylen) return -1;

    if (sync) /* acquire lock */
        sync(ETCH_SYNC_SET_, ht->synclock);

    if (FALSE == hadd((htab*)realtable, key, keylen, data)) /* jenkhash.lib */
        result = -1;    /* key already exists most likely */
    else
    if (out)
     {  etch_hashitem* outentry = (etch_hashitem*) *out; 
        outentry->key   = hkey  ((htab*)realtable);
        outentry->value = hstuff((htab*)realtable);
        outentry->hash  = ((htab*)realtable)->ipos->hval;         
     }

    if (sync) /* release lock */
        sync(ETCH_SYNC_REL_, ht->synclock);

    return result;  
}


/**
 * implementation of etch_hashtable.inserth() for the jenkins hashtable. 
 * key and data are pointers to memory owned by the caller. the hashtable does 
 * not make copies of this data. the caller is responsible for freeing said 
 * memory, however note that etch_hashtable.clear() frees this memory.
 * key cannot be null but data can be null. 
 * key object is expected to contain its hashkey value in its first 4 bytes. 
 * jenkins will use this value, rather than compute a hash value.
 * if out is non_null, *out is assumed to point at a etch_hashitem struct,
 * and this struct is populated with pointers to the inserted item.
 * result is zero if OK, otherwise -1;
 */
int jenkins_inserth(void* realtable, void* key, void* data, void* in, void** out) 
{
    int  result = 0;
    etch_hashtable* ht = (etch_hashtable*) in; 
    mapcallback sync = ht && ht->synchook && ht->synclock? ht->synchook: NULL;
    if (!realtable || !key) return -1;

    if (sync) /* acquire lock */
        sync(ETCH_SYNC_SET_, ht->synclock);

    if (FALSE == haddx((htab*)realtable, key, data)) /* jenkhash.lib */
        result = -1;    /* key already exists most likely */
    else
    if (out)
     {  etch_hashitem* outentry = (etch_hashitem*) *out; 
        outentry->key   = hkey  ((htab*)realtable);
        outentry->value = hstuff((htab*)realtable);
        outentry->hash  = ((htab*)realtable)->ipos->hval;         
     }

    if (sync) /* release lock */
        sync(ETCH_SYNC_REL_, ht->synclock);

    return result;  
}


/**
 * implementation of etch_hashtable.find() for the jenkins hashtable. 
 * moves the current position on the underlying table to that of supplied key.
 * if out is non_null, *out is assumed to point at a etch_hashitem struct,
 * and this struct is populated with pointers to the located item's key and value.
 * result is zero if OK, otherwise -1;
 */
int jenkins_find(void* realtable, void* key, const int keylen, 
    void* in, void** out) 
{
    int  result = 0;
    etch_hashtable* ht = (etch_hashtable*) in; 
    mapcallback sync = ht && ht->synchook && ht->synclock? ht->synchook: NULL;
    if (!realtable || !key || !keylen) return -1;

    if (sync) /* acquire lock */
        sync(ETCH_SYNC_SET_, ht->synclock);

    if (FALSE == hfind((htab*)realtable, (unsigned char*)key, keylen))  
        result = -1;
    else
    if (out)
    {   etch_hashitem* outentry = (etch_hashitem*) *out; 
        outentry->key   = hkey  ((htab*)realtable);
        outentry->value = hstuff((htab*)realtable);
        outentry->hash  = ((htab*)realtable)->ipos->hval; 
    }

    if (sync) /* release lock */
        sync(ETCH_SYNC_REL_, ht->synclock);

    return result;  
}


/**
 * implementation of etch_hashtable.findh() for the jenkins hashtable. 
 * Implements a find by hashed key, otherwise see comments for find().
 * result is zero if OK, otherwise -1;
 */
int jenkins_findh(void* realtable, const unsigned int hashed, 
    void* in, void** out) 
{
    int result = 0;
    etch_hashtable* ht = (etch_hashtable*) in; 
    mapcallback sync = ht && ht->synchook && ht->synclock? ht->synchook: NULL;
    if (!realtable || !hashed) return -1;

    if (sync) /* acquire lock */
        sync(ETCH_SYNC_SET_, ht->synclock);

    if (FALSE == hfindx((htab*)realtable, hashed)) 
        result = -1;
    else     
    if (out)
    {   char* tkey = hkey  ((htab*)realtable);
        void* data = hstuff((htab*)realtable);
        etch_hashitem* outentry = (etch_hashitem*) *out; 
        outentry->key   = tkey;
        outentry->value = data;
    }

    if (sync) /* release lock */
        sync(ETCH_SYNC_REL_, ht->synclock);

    return result;  
}


/**
 * implementation of etch_hashtable.first() for the jenkins hashtable. 
 * moves the current position on the underlying table to that of the first item.
 * If out is non_null, *out is assumed to point at a etch_hashitem struct,
 * and this struct is populated with pointers to the located item's key and value.
 * in parameter is ignored for this implementation.
 * result is zero if OK, otherwise -1, indicating bad params or an empty table.
 * @note this method is NOT SYNCHRONIZED, since in most or all cases it is invoked 
 * only during iteration of the map, and in that case the map is locked explicitly 
 * by the caller (hashtable_setlock()), and the mutex may not support nesting. 
 * if there is a need to synch it otherwise, wrap the call as follows:
 *    map->synchook(ETCH_SYNC_SET_, map->synclock);
 *    map->first(...);
 *    map->synchook(ETCH_SYNC_REL_, map->synclock);
 */
int jenkins_first(void* realtable, void* in, void** out) 
{
    int result = 0;

    if (FALSE == hfirst((htab*)realtable))  
        result = -1; /* table is empty most likely */
    else            
    if (out)
    {   char* tkey = hkey  ((htab*)realtable);
        void* data = hstuff((htab*)realtable);
        etch_hashitem* outentry = (etch_hashitem*) *out; 
        outentry->key   = tkey;
        outentry->value = data;
    }

    return result;  
}


/**
 * implementation of etch_hashtable.next() for the jenkins hashtable. 
 * Moves the current position on the underlying table to that of the next item 
 * in the table. If out is non_null, *out is assumed to point at a etch_hashitem,
 * and this struct is populated with pointers to the located item's key and value.
 * in parameter is ignored for this implementation.
 * result is zero if OK, otherwise -1, indicating either bad params, or that 
 * there are no more entries, in which case the current position will have wrapped
 * to the first item, if any entries in fact still remain.
 * @note this method is NOT SYNCHRONIZED, since in most or all cases it is invoked 
 * only during iteration of the map, and in that case the map is locked explicitly 
 * by the caller (hashtable_setlock()), and the mutex may not support nesting. 
 * if there is a need to synch it otherwise, wrap the call as follows:
 *    map->synchook(ETCH_SYNC_SET_, map->synclock);
 *    map->next(...);
 *    map->synchook(ETCH_SYNC_REL_, map->synclock);
 */
int jenkins_next(void* realtable, void* in, void** out) 
{
    int is_next_found = 0, is_table_empty = 0;
    char* tkey = NULL; 
    void* data = NULL;
    if (!realtable) return -1;

    /* hnext returns 1 if there is a next entry, or 0 if there is no next entry
     * and the position has wrapped to the beginning of the table. However if  
     * the table is now empty, there is no current position, and so we test for
     * that condition before attempting to reference the current position.
     */
    is_next_found  = hnext((htab*)realtable);  /* jenkhash.h */
    is_table_empty = NULL == ((htab*)realtable)->ipos;

    if (out)  /* return data at current position if requested */
    {   etch_hashitem* outentry = (etch_hashitem*) *out; 

        if (!is_table_empty)
        {   tkey = hkey  ((htab*)realtable);
            data = hstuff((htab*)realtable);
        }
        outentry->key   = tkey;
        outentry->value = data;
    }

    return is_next_found? 0: -1; 
}


/**
 * implementation of etch_hashtable.current() for the jenkins hashtable. 
 * retrieves data for the entry at the current hashtable position.
 * *out is assumed to point at a etch_hashitem struct; this struct is populated 
 * with pointers to the current item's key and value.
 * in parameter is ignored for this implementation.
 * result is zero if OK, otherwise -1, indicating bad params or an empty table.
 * @note this method is NOT SYNCHRONIZED, since it is not meaningful for a shared
 * hashtable (current position will change with every operation).  
 * if there is a need to synch it, wrap the call as follows:
 *    map->synchook(ETCH_SYNC_SET_, map->synclock);
 *    map->current(...);
 *    map->synchook(ETCH_SYNC_REL_, map->synclock);
 */
int jenkins_current(void* realtable, void* in, void** out) 
{
    unsigned hash = 0;
    char* tkey = NULL; 
    void* data = NULL;
    etch_hashitem* outentry = NULL; 
    if (!realtable || !out || !((htab*)realtable)->count) return -1;

    tkey = hkey  ((htab*)realtable);    
    data = hstuff((htab*)realtable);
    hash =((htab*)realtable)->ipos->hval;

    outentry = (etch_hashitem*) *out; 
    outentry->key   = tkey;
    outentry->value = data;
    outentry->hash  = hash; 

    return tkey? 0: -1;
}


/**
 * Implementation of etch_hashtable.remove() for the jenkins hashtable. 
 * Frees the entry for the key supplied , but neither the key nor the value, 
 * are freed, recall that neither of these is a copy but are instead pointers.
 * To actually free memory for these items, pass etch_hashitem** &out),
 * and you can then free(out->key), and free(out->value), at your leisure. 
 * Result is zero if OK, otherwise -1;
 */
int jenkins_remove(void* realtable, void* key, const int keylen, 
    void* in, void** out) 
{
    int result = 0;
    etch_hashtable* ht = (etch_hashtable*) in; 
    mapcallback sync = ht && ht->synchook && ht->synclock? ht->synchook: NULL;
    if (!realtable || !key || !keylen) return -1;

    if (sync) /* acquire lock */
        sync(ETCH_SYNC_SET_, ht->synclock);

    if (FALSE == hfind((htab*)realtable, key, keylen)) /* locate entry */
        result = -1;                    /* key nonexistent most likely */
    else
    {   if (out)           /* save off the entry contents if requested */
        {   etch_hashitem* outentry = (etch_hashitem*) *out; 
            outentry->key   = hkey  ((htab*)realtable);
            outentry->value = hstuff((htab*)realtable);
            outentry->hash  = ((htab*)realtable)->ipos->hval;   
        }

        hdel((htab*)realtable);    /* delete entry at current position */
    } 

    if (sync) /* release lock */
        sync(ETCH_SYNC_REL_, ht->synclock);

    return result;  
}


/**
 * jenkins_removeh()
 */
int jenkins_removeh(void* realtable, const unsigned key, void* in, void** out) 
{
    int result = 0;
    etch_hashtable* ht = (etch_hashtable*) in; 
    mapcallback sync = ht && ht->synchook && ht->synclock? ht->synchook: NULL;
    if (!realtable || !key) return -1;

    if (sync) /* acquire lock */
        sync(ETCH_SYNC_SET_, ht->synclock);
 
    if (FALSE == hfindx((htab*)realtable, key)) /* locate entry */
        result = -1;
    else  
    {   if (out)
        {   etch_hashitem* outentry = (etch_hashitem*) *out; 
            outentry->key   = hkey  ((htab*)realtable);
            outentry->value = hstuff((htab*)realtable);
        }

        hdel((htab*)realtable);    /* delete entry at current position */
    }

    if (sync) /* release lock */
        sync(ETCH_SYNC_REL_, ht->synclock);

    return result;  
}



/**
 * implementation of etch_hashtable.clear() for the jenkins hashtable. 
 * empties the table and, if requested, frees memory for keys/and/or values. 
 * out parameter is ignored for this implementation.
 * Result is count of table entries freed, or -1 if error.
 * Use the freeuser parameter with care. recall that the hashtable stores
 * pointers to keys and data, plus key length. If user did not allocate each
 * key separately then setting freeuser would cause a crash. for example,  
 * if I used a vector of keys and a vector of key lengths; setting freeuser
 * would ask to free (key length) bytes at some vector offset, obviously
 * an error. also, currently freeuser does not check the memtable, so if
 * allocations are being tracked, freeuser should not be used. we could
 * change this code to query the memtable first, or alternatively, change
 * etch_free to not complain about allegedly missing memtable entries.
 */
int jenkins_clear (void* realtable, const int freekey, const int freeval,
    void* in, void** out) 
{
    int   freecount = 0, currcount = 0, freehandled = 0;
    int   is_static_keys = 0, is_static_values = 0, is_etch_free = 0;
    mapcallback callback = NULL;
    char* key, *value;
    htab* jenktable;
    etch_hashtable* ht = (etch_hashtable*) in; 
    mapcallback sync = ht && ht->synchook && ht->synclock? ht->synchook: NULL;
    if (!(jenktable = (htab*)realtable)) return -1;

    if (sync) /* acquire lock */
        sync(ETCH_SYNC_SET_, ht->synclock);

    if (ht) 
    {   is_static_keys   = ht->is_readonly_keys;
        is_static_values = ht->is_readonly_values;
        is_etch_free     = ht->is_tracked_memory;
        callback         = ht->freehook;
    }   
                                           
    while (0 < (currcount = hcount(jenktable)))       
    {         
        key   = hkey(jenktable);    /* free entry's key if asked */
        value = hstuff(jenktable);
                                    /* optional callback to handle free */
        freehandled = callback? callback(key, value): FALSE; 

        if (freekey && !is_static_keys && !freehandled)  
            if  (is_etch_free) 
                 etch_free(key);
            else free(key); 
                        
        if (freeval && !is_static_values && !freehandled)
            if  (is_etch_free) 
                 etch_free(value);
            else free(value); 
    
        hdel(jenktable);  /* free current table slot */                  
        freecount++;
     }

    if (sync) /* release lock */
        sync(ETCH_SYNC_REL_, ht->synclock);

    return freecount;     /* return count of items freed */
}


/**
 * implementation of etch_hashtable.count() for the jenkins hashtable. 
 * in and out parameters are ignored for this implementation.
 * result is current number of table entries, or -1 if error.
 */
int jenkins_count(void* realtable, void* in, void** out) 
{                                   
    const int count = realtable? ((htab*)realtable)->count: -1;   
    return count;  
}


/**
 * implementation of etch_hashtable.size() for the jenkins hashtable. 
 * in and out parameters are ignored for this implementation.
 * result is current maximum number of table entries, or -1 if error.
 */
int jenkins_size(void* realtable, void* in, void** out) 
{                                  
    const int count = realtable? 1 << ((htab*)realtable)->logsize: -1;   
    return count;  
}


/**
 * implementation of etch_hashtable.stats() for the jenkins hashtable. 
 * in and out parameters are ignored for this implementation.
 * result is current maximum number of table entries, or -1 if error.
 */
int jenkins_stats(void* realtable, void* in, void** out) 
{                                 
    if (realtable) hstat((htab*)realtable);     
    return realtable? 0: -1;  
}


/**
 * jenkins_hash
 * implementation of etch_hashtable.hash() for the jenkins hashtable. 
 * priorhash is result of the previous operation, or any arbitrary value.
 * in and out parameters are ignored for this implementation. result is a 
 * hash of the supplied key, as used by the jenkins hashtable, or zero
 * if parameters were in error.
 * author's comments: If you need less than 32 bits, use a bitmask.  
 * for example, if you need only 10 bits, do h = (h & hashmask(10)),
 * in which case, the hash table should have hashsize(10) elements.
 * if you are hashing n strings (unsigned char**)k, do it like this:
 * for (i=0, h=0; i < n; ++i) h = lookup(k[i], len[i], h);   
 */
int jenkins_hash(void* realtable, char* key, const int keylen, 
                         const int priorhash, void* in, void** out)  
{                                  
    if (!realtable || !key || keylen < 1 || keylen > MAX_ETCHHASHKEY) return 0;    
    return lookup(key, keylen, priorhash); 
}


/**
 * jenkins_hashx
 * see comments at jenkins_hash
 */
int jenkins_hashx(char* key, const int keylen, const int priorhash)  
{                                 
    if (!key || keylen < 1 || keylen > MAX_ETCHHASHKEY) return 0;    
    return lookup(key, keylen, priorhash); 
}


/* - - - - - - - - - - - - - - - - - - - - - 
 * constructors, destructors
 * - - - - - - - - - - - - - - - - - - - - -
 */

/**
 * constructor for a hashtable implementation. implements iterable.
 * creates the underlying hashtable and returns a populated etch_hashtable
 * interface to it. initialsize is the number of items the hashtable can hold 
 * before it reallocates itself. note that this value may be altered by the
 * implementation, e.g. if it is out of range or if it is not a power of 2.
 */
etch_hashtable* new_hashtable(const int initialsize)
{
    etch_hashtable* hashtable = ctor_jenkins_hashtable(initialsize);

    new_iterable(&hashtable->iterable, NULL, hashtable_iterable_first, 
        hashtable_iterable_next, hashtable_iterable_has_next); 

    hashtable->is_readonly_keys   = HASHTABLE_DEFAULT_READONLY_KEYS;
    hashtable->is_readonly_values = HASHTABLE_DEFAULT_READONLY_VALUES;
    hashtable->is_tracked_memory  = HASHTABLE_DEFAULT_TRACKED_MEMORY;
    hashtable->content_type       = HASHTABLE_DEFAULT_CONTENT_TYPE;

    return hashtable; 
}


/**
 * constructor for etch_set, which is a hashtable containing object keys
 * and null values
 */
etch_set* new_set (const int initialsize)
{
    etch_set* newset = new_hashtable(initialsize);
    newset->content_type = ETCHHASHTABLE_CONTENT_OBJECT_NONE;
    newset->is_readonly_values = TRUE;
    return newset;
}


/**
 * constructor for the etch_hashtable interface. constructs and initializes 
 * the interface shell, but not the underlying hashtable.
 */
etch_hashtable* new_etch_hashtable()
{
    etch_hashtable* newmap = 0;
    /* we do not track the allocation of the tracking hashtable      
     * since the tracking table of course does not yet exist. 
     */
    if  (is_memtable_instance)
         newmap = malloc(sizeof(etch_hashtable));        
    else newmap = etch_malloc(sizeof(etch_hashtable), ETCHTYPEB_HASHTABLE);

    memset(newmap, 0, sizeof(etch_hashtable)); 
    newmap->obj_type = ETCHTYPEB_HASHTABLE;
    newmap->class_id = CLASSID_HASHTABLE;
    newmap->get_hashkey = defgethashkey;
    newmap->get_hashkey((objmask*)newmap);  
    return newmap; 
}


/**
 * destructor for a hashtable implementation.
 * is_free_keys parameter asks that memory pointed to by hashbucket keys be freed.
 * Use this with care, since this obviously requires that you have malloc'ed keys 
 * individually, and did not, for example, hash your keys out of a memory vector, 
 * static variables, or the like. is_free_values likewise for the table content. 
 * The readonly flags set on the hashtable take precedence over either. 
 * Use of either of course means your pointers to content will now be dangling.  
 */
int destroy_hashtable(etch_hashtable* map, const int is_free_keys, const int is_free_values)
{
    int is_free_keymem = 0, is_free_valmem = 0;
    if (NULL == map) return -1;

    if (map->refcount > 0)        /* if object is refcounted */                                     
        if (--map->refcount > 0) /* destroy only if last ref */
            return -1;  

    if (map->synchook) 
        if (0 != map->synchook(ETCH_SYNC_TRY_, map->synclock))
            return -1;          /* disallow multiple destroy */
    
    if (!is_etchobj_static_content(map))  
    {
        is_free_keymem = !map->is_readonly_keys   && is_free_keys;
        is_free_valmem = !map->is_readonly_values && is_free_values;

        /* free all buckets, and also contents only if is_free_contents is set */
        if (map->realtable && map->vtab && !is_etchobj_static_content(map)) 
        {    
            map->vtab->clear(map->realtable, is_free_keymem, is_free_valmem, map, 0);  
            map->vtab->hdestroy(map->realtable, map, 0);
        }

        if (map->synchook) /* release lock */
        {
            map->synchook(ETCH_SYNC_REL_, map->synclock);                       
            if (!is_etchobj_static_content(map))
                map->synchook(ETCH_SYNC_DEL_, map->synclock);
        }
    }

    if (!is_etchobj_static_shell(map))
        if  (is_memtable_instance) /* free the etch_hashtable object */
             free(map);
        else etch_free(map);  

    return 0;
}


/**
 * implementation of etch_hashtable.destroy() for the jenkins hashtable.
 * destroys the table, but not the memory allocated for the individual item 
 * keys and values. use clear(), not destroy(), for that purpose.
 * out parameter is ignored for this implementation.
 * result is zero if OK, otherwise -1;
 */
int jenkins_destroy(void* realtable, void* in, void** out)
{
    etch_hashtable* ht = (etch_hashtable*) in; 
    mapcallback sync = ht && ht->synchook && ht->synclock? ht->synchook: NULL;
    if (!realtable) return -1;

    if (sync) 
        if (0 != sync(ETCH_SYNC_TRY_, ht->synclock))
            return -1;  /* disallow multiple destroy */

    if (((htab*)realtable)->table)
        hdestroy((htab*) realtable);  /* jenkhash.lib */

    if (sync) /* release lock */
        sync(ETCH_SYNC_REL_, ht->synclock);
    
    return 0;
}


/**
 * implementation of etch_hashtable.create() for the jenkins hashtable.
 * this is the constructor for the underlying hashtable.
 * we receive initial size in items and convert this to bit width.
 * If initial size supplied is not a power of 2 we make it so.
 * jenkins takes a log2 value as size, e.g. size 6 means size is 6 bits wide = 64.
 * returns in *out, a pointer to a jenkins htab struct describing the underlying table.
 * in parameter is ignored for this implementation.
 * result is zero if OK, otherwise -1;
 */
int jenkins_create(const int initialsize_items, void* in, void** out)
{
    htab* hashtable = NULL;
    int initialsize_bits_plus1 = 0, initialsize, divby2;
    if (out == NULL) return -1; 

    if  (initialsize_items <= 0)
         initialsize = ETCH_DEFAULT_HASHTABLE_SIZE;
    else
    if  (initialsize_items < MIN_INITIAL_HASHTABLE_SIZE)
         initialsize = MIN_INITIAL_HASHTABLE_SIZE;
    else initialsize = initialsize_items;

    if  (initialsize > MAX_INITIAL_HASHTABLE_SIZE) 
         initialsize = MAX_INITIAL_HASHTABLE_SIZE;

    for (divby2 = initialsize; divby2; divby2 >>= 1)      
         initialsize_bits_plus1++; 

    hashtable = hcreate(initialsize_bits_plus1 - 1); /* jenkhash.lib */

    *out = hashtable;
    return hashtable? 0: -1;
}


/**
 * destroy_hashtable_default()
 * default destructor for jenkins hashtable
 */
int destroy_hashtable_default(etch_hashtable* map)
{
    destroy_hashtable(map, !map->is_readonly_keys, !map->is_readonly_values);
    return 0;
}


/**
 * clone_hashtable_default()
 * default copy constructor for jenkins hashtable.
 * we won't do an implementation at this level, since we would need to also clone
 * content, and only the instantiator knows key/value sizes
 */
etch_hashtable* clone_hashtable_default(etch_hashtable* map)
{
    return NULL;
}


/**
 * ctor_jenkins_hashtable()
 * constructor for jenkins hashtable interface. 
 * populates interface implementation methods and creates the underlying hashtable.
 * returns pointer to etch_hashtable, or NULL if table could not be created.
 */
etch_hashtable* ctor_jenkins_hashtable(const int initialsize_items)
{
    htab* jenkins_hashtable = NULL;
    etch_hashtable* hashtable = NULL;
    i_etch_hashtable* vtab = NULL;
    const unsigned short CLASS_ID = CLASSID_HASHTABLE_VTAB;
    int result = 0, is_just_cached = FALSE;

    hashtable = new_etch_hashtable(); 

    vtab = cache_find(get_vtable_cachehkey(CLASS_ID), 0);

    if(!vtab)  
    { 
        vtab = new_vtable(hashtable->vtab, sizeof(i_etch_hashtable), CLASS_ID);
        vtab->create   = jenkins_create;
        vtab->hdestroy = jenkins_destroy; 
        vtab->insert   = jenkins_insert;
        vtab->inserth  = jenkins_inserth;
        vtab->find     = jenkins_find;
        vtab->findh    = jenkins_findh;
        vtab->first    = jenkins_first;
        vtab->next     = jenkins_next;
        vtab->current  = jenkins_current;
        vtab->remove   = jenkins_remove;
        vtab->removeh  = jenkins_removeh;
        vtab->clear    = jenkins_clear;
        vtab->count    = jenkins_count;
        vtab->size     = jenkins_size;
        vtab->stats    = jenkins_stats;

        cache_insert(vtab->hashkey, vtab, FALSE);
        is_just_cached = TRUE;
    } 
 
    hashtable->vtab = vtab;

    /* create the underlying real hashtable */
    result = vtab->create(initialsize_items, NULL, &jenkins_hashtable);
    hashtable->realtable = jenkins_hashtable;

    hashtable->destroy = destroy_hashtable_default;
    hashtable->clone   = clone_hashtable_default;

    if (result == -1)   
    {   if (is_just_cached)
            cache_del(CLASS_ID);
        hashtable->destroy(hashtable);
    }     

    return hashtable; 
}


/*
 * new_systemhashtable()
 * for such a hashtable we will not use the vtab interface
 * but rather will call the implementation methods directly.
 */
etch_hashtable* new_systemhashtable(const int initialsize_items)
{
    int result = 0;
    htab* jenkins_hashtable = NULL;
    etch_hashtable* hashtable = 0;
  
    hashtable = malloc(sizeof(etch_hashtable)); 
    memset(hashtable, 0, sizeof(etch_hashtable)); 

    result = jenkins_create(initialsize_items, NULL, &jenkins_hashtable);
    hashtable->realtable = jenkins_hashtable;

    if (result == 0) 
        new_iterable(&hashtable->iterable, NULL, hashtable_iterable_first, 
            hashtable_iterable_next, hashtable_iterable_has_next); 

    else /* some problem creating hashtable */  
    {
        delete_systemhashtable(hashtable);
        hashtable = NULL;
    }
 
    return hashtable; 
}


/*
 * delete_systemhashtable()
 * delete an untracked hashtable
 */
void delete_systemhashtable(etch_hashtable* hashtable)
{
   if (!hashtable) return;
   jenkins_destroy(hashtable->realtable, NULL, NULL);
   free(hashtable);
}


/* - - - - - - - - - - - - - - - - - - - - - 
 * hashtable synchronization
 * - - - - - - - - - - - - - - - - - - - - -
 */

/*
 * hashtable_defsynchook()
 * hashtable synchronization hook usable for most purposes.
 * to enable synchronization, set map.synchook to this function, 
 * and set map.synclock to an instantiated mutex.
 */
int hashtable_defsynchook(void* action, void* mutex)
{
    /* all the casting is to quash pointer cast warnings */
    return etchmutex_hookproc((int)(((size_t) action) & 0xf), mutex);
}


/* 
 * hashtable_getlock()
 * explicitly set this map's synchronization lock, waiting if unavailable.
 * this should be used only for locking a map prior to iterating the map.   
 * for synchronization of map operations, the presence of map.synchook  
 * and map.synclock is sufficient. 
 */
int hashtable_getlock (etch_hashtable* map)
{
    ETCH_ASSERT(map && map->synchook && map->synclock);
    return map->synchook(ETCH_SYNC_SET_, map->synclock);
}


/* 
 * hashtable_trylock()
 * explicitly set this map's synchronization lock, failing if unavailable.
 * this should be used only for locking a map prior to iterating the map.   
 * for synchronization of map operations, the presence of map.synchook  
 * and map.synclock is sufficient. 
 */
int hashtable_trylock (etch_hashtable* map)
{
    ETCH_ASSERT(map && map->synchook && map->synclock);
    return map->synchook(ETCH_SYNC_TRY_, map->synclock);
}

 
/* 
 * hashtable_rellock()
 * release explicitly set this map's synchronization lock.
 * this should be used only for unlocking a map after iterating the map.   
 * for synchronization of map operations, the presence of map.synchook  
 * and map.synclock is sufficient. 
 */
int hashtable_rellock (etch_hashtable* map) 
{
    ETCH_ASSERT(map && map->synchook && map->synclock);
    return map->synchook(ETCH_SYNC_REL_, map->synclock);
}


/* - - - - - - - - - - - - - - - - - - - - - 
 * i_iterable implementations
 * - - - - - - - - - - - - - - - - - - - - -
 */

/*
 * hashtable_iterable_first() 
 * i_iterable first() implementation
 */
int hashtable_iterable_first(etch_iterator* iter)
{
    etch_hashtable* hash = NULL;
    etch_hashitem   hashbucket, *outentry = &hashbucket;
    if (!iter || !iter->collection) return -1;
    iter->current_key = iter->current_value = NULL;
    hash = iter->collection;

    if (-1 == hash->vtab->first(hash->realtable, 0, &outentry))
        return -1;

    iter->current_key   = outentry->key;
    iter->current_value = outentry->value;     
    iter->ordinal++;
    return 0;
}


/*
 * hashtable_iterable_next() 
 * i_iterable next() implementation
 * functions as first() if there is no current position.
 */
int hashtable_iterable_next(etch_iterator* iter)
{
    etch_hashtable* hash = NULL;
    etch_hashitem   hashbucket, *outentry = &hashbucket;
    if (!iter || !iter->collection || !iter->ordinal) return -1;
    iter->current_key = iter->current_value = NULL;
    hash = iter->collection;

    if (-1 == hash->vtab->next(hash->realtable, 0, &outentry))   
        return -1;

    iter->current_key   = outentry->key;
    iter->current_value = outentry->value;     
    iter->ordinal++;
    return 0;
}


/*
 * hashtable_iterable_has_next() 
 * i_iterable has_next() implementation.
 */
int hashtable_iterable_has_next(etch_iterator* iter)
{
    return iter && iter->collection && iter->current_key && iter->ordinal;
}


/* - - - - - - - - - - - - - - - - - - - - - 
 * clear() callbacks
 * - - - - - - - - - - - - - - - - - - - - -
 */

/* 
 * string_to_object_clear_handler()
 * callback set to handle freeing of key/value memory during destroy()  
 * and subsequent clear() of a string-to-etch_object hashtable.
 * handlers return FALSE to indicate memory management NOT handled,
 */
int string_to_object_clear_handler (wchar_t* key, objmask* value)  
{
    etch_free(key); /* free string key */
    if (value) value->destroy(value); /* free etch object value */
    return TRUE;
}


/* 
 * object_to_object_clear_handler()
 * callback set to handle freeing of key/value memory during destroy()  
 * and subsequent clear() of a objmask-to-objmask hashtable.
 * handlers return FALSE to indicate memory management NOT handled.
 */
int object_to_object_clear_handler (objmask* key, objmask* value)  
{
    if (key)   key->destroy(key);     /* free etch object key */
    if (value) value->destroy(value); /* free etch object value */
    return TRUE;
}


/* 
 * etch_noop_clear_handler()
 * callback set to handle freeing of key/value memory during destroy()  
 * and subsequent clear() of a objmask-to-objmask hashtable.
 */
int etch_noop_clear_handler (objmask* key, objmask* value)  
{
    return TRUE;
}


/* - - - - - - - - - - - - - - - - - - - - - 
 * etch_map, etch_set
 * - - - - - - - - - - - - - - - - - - - - -
 */

/* 
 * new_etch_map()
 * an etch_map is a hashtable having object keys and object values. 
 * an object key should of course have its hashkey pre-computed appropriately.
 * if instantiator wants to use non-disposable objects as keys and/or values,
 * is_readonly_keys and/or is_readonly_values should be set, and the freehook
 * callback overridden. furthermore if caller chooses to use the same object  
 * as both key and value, similar steps should be taken to ensure that code  
 * does not try to destroy both key and value.
 */
etch_hashtable* new_etch_map(const int initialsize)
{
    etch_hashtable* newmap = new_hashtable(initialsize);
    newmap->content_type = ETCHHASHTABLE_CONTENT_OBJECT_OBJECT;
    newmap->freehook = object_to_object_clear_handler;
    newmap->is_readonly_keys = newmap->is_readonly_values = FALSE;
    return newmap;
}


/* 
 * new_etch_set()
 * an etch_set is a hashtable having object keys and null values. 
 */
etch_hashtable* new_etch_set(const int initialsize)
{
    etch_hashtable* newset = new_hashtable(initialsize);
    newset->class_id = CLASSID_ETCH_SET; /* serializer will expect this */
    newset->content_type = ETCHHASHTABLE_CONTENT_OBJECT_NONE;
    newset->freehook = object_to_object_clear_handler; 
    newset->is_readonly_keys   = FALSE;
    newset->is_readonly_values = TRUE; 
    return newset;
}


