/**
 *
 * Copyright (C) 1995 Free Software Foundation, Inc.
 *
 * This file is part of the GNU LessTif Library.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **/

#include <X11/Xfuncs.h>
#include <XmI/XmI.h>
#include <XmI/HashI.h>

/*
 * This is a general-purpose hash table module. It can be used to implement
 * caches and all other kinds of associative arrays -- like the upcomming
 * trait mechanism.
 *
 * Written by Harald Albrecht (albrecht@igpm.rwth-aachen.de)
 */

/*
 * To improve allocation performance, we don't give buckets back to the
 * malloc() memory allocator. Instead we put buckets that aren't needed
 * any longer in our own free list.
 */
static LTBucket BucketFreeStore = NULL;

/*
 * Allocate a bucket. Instead of really allocating only one of these tiny
 * suckers we allocate a whole bunch of them at once. This should improve
 * performance. The buckets that are not needed at this time are linked
 * into the free list.
 */
static LTBucket LTCreateBucket(void)
{
    LTBucket Bucket;
    int      i;
    
    if ( BucketFreeStore == NULL ) {
        BucketFreeStore = (LTBucket) XtMalloc(sizeof(LTBucketRec) * 
					      LTHASH_BUCKET_CHUNK_SIZE);
	Bucket = BucketFreeStore;
        for ( i = 1; i < LTHASH_BUCKET_CHUNK_SIZE; i++ ) {
            Bucket->Next = Bucket + 1;
            Bucket++;
        }
        Bucket->Next = NULL;
    }
    Bucket          = BucketFreeStore;
    BucketFreeStore = Bucket->Next;
    return Bucket;
} /* LTCreateBucket */

/*
 * Free a bucket. As I mentioned above, the bucket isn't really freed
 * (from malloc()'s point of view) but instead linked into a free list.
 * Unfortunately ANSI C doesn't know of "inline", so I'm doing it the
 * traditional way using #define. Ouch.
 */
#define LTDestroyBucket(Bucket, IDSize) { \
    if ( IDSize ) {                       \
        XtFree((char *) Bucket->ItemID);  \
    }                                     \
    Bucket->Next    = BucketFreeStore;    \
    BucketFreeStore = Bucket;             \
} /* LTDestroyBucket */

/*
 * This one frees a whole bucket chain. We do this by just linking
 * the whole chain into the free list at once and skip calling LTDeleteBucket
 * for every single bucket.
 */
static void LTDestroyBucketChain(LTBucket FirstBucket, unsigned int IDSize)
{
    LTBucket Bucket, NextBucket;

    if ( FirstBucket == NULL ) {
        return;
    }
    Bucket = FirstBucket;
    if ( IDSize ) {
        while ( (NextBucket = Bucket->Next) != NULL ) {
	    XtFree((char *) Bucket->ItemID);
            Bucket = NextBucket;
        }
    } else {
        while ( (NextBucket = Bucket->Next) != NULL ) {
            Bucket = NextBucket;
        }
    }
    Bucket->Next    = BucketFreeStore;
    BucketFreeStore = FirstBucket;
} /* LTDestroyBucketChain */

/*
 * Here we provide default hash and compare functions just for those who
 * are lazy enough not to invent their own ones.
 */
static unsigned int LTDefaultGetHash(LTHashItemID item_id)
{
    return (unsigned int) item_id;
} /* LTDefaultGetHash */

static unsigned int LTDefaultStringGetHash(LTHashItemID item_id)
{
    unsigned int  HashValue;
    char         *p, ch;

    p = (char *) item_id;
    HashValue = 0;
    while ( (ch = *p++) ) {
        HashValue += (HashValue << 3) + ch;
    }
    return HashValue;
} /* LTDefaultStringGetHash */

static Boolean LTDefaultCompare(LTHashItemID item1_id, LTHashItemID item2_id)
{
    return item1_id == item2_id ? True : False;
} /* LTDefaultCompare */

static Boolean LTDefaultStringCompare(LTHashItemID item1_id, LTHashItemID item2_id)
{
    return strcmp((const char *) item1_id, (const char *) item2_id) == 0 ?
               True : False;
} /* LTDefaultStringCompare */

/*
 * Create a new hash table. The programmer can supply her/his own functions
 * for calculating the hash value and for comparing items. There is no need
 * to specify a hash table size as the hash table will adjust itself
 * dynamically.
 */
