/*
===========================================================================
Copyright (C) 2024 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

OpenMoHAA source code 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// umap.h: Hash table based map class template.  Maps text based keys to previously
// stored data.
//

#pragma once

#include "str.h"
#include <stdlib.h>

template<class Type>
class UMap : public Class
{
private:
    struct hashnode_s {
        str         key;
        Type        value;
        hashnode_s *next;

        hashnode_s(const char *k, Type v, hashnode_s *n)
            : key(k)
            , value(v)
            , next(n) {};
        hashnode_s(const str& k, Type v, hashnode_s *n)
            : key(k.c_str())
            , value(v)
            , next(n) {};
    };

    hashnode_s **m_heads;
    Type         m_defaultvalue;

    unsigned m_tablesize;
    unsigned m_numentries;
    unsigned m_tablesizemask;

    unsigned getHash(const char *key);

public:
    void     Clear(void);
    unsigned getNumEntries(void) const;
    void     DeleteDefaultValues(void);

    Type& operator[](const char *key);
    Type& operator[](const str& key);

    const char *getKeyByIndex(int index) const;
    Type&       getValueByIndex(unsigned index);

    ~UMap<Type>();

    UMap(Type defaultvalue, unsigned tablesize = 64)
        : m_defaultvalue(defaultvalue)
        , m_tablesize(tablesize)
    {
        int i;
        int bits;

        assert(m_tablesize > 0);

        m_heads = new hashnode_s *[m_tablesize];
        memset(m_heads, 0, sizeof(*m_heads) * m_tablesize);

        m_numentries    = 0;
        m_tablesizemask = 0;

        bits = 0;
        for (i = 0; i < 32; i++) {
            if (m_tablesize & (1 << i)) {
                bits++;
            }
        }

        if (bits == 1) {
            m_tablesizemask = m_tablesize - 1;
        }
    };

    UMap(UMap<Type>& map)
        : m_defaultvalue(map.m_defaultvalue)
        , m_tablesize(map.m_tablesize)
    {
        unsigned     i;
        hashnode_s  *node;
        hashnode_s **prev;

        assert(m_tablesize > 0);

        m_heads         = new hashnode_s *[m_tablesize];
        m_numentries    = map.m_numentries;
        m_tablesizemask = map.m_tablesizemask;

        for (i = 0; i < m_tablesize; i++) {
            if (!map.m_heads[i]) {
                m_heads[i] = NULL;
                continue;
            }

            prev = &m_heads[i];
            for (node = map.m_heads[i]; node != NULL; node = node->next) {
                *prev = new hashnode_s(node->key, node->value, NULL);
                prev  = &(*prev)->next;
            }
        }
    };
};

template<class Type>
UMap<Type>::~UMap<Type>()
{
    Clear();
    delete m_heads;
}

template<class Type>
unsigned UMap<Type>::getHash(const char *key)
{
    unsigned    h;
    const char *v;

    if (m_tablesizemask) {
        h = 0;
        for (v = key; *v != '\0'; v++) {
            h = (64 * h + unsigned(*v)) & m_tablesizemask;
        }
    } else {
        h = 0;
        for (v = key; *v != '\0'; v++) {
            h = (64 * h + unsigned(*v)) % m_tablesize;
        }
    }

    return h;
}

template<class Type>
Type& UMap<Type>::operator[](const char *key)
{
    hashnode_s **head;
    hashnode_s  *node;
    unsigned     hash;

    // FIXME
    // if list was always sorted, could just insert new node as soon as we get
    // node whose key is greater than the search key
    hash = getHash(key);
    head = &m_heads[hash];
    if (*head) {
        for (node = *head; node != NULL; node = node->next) {
            if (node->key == key) {
                return node->value;
            }
        }
    }

    m_numentries++;

    *head = new hashnode_s(key, m_defaultvalue, *head);

    return (*head)->value;
}

template<class Type>
const char *UMap<Type>::getKeyByIndex(int index) const
{
    unsigned head;

    for (head = 0; head < m_tablesize; head++) {
        hashnode_s *node;

        for (node = m_heads[head]; node; node = node->next) {
            if (!index) {
                return node->key.c_str();
            }

            index--;
        }
    }

    return NULL;
}

template<class Type>
Type& UMap<Type>::getValueByIndex(unsigned index)
{
    unsigned head;

    ASSERT(index < getNumEntries());

    for (head = 0; head < m_tablesize; head++) {
        hashnode_s *node;

        for (node = m_heads[head]; node; node = node->next) {
            if (!index) {
                return node->value;
            }

            index--;
        }
    }

    // Should never, EVER get here.
    ASSERT(0);
    return m_defaultvalue;
}

template<class Type>
inline void UMap<Type>::DeleteDefaultValues(void)
{
    unsigned head;

    for (head = 0; head < m_tablesize; head++) {
        hashnode_s *node;

        while (m_heads[head] && m_heads[head]->value == m_defaultvalue) {
            node          = m_heads[head];
            m_heads[head] = node->next;
            delete node;
            m_numentries--;
        }

        if (!m_heads[head]) {
            continue;
        }

        for (node = m_heads[head]; node->next;) {
            hashnode_s *next = node->next;

            if (next->value == m_defaultvalue) {
                node->next = next->next;
                delete next;
                m_numentries--;
            } else {
                node = node->next;
            }
        }
    }
}

template<class Type>
inline Type& UMap<Type>::operator[](const str& key)
{
    return this->[key.c_str()];
}

template<class Type>
void UMap<Type>::Clear(void)
{
    unsigned    i;
    hashnode_s *node;
    hashnode_s *next;

    for (i = 0; i < m_tablesize; i++) {
        next = m_heads[i];
        while (next != NULL) {
            node = next;
            next = next->next;
            delete node;
        }

        m_heads[i] = NULL;
    }

    m_numentries = 0;
}

template<class Type>
inline unsigned UMap<Type>::getNumEntries(void) const
{
    return m_numentries;
}
