/* $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. 
 */ 

/*
 * etchmap.c -- generic int or string to object map.
 * note that this code does not implement an etch_map type, that type was added
 * to etch after we had already defined this particular API.
 * todo: convert global cache to use this code.
 */

#include "etchmap.h"
#include "etch_global.h"
#pragma warning (disable:4996)


/*
 * make_etchmap_key() 
 * given 32-bit key constructs alpha key for the map returning key byte length
 */
int make_etchmap_key(const unsigned int key, char* buf, const int buflen)
{
   _itoa_s(key, buf, buflen, 10);
   return (int)strlen(buf);
}


/*
 * etchmap_populate_out() 
 * populate caller's out struct
 */
void etchmap_populate_out(etch_hashitem* useritem, etch_hashitem* curritem)
{
    useritem->key   = curritem->key;
    useritem->value = curritem->value;
    useritem->hash  = curritem->hash;
}


/*
 * etchmap_findxl() 
 * @return a reference to map content, not owned by caller.
 * if the etch_hashitem out parameter is specified, it is populated on success.
 */
void* etchmap_findxl(etch_hashtable* map, char* key, unsigned keylen, void** out)
{
    etch_hashitem  hashbucket;
    etch_hashitem* founditem = &hashbucket;

    int result = jenkins_find(map->realtable, key, keylen, 0, &founditem);

    #if ETCHMAP_DEBUG
    #pragma warning(disable:4313) 
    if  (result == 0) 
         printf("etchmap_found key %s len %d addr %08x\n", 
                 key, keylen, founditem->value);
    else printf("etchmap_notfound key %s len %d\n", key, keylen);
    #endif 
  
    if (result == -1) return NULL;

    if (out)
        etchmap_populate_out(*out, founditem);        

    return founditem->value;
}


/*
 * etchmap_find() 
 * locate object with specified key, returning it or NULL.
 * @return a reference to map content, not owned by caller.
 * if the etch_hashitem out parameter is specified, it is populated on success.
 */
void* etchmap_find(etch_hashtable* map, const unsigned int objkey, void** out)
{
    char ckey[ETCHMAP_MAX_IKEYSIZE+1];

    unsigned keylen = make_etchmap_key(objkey, ckey, ETCHMAP_MAX_IKEYSIZE);

    return etchmap_findxl(map, ckey, keylen, out);
}


/*
 * etchmap_findx() 
 * locate object with specified ansi char key, returning it or NULL
 */
void* etchmap_findx(etch_hashtable* map, char* key, void** out)
{
    unsigned keylen = (unsigned)strlen(key);

    return etchmap_findxl(map, key, keylen, out);
}


/*
 * etchmap_findxw() 
 * locate object with specified unicode key, returning it or NULL
 * @return a reference to map content, not owned by caller.
 * if the etch_hashitem out parameter is specified, it is populated on success.
 */
void* etchmap_findxw(etch_hashtable* map, wchar_t* key, void** out)
{
    unsigned keylen = (unsigned)(wcslen(key) * sizeof(wchar_t));

    return etchmap_findxl(map, (char*) key, keylen, out);
}


/*
 * etchmap_find_by_hash() 
 * locate object with specified hashkey, returning it or NULL.
 * @return a reference to map content, not owned by caller.
 * if the etch_hashitem out parameter is specified, it is populated on success.
 */
void* etchmap_find_by_hash(etch_hashtable* map, const unsigned hash, void** out)
{
    etch_hashitem  hashbucket;
    etch_hashitem* founditem = &hashbucket;

    int result = jenkins_findh(map->realtable, hash, map, &founditem);
    if (result == -1) return NULL;

    if (out)
        etchmap_populate_out(*out, founditem);       
    
    return founditem->value;
}


/*
 * etchmap_current() 
 * return object at current position
 */
etch_hashitem* etchmap_current(etch_hashtable* map)
{
    etch_hashitem  hashbucket;
    etch_hashitem* founditem = &hashbucket;

    int result = jenkins_current(map->realtable, 0, &founditem);
    return (result == -1)? NULL: founditem;
}


/*
 * etchmap_delxl() 
 * remove specified object from map given ansi char key and length.
 * return pointer to object, which is not freed here, it is owned by caller.
 */
void* etchmap_delxl (etch_hashtable* map, char* ckey, const unsigned keylen)
{
    etch_hashitem hashbucket;
    etch_hashitem* founditem = &hashbucket;

    int result = jenkins_remove(map->realtable, ckey, keylen, 0, &founditem);
    if (result == -1) return NULL;

    free(founditem->key); /* free 4-byte key allocated in etchmap_add() */
    return founditem->value;
}


/*
 * etchmap_del() 
 * remove specified object from map given integer key.
 * return pointer to object, which is not freed here, it is owned by caller.
 */
