/*
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (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.mozilla.org/NPL/

 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.

 * The Original Code is Rhino code, released
 * May 6, 1999.

 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
 * Rights Reserved.

 * Contributor(s): 

 * Alternatively, the contents of this file may be used under the
 * terms of the GNU Public License (the "GPL"), in which case the
 * provisions of the GPL are applicable instead of those above.
 * If you wish to allow use of your version of this file only
 * under the terms of the GPL and not to allow others to use your
 * version of this file under the NPL, indicate your decision by
 * deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL.  If you do not delete
 * the provisions above, a recipient may use your version of this
 * file under either the NPL or the GPL.
 */

#ifndef _JS_HASH_TABLE_H_
#define _JS_HASH_TABLE_H_

#include <string.h>
#include <assert.h>
#include "js-enumeration.h"

/* Classes */
template <typename _Val, typename _Key, class _Allocator>
class JSHashtable {
    public:
       	void  operator delete(void *ptr) {
       		assert(0);
       	}
	class HashtableEntry;

	typedef	size_t	(*hashCodeProc_t)(_Key key);
	typedef	int	(*cmpKeyProc_t)(_Key key1, _Key key2);
	typedef	void	(*dupEntryProc_t)(HashtableEntry *dst,
				HashtableEntry *src, _Allocator *allocator);
	typedef	void	(*destroyEntryProc_t)(HashtableEntry *tabEntry,
				_Allocator *allocator);
	class HashtableEntry {
 	    public:
		int		hash;
		_Key		key;
		_Val		value;
		HashtableEntry	*next;

	    public:
		HashtableEntry	*clone(dupEntryProc_t dupEntry,
				_Allocator *allocator) {
		    HashtableEntry *tabEntry
			= (HashtableEntry *)
				allocator->allocate(sizeof (*tabEntry));

		    (*dupEntry)(tabEntry, this, allocator);
		    tabEntry->next = (next != NULL) ?
				(HashtableEntry *) next->clone(
							dupEntry, allocator)
				: (HashtableEntry *) NULL;
		    return tabEntry;
		}
	} ;
	class HashtableEnumerator: public JSEnumeration<HashtableEntry *> {
	    public:
		int 	index;
		HashtableEntry **table;
		HashtableEntry *tabEntry;
 
	    public:
		HashtableEnumerator(HashtableEntry **table, int tableLength) {
		    this->table = table;
		    index = tableLength;
		    tabEntry = NULL;
		}  
       
		jsBool_t	hasMoreElements() {
		    if (tabEntry != NULL) {
			return TRUE;
		    }
		    while (index-- > 0) {
			if ((tabEntry = table[index]) != NULL) {
			    return TRUE;
			}
		    }
		    return FALSE;
		}
		HashtableEntry *
		    nextElement() {
		    HashtableEntry *e = tabEntry;

		    if (e == NULL) {
			while ((index-- > 0)
				&& ((e = table[index]) == NULL)) ;
			if (e == NULL) return NULL;
		    }
		    tabEntry = e->next;
		    return e;
		}
	} ;
	class HashtableKeyEnumerator: public JSEnumeration<_Key> {
	    public:
		HashtableEnumerator enumerator;
 
	    public:
		HashtableKeyEnumerator(HashtableEntry **table, int tableLength)
			: enumerator(table, tableLength) {
		}  
       
		jsBool_t	hasMoreElements() {
		    return enumerator.hasMoreElements();
		}
		_Key
		    nextElement() {
		    HashtableEntry *e = enumerator.nextElement();

		    return e->key;
		}
	} ;
	class HashtableValEnumerator: public JSEnumeration<_Val> {
	    public:
		HashtableEnumerator enumerator;
 
	    public:
		HashtableValEnumerator(HashtableEntry **table, int tableLength)
			: enumerator(table, tableLength) {
		}  
       
		jsBool_t	hasMoreElements() {
		    return enumerator.hasMoreElements();
		}
		_Val
		    nextElement() {
		    HashtableEntry *e = enumerator.nextElement();

		    return e->value;
		}
	} ;

    protected:
	HashtableEntry
		**table;
	int	tableLength;
	int	count;
	int	threshold;
	float	loadFactor;
	_Allocator
		*allocator;
	hashCodeProc_t
		hashCode;
	cmpKeyProc_t	
		cmpKey;
	dupEntryProc_t
		dupEntry;
	destroyEntryProc_t
		destroyEntry;

    protected:
	void rehash() {
	    int oldCapacity = tableLength;
	    HashtableEntry **oldTable= table;

	    int newCapacity = oldCapacity * 2 + 1;
	    HashtableEntry **newTable
			= (HashtableEntry **) allocator->allocate(
					sizeof (*newTable) * newCapacity);
	    memset(newTable, 0, sizeof (*newTable) * newCapacity);

	    threshold = (int) (newCapacity * loadFactor);

	    tableLength = newCapacity;
	    table = newTable;

	    for (int i = oldCapacity ; i-- > 0 ;) {
		for (HashtableEntry *old = oldTable[i] ; old != NULL ; ) {
		    HashtableEntry *e = old;
		    old = old->next;

		    int index = (e->hash & 0x7FFFFFFF) % newCapacity;
		    e->next = newTable[index];
		    newTable[index] = e;
		}      
	    }
	    allocator->deallocate(oldTable);
	}