LTHashTable LTHashTableCreate(LTHashGetHashFunction GetHash,
                              LTHashCompareFunction Compare,
			      unsigned int IDSize)
{
    LTHashTable ht;
    
    ht                  = (LTHashTable) XtMalloc(sizeof(LTHashTableRec));
    ht->Mask            = LTHASH_DEFAULT_TABLE_SIZE;
    ht->InUse           = 0;
    ht->HighWaterMark   = (ht->Mask + 1) * LTHASH_AVERAGE_WATERMARK;
    ht->BucketChainList = (LTBucketList) XtCalloc(ht->Mask + 1, 
                                                  sizeof(LTBucket));
    ht->GetHash         = GetHash ? GetHash : 
                            (IDSize == LTHASH_ID_STRING ? 
                              LTDefaultStringGetHash : LTDefaultGetHash);
    ht->Compare         = Compare ? Compare : 
                            (IDSize == LTHASH_ID_STRING ?
			      LTDefaultStringCompare : LTDefaultCompare);
    ht->IDSize          = IDSize;
    return ht;
} /* LTHashTableCreate */

/*
 * Deletes a hash table: frees the memory used by the hash table and puts
 * the buckets back into the free list.
 */
void LTHashTableDelete(LTHashTable ht)
{
    LTBucketList BucketChain;
    int          i;
    
    if ( ht == NULL ) {
        _XmError(NULL, "LTHashTableDelete: attempt to delete non-existing "
                       "hash table.");
    }
    /*
     * First free all buckets we've allocated for our hash table.
     */
    BucketChain = ht->BucketChainList;
    for ( i = 0; i <= ht->Mask; i++ ) {
        LTDestroyBucketChain(*BucketChain++, ht->IDSize);
    }
    /*
     * Finally give back the memory occupied the the hash table description
     * and the bucket chain list to the free store (heap,...).
     */
    XtFree((char *) ht->BucketChainList);
    XtFree((char *) ht);
} /* LTHashTableDelete */

/*
 * Increases the size of a hash table when the average filling level has
 * been reached.
 */
static void LTHashTableGrow(LTHashTable ht)
{
    LTBucketList BucketChain, OldBucketChain;
    LTBucket     Bucket, NextBucket;
    int          i, OldMask, Index;
    
    if ( ht->InUse < ht->HighWaterMark ) {
        return;
    }
    /*
     * First, save the old bucket chain table and the old table size...
     */
    OldBucketChain = ht->BucketChainList;
    OldMask        = ht->Mask;
    /*
     * ...then allocate a new, larger table...
     */
    ht->Mask            = (ht->Mask << 2) | 0x3;
    ht->HighWaterMark   = (ht->Mask + 1) * LTHASH_AVERAGE_WATERMARK;
    BucketChain         = (LTBucketList) XtCalloc(ht->Mask + 1,
						  sizeof(LTBucket));
    ht->BucketChainList = BucketChain;
    /*
     * ...and finally move the buckets from the old hash table to their
     * new places in the new hash table.
     */
    for ( i = 0; i <= OldMask; i++ ) {
        Bucket = OldBucketChain[i];
	while ( Bucket ) {
	    NextBucket         = Bucket->Next;
	    Index              = ht->GetHash(Bucket->ItemID) & ht->Mask;
	    Bucket->Next       = BucketChain[Index];
	    BucketChain[Index] = Bucket;
	    Bucket             = NextBucket;
	}
    }
    /*
     * That's it! Now clean up the leftovers.
     */
    XtFree((char *) OldBucketChain);
} /* LTHashTableGrow */

/*
 * Returns how many items are currently stored in the hash table. This is
 * just for the curious.
 */
int LTHashTableGetNumItems(LTHashTable ht)
{
    if ( ht == NULL ) {
        _XmError(NULL, "LTHashTableGetNumItems: NULL hash table specified.");
    }
    return ht->InUse;
} /* LTHashTableGetNumItems */

/*
 * Adds an item to the hash table. If the item already exists in the hash
 * table then the function will return False immediatly.
 */