void* etchmap_del (etch_hashtable* map, const unsigned int objkey)
{
    char ckey[ETCHMAP_MAX_IKEYSIZE+1];

    unsigned keylen = make_etchmap_key(objkey, ckey, ETCHMAP_MAX_IKEYSIZE);

    return etchmap_delxl(map, ckey, keylen);
}


/*
 * etchmap_delx() 
 * remove specified object from map given ansi char key.
 * return pointer to object, which is not freed here, it is owned by caller.
 */
void* etchmap_delx (etch_hashtable* map, char* key)
{
    const unsigned keylen = (unsigned)strlen(key);

    return etchmap_delxl(map, key, keylen);
}


/*
 * etchmap_delxw() 
 * remove specified object from map given unicode key.
 * return pointer to object, which is not freed here, it is owned by caller.
 */
void* etchmap_delxw (etch_hashtable* map, wchar_t* key)
{
    const unsigned keylen = (unsigned)(wcslen(key) * sizeof(wchar_t));

    return etchmap_delxl(map, (char*) key, keylen);
}


/*
 * etchmap_insertxl() 
 * add specified object to map given ansi char key and char length, 
 * with preexistence test optional.
 * @param key a string for which caller retains ownership, map makes a copy.
 * @param data an object owned by map or not depending on map attributes.
 * @return hash of supplied key, or zero if insertion error.
 */
int etchmap_insertxl (etch_hashtable* map, 
    char* key, const unsigned keylen, void* data, const int is_check)
{
    int   result = 0, keylent = 0;
    char* pkey = NULL;
    void* pval = NULL;
    etch_hashitem  hashbucket; 
    etch_hashitem* inserteditem = &hashbucket;
    memset(&hashbucket, 0, sizeof(etch_hashitem));

    if (is_check)
    {   pval = etchmap_findxl(map, key, keylen, &inserteditem);
        if (pval) return inserteditem->hash; /* entry exists */
    }

    keylent = keylen + 1;/* add new entry */
    pkey    = malloc(keylent); 
    strcpy_s(pkey, keylent, key);

    #if ETCHMAP_DEBUG
    #pragma warning(disable:4313) 
    printf("etchmap_insertxl key %s len %d addr %08x\n", pkey, keylen, data); 
    #endif

    result = jenkins_insert
       (map->realtable, pkey, keylen, data, 0, 0, &inserteditem);

    /* etchmap_dump(); */

    return inserteditem->hash;
} 


/*
 * etchmap_insertxlw() 
 * add specified object to map given unicode key and char length, 
 * with preexistence test optional.
 * @param key a string for which caller retains ownership, map makes a copy.
 * @param data an object owned by map or not depending on map attributes.
 * @return hash of supplied key, or zero if insertion error.
 */
int etchmap_insertxlw (etch_hashtable* map, 
    wchar_t* key, const unsigned keylen, void* data, const int is_check)
{
    int result = 0, keylent = 0;
    wchar_t* pkey = NULL;
    void*    pval = NULL;
    etch_hashitem  hashbucket; 
    etch_hashitem* inserteditem = &hashbucket;
    memset(&hashbucket, 0, sizeof(etch_hashitem));

    if (is_check)
    {   pval = etchmap_findxl(map, (char*) key, keylen, &inserteditem);
        if (pval) return inserteditem->hash; /* entry exists */
    }

    keylent = keylen + sizeof(wchar_t);     /* add new entry */
    /* watch this spot. issue observed here where etch_malloc reports that it 
     * "could not add x to heap tracking store". my guess is that a heap item
     * at address x had been etch_freed, but key x was for some reason not
     * removed from the tracking table. memory address x was subsequently
     * re-issued by malloc, and when etch_malloc attempts to add it to the
     * tracking table, the address already exists there (or there is a hash
     * collision?). regardless, this is a tracking error, is probably not a 
     * leak (because etch_free frees the memory regardless of this error), 
     * however the error messages are a problem so we need to address this.
     */
    pkey = etch_malloc(keylent, 0);   
    wcscpy(pkey, key);

    result = jenkins_insert
       (map->realtable, pkey, keylen, data, 0, 0, &inserteditem);

    return inserteditem->hash;
} 


/*
 * etchmap_insert() 
 * add specified object to map with preexistence test optional.
 * return inserted item hash, or zero if insertion error.
 */
int etchmap_insert (etch_hashtable* map, 
    const unsigned int key, void* data, const int is_check)
{
    char  ckey[ETCHMAP_MAX_IKEYSIZE+1];
    unsigned keylen = make_etchmap_key(key, ckey, ETCHMAP_MAX_IKEYSIZE);

    return etchmap_insertxl(map, ckey, keylen, data, is_check);
} 


