/*
 * Copyright (C) 2002,2003 Daniel Heck
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
#ifndef ECL_DICT_HH
#define ECL_DICT_HH

#include "ecl_error.hh"
#include <cstddef>
#include <utility>
#include <vector>

namespace ecl {

unsigned hash(const std::string &key);

class XInvalidKey : XGeneric {
public:
    XInvalidKey() : XGeneric("invalid dictionary key") {}
};

template <class T>
class Dict {
public:
    typedef std::string key_type;
    typedef std::pair<key_type, T> value_type;
    typedef size_t size_type;

private:
    struct Entry {
        Entry(const key_type &k, const T &v) : pair(k, v), next(nullptr) {}
        value_type pair;
        Entry *next;
    };

    // ---------- Iterator ----------
    template <class U>
    class Iter {
    public:
        typedef U value_type;
        typedef ptrdiff_t difference_type;
        typedef std::forward_iterator_tag iterator_category;
        typedef value_type &reference;
        typedef value_type *pointer;

        Iter() : dict(nullptr), idx(0), cur(nullptr) {}
        Iter(const Dict<T> *d, size_type i, Entry *c) : dict(d), idx(i), cur(c) {}
        Iter(const Dict<T> *d) : dict(d) {
            for (idx = 0; idx < dict->buckets.size(); ++idx)
                if ((cur = dict->buckets[idx]) != nullptr)
                    return;
            dict = nullptr;  // End
            idx = 0;
        }
        bool operator==(const Iter &i) { return dict == i.dict && idx == i.idx && cur == i.cur; }
        bool operator!=(const Iter &i) { return !this->operator==(i); }

        reference operator*() { return cur->pair; }
        pointer operator->() { return &cur->pair; }
        Iter &operator++() {
            if ((cur = cur->next) == nullptr) {
                for (++idx; idx < dict->buckets.size(); ++idx)
                    if ((cur = dict->buckets[idx]) != nullptr)
                        return *this;
                dict = nullptr;
                cur = nullptr;
                idx = 0;
            }
            return *this;
        }
        Iter operator++(int) {
            Iter tmp = *this;
            ++*this;
            return tmp;
        }

    private:
        friend class Dict<T>;

        const Dict<T> *dict;
        size_type idx;
        Entry *cur;
    };

public:
    typedef Iter<value_type> iterator;
    typedef Iter<const value_type> const_iterator;
    friend class Iter<value_type>;
    friend class Iter<const value_type>;

    Dict(size_type table_size = 257);
    ~Dict();

    size_type size() const { return nentries; }

    iterator begin() { return iterator(this); }
    iterator end() { return iterator(); }
    const_iterator begin() const { return const_iterator(this); }
    const_iterator end() const { return const_iterator(); }

    iterator find(const key_type &key);
    const_iterator find(const key_type &key) const;

    T &lookup(const key_type &key) {
        Entry *e = find_entry(key);
        //     	    if (!e) throw XInvalidKey();
        return e->pair.second;
    }
    T &operator[](const key_type &key) { return lookup(key); }

    const T &lookup(const key_type &key) const {
        Entry *e = find_entry(key);
        if (!e)
            throw XInvalidKey();
        return e->pair.second;
    }

    const T &operator[](const key_type &key) const { return lookup(key); }

    bool has_key(const key_type &key) const;

    /*! Insert a new element into the table. */
    void insert(const key_type &key, const T &val);

    /*! Remove all entries from the hash table. */
    void clear();

    /*! Remove the entry with key \var key from the table. */
    void remove(const key_type &key);

    /*! Remove the element pointed to by an iterator. */
    void remove(iterator i);

private:
    Entry *find_entry(const key_type &key) const;

    // ----------  Variables ----------
    size_type nentries;  // Number of entries
    std::vector<Entry *> buckets;

private:
    // Copying not implemented yet
    Dict(const Dict<T> &d) = delete;
    Dict &operator=(const Dict<T> &d) = delete;
};

/* -------------------- Dict implementation -------------------- */

template <class T>
Dict<T>::Dict(size_type table_size)
: nentries(0), buckets(table_size, nullptr) {}

template <class T>
Dict<T>::~Dict() {
    clear();
}

template <class T>
typename Dict<T>::iterator Dict<T>::find(const key_type &key) {
    unsigned h = hash(key) % buckets.size();
    for (Entry *e = buckets[h]; e != nullptr; e = e->next)
        if (e->pair.first == key)
            return iterator(this, h, e);
    return end();
}

template <class T>
typename Dict<T>::const_iterator Dict<T>::find(const key_type &key) const {
    unsigned h = hash(key) % buckets.size();
    for (Entry *e = buckets[h]; e != 0; e = e->next)
        if (e->pair.first == key)
            return const_iterator(this, h, e);
    return end();
}

template <class T>
void Dict<T>::clear() {
    for (auto &entry : buckets) {
        Entry *cur, *next;
        for (cur = entry; cur != nullptr; cur = next) {
            next = cur->next;
            delete cur;
        }
        entry = nullptr;
    }
    nentries = 0;
}

template <class T>
void Dict<T>::insert(const key_type &key, const T &val) {
    unsigned h = hash(key) % buckets.size();
    auto e = new Entry(key, val);
    e->next = buckets[h];
    buckets[h] = e;
    nentries += 1;
}

template <class T>
void Dict<T>::remove(const key_type &key) {
    unsigned h = hash(key) % buckets.size();
    Entry *e = buckets[h];
    Entry **eptr = &buckets[h];

    while (e != nullptr) {
        if (e->pair.first == key) {
            *eptr = e->next;  // Modify pointer referring to e
            delete e;
            nentries -= 1;
            break;
        }
        eptr = &e->next;
        e = e->next;
    }
}

template <class T>
bool Dict<T>::has_key(const key_type &key) const {
    return find_entry(key) != nullptr;
}

template <class T>
typename Dict<T>::Entry *Dict<T>::find_entry(const key_type &key) const {
    unsigned h = hash(key) % buckets.size();
    for (Entry *e = buckets[h]; e != nullptr; e = e->next)
        if (e->pair.first == key)
            return e;
    return nullptr;
}

}  // namespace ecl

#endif
