/*
 * 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_ */