Boolean LTHashTableAddItem(LTHashTable ht,
			   LTHashItemID id, LTHashItemValue value)
{
    int          Index;
    LTBucket     Bucket;
    
    if ( ht == NULL ) {
        _XmError(NULL, "LTHashTableAddItem: NULL hash table specified.");
    }
    Index = ht->GetHash(id) & ht->Mask;
    Bucket = ht->BucketChainList[Index];
    while ( Bucket &&
	    !ht->Compare(Bucket->ItemID, id) ) {
        Bucket = Bucket->Next;
    }
    if ( Bucket == NULL ) {
        /*
	 * There's no such item in the hash table. Thus allocate a new
	 * bucket and fill it with the item and the value. That's it.
	 */
        Bucket = LTCreateBucket();
	if ( ht->IDSize ) {
	    if ( ht->IDSize == LTHASH_ID_STRING ) {
	        Bucket->ItemID = (LTHashItemID) XtNewString((String) id);
	    } else {
	        Bucket->ItemID = (LTHashItemID) XtMalloc(ht->IDSize);
		bcopy(id, Bucket->ItemID, ht->IDSize);
	    }
	} else {
            Bucket->ItemID = id;
	}
	Bucket->Value = value;
	Bucket->Next  = ht->BucketChainList[Index];
	ht->BucketChainList[Index] = Bucket;
	ht->InUse++;
	LTHashTableGrow(ht);
	return True;
    }
    return False;
} /* LTHashTableAddItem */

/*
 * Adds an item to or replaces an item in a hash table. If the item already
 * exists then old value is returned in "value_ret". The function then
 * returns True. If such an item does not yet exists it is created and
 * the function returns False.
 */
Boolean LTHashTableReplaceItem(LTHashTable ht,
			       LTHashItemID id, LTHashItemValue value,
			       LTHashItemValue *value_ret)
{
    int          Index;
    LTBucket     Bucket;
    
    if ( ht == NULL ) {
        _XmError(NULL, "LTHashTableReplaceItem: NULL hash table specified.");
    }
    Index = ht->GetHash(id) & ht->Mask;
    Bucket = ht->BucketChainList[Index];
    while ( Bucket &&
	    !ht->Compare(Bucket->ItemID, id) ) {
        Bucket = Bucket->Next;
    }
    if ( Bucket == NULL ) {
        /*
	 * There's no such item in the hash table. Thus allocate a new
	 * bucket and fill it with the item and the value. That's it.
	 */
        Bucket = LTCreateBucket();
	if ( ht->IDSize ) {
	    if ( ht->IDSize == LTHASH_ID_STRING ) {
	        Bucket->ItemID = (LTHashItemID) XtNewString((String) id);
	    } else {
	        Bucket->ItemID = (LTHashItemID) XtMalloc(ht->IDSize);
		bcopy(id, Bucket->ItemID, ht->IDSize);
	    }
	} else {
            Bucket->ItemID = id;
	}
	Bucket->Value = value;
	Bucket->Next  = ht->BucketChainList[Index];
	ht->BucketChainList[Index] = Bucket;
	ht->InUse++;
	LTHashTableGrow(ht);
	return False;
    }
    /*
     * There's already such an item in the list, so we're going to over-
     * write the item's value. And we return the old value.
     */
    if ( value_ret ) {
        *value_ret = Bucket->Value;
    }
    Bucket->Value = value;
    return True;
} /* LTHashTableReplaceItem */

Boolean LTHashTableReplaceItemAndID(LTHashTable ht,
				    LTHashItemID id, LTHashItemValue value,
				    LTHashItemID *id_ret,
				    LTHashItemValue *value_ret)
{
    int          Index;
    LTBucket     Bucket;
    
    if ( ht == NULL ) {
        _XmError(NULL, "LTHashTableReplaceItem: NULL hash table specified.");
    }
    Index = ht->GetHash(id) & ht->Mask;
    Bucket = ht->BucketChainList[Index];
    while ( Bucket &&
	    !ht->Compare(Bucket->ItemID, id) ) {
        Bucket = Bucket->Next;
    }
    if ( Bucket == NULL ) {
        /*
	 * There's no such item in the hash table. Thus allocate a new
	 * bucket and fill it with the item and the value. That's it.
	 */
        Bucket = LTCreateBucket();
	if ( ht->IDSize ) {
	    if ( ht->IDSize == LTHASH_ID_STRING ) {
	        Bucket->ItemID = (LTHashItemID) XtNewString((String) id);
	    } else {
	        Bucket->ItemID = (LTHashItemID) XtMalloc(ht->IDSize);
		bcopy(id, Bucket->ItemID, ht->IDSize);
	    }
	} else {
            Bucket->ItemID = id;
	}
	Bucket->Value = value;
	Bucket->Next  = ht->BucketChainList[Index];
	ht->BucketChainList[Index] = Bucket;
	ht->InUse++;
	LTHashTableGrow(ht);
	return False;
    }
    /*
     * There's already such an item in the list, so we're going to over-
     * write the item's value. And we return the old value. And we over-
     * write the item's id, too.
     */
    if ( id_ret ) {
        *id_ret = Bucket->ItemID;
    }
    if ( ht->IDSize ) {
        if ( ht->IDSize == LTHASH_ID_STRING ) {
	    Bucket->ItemID = (LTHashItemID) XtNewString((String) id);
	} else {
	    Bucket->ItemID = (LTHashItemID) XtMalloc(ht->IDSize);
	    bcopy(id, Bucket->ItemID, ht->IDSize);
	}
    } else {
        Bucket->ItemID = id;
    }
    if ( value_ret ) {
        *value_ret = Bucket->Value;
    }
    Bucket->Value = value;
    return True;
} /* LTHashTableReplaceItemAndID */