    public:
	JSHashtable(hashCodeProc_t hashCode, cmpKeyProc_t cmpKey,
		dupEntryProc_t dupEntry, destroyEntryProc_t destroyEntry,
		_Allocator *allocator,
		int initialCapacity = 101, float loadFactor = 0.75f) {
	    this->hashCode = hashCode;
	    this->cmpKey = cmpKey;
	    this->dupEntry = dupEntry;
	    this->destroyEntry = destroyEntry;
	    this->allocator = allocator;
	    this->loadFactor = loadFactor;
	    tableLength = initialCapacity;
	    if (initialCapacity > 0) {
	        table = (HashtableEntry **) allocator->allocate(
					sizeof (*table) * initialCapacity);
	        memset(table, 0, sizeof (*table) * initialCapacity);
	    } else {
		table = (HashtableEntry **) NULL;
	    }
	    count = 0;
	    threshold = (int) (initialCapacity * loadFactor);
	}
	~JSHashtable() {
	    for (int i = tableLength ; i-- > 0 ;) {
		for (HashtableEntry *tabEntry = table[i] ;
		    tabEntry != NULL ; ) {
		    HashtableEntry *nextTabEntry = tabEntry->next;

		    (*destroyEntry)(tabEntry, allocator);
		    tabEntry = nextTabEntry;
		}      
	    }
	    allocator->deallocate(table);
	}

	_Val	get(_Key key);
	_Val	put(_Key key, _Val value);
	_Val	remove(_Key key);
	jsBool_t
		containsKey(_Key key);
	HashtableEnumerator
		entries() {
	    return HashtableEnumerator(table, tableLength);
	}
	HashtableKeyEnumerator
		keys() {
	    return HashtableKeyEnumerator(table, tableLength);
	}
	HashtableValEnumerator
		values() {
	    return HashtableValEnumerator(table, tableLength);
	}
	int size() {
	    return count;
	}
} ;

/* Inlines */
template <typename _Val, typename _Key, class _Allocator>
_Val
JSHashtable<_Val, _Key, _Allocator>::get(_Key key)
{
    HashtableEntry **tab = table;

    if (tab != NULL) {
        int hash = (*hashCode)(key);
        int index = (hash & 0x7FFFFFFF) % tableLength;

        for (HashtableEntry *e = tab[index] ; e != NULL ; e = e->next) {
	    if ((e->hash == hash) && (*cmpKey)(key, e->key) == 0) {
	        return e->value;
	    }
        }
    }
    return 0;
}
template <typename _Val, typename _Key, class _Allocator>
_Val
JSHashtable<_Val, _Key, _Allocator>::put(_Key key, _Val value)
{
    HashtableEntry **tab = table;
    int hash = (*hashCode)(key);
    int index = 0; // set 0 to avoid comiple error;

    if (tab != NULL) {
        index = (hash & 0x7FFFFFFF) % tableLength;

        for (HashtableEntry *e = table[index] ; e != NULL ; e = e->next) {
	    if ((e->hash == hash) && (*cmpKey)(key, e->key) == 0) {
	        _Val old = e->value;

	        e->value = value;
	        return old;    
	    }
        }
    }

    if (count >= threshold) {
	// Rehash the table if the threshold is exceeded
	rehash();
	return put(key, value);
    }
 
    // Creates the new entry.
    HashtableEntry *ne = (HashtableEntry *) allocator->allocate(sizeof (*ne));
    HashtableEntry s;
   
    s.hash = hash;
    s.key = key;
    s.value = value;
    s.next = tab[index];
    dupEntry(ne, &s, allocator);
    tab[index] = ne;
    count++;
    return 0;
}
template <typename _Val, typename _Key, class _Allocator>
_Val
JSHashtable<_Val, _Key, _Allocator>::remove(_Key key)
{
    HashtableEntry **tab = table;

    if (tab != NULL) {
        int hash = (*hashCode)(key);
        int index = (hash & 0x7FFFFFFF) % tableLength;

        for (HashtableEntry *e = tab[index], *prev = NULL;
	    e != NULL ; prev = e, e = e->next) {
	    if ((e->hash == hash) && cmpKey(e->key, key) == 0) {
	        if (prev != NULL) {
		    prev->next = e->next;
	        } else {
		    tab[index] = e->next;
	        }
	        count--; 
	        _Val value = e->value;

	        (*destroyEntry)(e, allocator);
	        return value;  
	    }
        }
    }
    return 0;
}
template <typename _Val, typename _Key, class _Allocator>
jsBool_t
JSHashtable<_Val, _Key, _Allocator>::containsKey(_Key key)
{
    HashtableEntry **tab = table;

    if (tab != NULL) {
        int hash = hashCode(key);
        int index = (hash & 0x7FFFFFFF) % tableLength;

        for (HashtableEntry *e = tab[index] ; e != NULL ; e = e->next) {
            if ((e->hash == hash) && cmpKey(e->key, key) == 0) {
                return TRUE;    
            }
        }
    }
    return FALSE;
}

#endif /* _JS_HASH_TABLE_H_ */