/*
 * etchmap_insertx() 
 * add specified object to map with preexistence test optional.
 * param key a string for which caller retains ownership, map makes a copy.
 * @param data the value of the key/value pair, owned by map or not depending
 * on map attributes.
 * return inserted item hash, or zero if insertion error.
 */
int etchmap_insertx (etch_hashtable* map, char* key, void* data, const int is_check)
{
    unsigned keylen = (unsigned)strlen(key);
    
    return etchmap_insertxl(map, key, keylen, data, is_check);
} 


/*
 * etchmap_insertxw() 
 * add specified object to map with preexistence test optional.
 * param key a string for which caller retains ownership, map makes a copy.
 * @param data the value of the key/value pair, owned by map or not depending
 * on map attributes.
 * return inserted item hash, or zero if insertion error.
 */
int etchmap_insertxw (etch_hashtable* map, wchar_t* key, void* data, const int is_check)
{
    unsigned keylen = (unsigned)(wcslen(key) * sizeof(wchar_t));
    
    return etchmap_insertxlw(map, key, keylen, data, is_check);
} 


/*
 * etchmap_add() 
 * add specified object to map given integer key.
 * return 0 or -1.
 */
int etchmap_add (etch_hashtable* map, const unsigned int objkey, void* data)
{
    /* zero back from etchmap_insert() indicates insert error */ 
    return etchmap_insert (map, objkey, data, TRUE)? 0: -1;
} 


/*
 * etchmap_addx() 
 * add specified object to map.
 * return 0 or -1.
 */
int etchmap_addx(etch_hashtable* map, char* key, void* data)
{
    /* zero back from etchmap_insert() indicates insert error */ 
    return etchmap_insertx (map, key, data, TRUE)? 0: -1;
} 


/*
 * etchmap_addxw() 
 * add specified object to map.
 * return 0 or -1.
 */
int etchmap_addxw(etch_hashtable* map, wchar_t* key, void* data)
{
    /* zero back from etchmap_insert() indicates insert error */ 
    return etchmap_insertxw (map, key, data, TRUE)? 0: -1;
} 


/*
 * etchmap_count() 
 * return count of items in map.
 */
int etchmap_count(etch_hashtable* map)
{
    return map? map->vtab->count(map->realtable, 0, 0): 0;
}


/* 
 * string_to_etchobject_clear_handler()
 * callback set to handle freeing of key memory during a clear() of  
 * a string to object type map, where the objects are etch_objects. 
 * handlers return FALSE to indicate memory free NOT handled.
 */
int string_to_etchobject_clear_handler (char* key, objmask* value)  
{
    etch_free(key);
    if (value) value->destroy(value);
    return TRUE; 
}


/* 
 * string_to_genericobject_clear_handler()
 * callback set to handle freeing of key memory during a clear() of  
 * a string to object type map, where the objects are not etch_objects. 
 * handlers return FALSE to indicate memory free NOT handled.
 */
int string_to_genericobject_clear_handler (char* key, void* value)  
{
    etch_free(key);
    etch_free(value);
    return TRUE; 
}


/* - - - - - - - - - - - - - - - -
 * etch_map (object to object map)
 * - - - - - - - - - - - - - - - -
 */

/**
 * etchmap_is_object_key
 * @return boolean value indicating whether hashtable key is an etch object
 */  
int etchmap_is_object_key(etch_hashtable* map)
{
    int result = FALSE;
    if (map) switch(map->content_type)
    {   case ETCHHASHTABLE_CONTENT_OBJECT_OBJECT:
        case ETCHHASHTABLE_CONTENT_OBJECT_NONE:
             result = TRUE;
    }
    return result;
}


/**
 * etchmap_map_add
 * inserts item to object/object hashtable
 * it is assumed that hashkey has been pre-computed on the key object.
 */  
int etchmap_map_add (etch_hashtable* map, objmask* key, objmask* value) 
{    
    const int result = map->vtab->inserth(map->realtable, key, value, map, 0); 
    return result;  
}


/**
 * etchmap_map_find()
 * finds object with specified object key .
 * it is assumed that hashkey has been pre-computed on the key object.
 * @return 0 or -1
 * found item is returned via out parameter, if supplied.
 */  
int etchmap_map_find(etch_hashtable* map, objmask* key, etch_hashitem** out)
{
    const int result = map->vtab->findh(map->realtable, key->hashkey, map, out); 
    return result;
}


/**
 * etchmap_set_add
 * inserts item to object/null hashtable
 * it is assumed that hashkey has been pre-computed on the key object.
 */  
int etchmap_set_add (etch_hashtable* set, objmask* key) 
{    
    const int result = set->vtab->inserth(set->realtable, key, NULL, set, 0); 
    return result;
}