/*
 * Removes an item "item" from the hashtable "ht". If the function was
 * successfully and found the item and delted it then it returns True.
 * If there is no such "id" then the function returns False. If "id"
 * was found and "value_ret" is a non-NULL pointer, then the value of the
 * item is returned through the "value_ret" pointer. This is a shortcut for
 * many cleanup tasks which occur in conjunction with hash tables: you
 * have to delete an item and free the memory associated with it. Using
 * the "value" parameter you don't have first to lookup the item, then
 * free the associated memory and finally delete the item in the hash
 * table. Instead you just delete the item and use *value to free the
 * associated memory.
 */
Boolean LTHashTableRemoveItem(LTHashTable ht, LTHashItemID id,
			      LTHashItemValue *value_ret)
{
    int      Index;
    LTBucket Bucket, PrevBucket;
    
    if ( ht == NULL ) {
        _XmError(NULL, "LTHashTableRemoveItem: NULL hash table specified.");
    }
    Index  = ht->GetHash(id) & ht->Mask;
    Bucket = ht->BucketChainList[Index];
    
    PrevBucket = NULL;
    while ( Bucket ) {
        if ( ht->Compare(Bucket->ItemID, id) ) {
            if ( PrevBucket == NULL ) {
                ht->BucketChainList[Index] = Bucket->Next;
            } else {
                PrevBucket->Next = Bucket->Next;
            }
	    if ( value_ret ) {
	        *value_ret = Bucket->Value;
	    }
            LTDestroyBucket(Bucket, ht->IDSize);
            ht->InUse--;
	    return True;
        }
        PrevBucket = Bucket;
        Bucket     = Bucket->Next;
    }
    return False;
} /* LTHashTableRemoveItem */

/*
 * Lookups the value of an item within the hash table. The parameters and
 * the return value are the same as in LTHashTableRemoveItem(). But this
 * time the item isn't freed. This function is used to either get the value
 * assigned to an item or just to test the existence of the item.
 */
Boolean LTHashTableLookupItem(LTHashTable ht, LTHashItemID id,
			      LTHashItemValue *value)
{
    int      Index;
    LTBucket Bucket;

    if ( ht == NULL ) {
        _XmError(NULL, "LTHashTableLookupItem: NULL hash table specified.");
    }
    Index  = ht->GetHash(id) & ht->Mask;
    Bucket = ht->BucketChainList[Index];
    while ( Bucket &&
	    !ht->Compare(Bucket->ItemID, id) ) {
        Bucket = Bucket->Next;
    }
    if ( Bucket ) {
        if ( value ) {
            *value = Bucket->Value;
	}
	return True;
    }
    return False;
} /* LTHashTableLookupItem */

/*
 * Sad, sad, very sad. Sometimes we need an iterator to iterate over all
 * entries within the hash table.
 */
int LTHashTableForEachItem(LTHashTable ht, LTHashForEachFunction iter,
			   XtPointer ClientData)
{
    int                         Counter, i;
    LTBucket                    Bucket;
    LTHashForEachIteratorResult ItResult;

    if ( ht == NULL ) {
        _XmError(NULL, "LTHashTableForEachItem: NULL hash table specified.");
    }
    Counter = 0;
    for ( i = 0; i <= ht->Mask; i++ ) {
        Bucket = ht->BucketChainList[i];
	while ( Bucket ) {
	    ItResult = iter(ht, Bucket->ItemID, Bucket->Value, ClientData);
	    if ( ItResult == LTHASH_BREAK ) {
	        goto UndTschuess;
	    } else if ( ItResult == LTHASH_COUNTANDBREAK ) {
	        Counter++;
		goto UndTschuess;
	    } else if ( ItResult == LTHASH_COUNT ) {
	        Counter++;
	    }
	    Bucket = Bucket->Next;
	}
    }
UndTschuess:
    return Counter;
} /* LTHashTableForEachItem */

/* End of Hash.c */
