#ifndef UCI_GUARD_HASHTABLE_H
#define UCI_GUARD_HASHTABLE_H

#include "types.h"
#include <utility>      // std::pair
#include <functional>   // std::hash
#include <type_traits>
#include <cassert>

// Default policy; replace everything with the newest entry
template <typename T>
struct policy {
    constexpr bool operator()(const T&, const T&) {
        return true;
    }
};

template <
    typename Key,
    typename Entry,
    typename Policy = policy<Entry>,
    typename Hashing = std::hash<Key>
>
class HashTable {
public:
    using Hash = typename Hashing::result_type;
    using Line = typename std::pair<Hash, Entry>;

    HashTable() : _size{0}, _occupied{0}, _table{nullptr}, _hash{}, _policy{} { };
    HashTable(Index size)
        : _size{size}
        , _occupied{0}
        , _table{new (std::nothrow) Line[size]}
        , _hash{}
        , _policy{}
    {
        if (_table) {
            for (Index i = 0; i < _size; i++) {
                _table[i].first = static_cast<Hash>(0);
            }
        } else {
            _size = 0;
        }
    };

    ~HashTable() { if (_table) { delete[] _table; } }

    Index size() const { return _size; }
    Index used() const { return 1000 * _occupied / _size; }

    bool reserve(Index size) noexcept {
        _occupied = 0;
        if (size != _size) {
            if (_table) { delete[] _table; }
            _table = new (std::nothrow) Line[size];
        }
        if (_table) {
            _size = size;
            for (Index i = 0; i < _size; i++) {
                _table[i].first = static_cast<Hash>(0);
            }
            return false;
        } else {
            _size = 0;
            return true;
        }
    }

    void clear() noexcept {
        _occupied = 0;
        for (Index i = 0; i < _size; i++) {
            _table[i].first = static_cast<Hash>(0);
        }
    }

    void erase() {
        _occupied = 0;
        _size = 0;
        if (_table) { delete[] _table; }
        _table = nullptr;
    }

    void insert(const Key& key, const Entry& entry) {
        assert(0 < _size);

        Hash hash = _hash(key);
        Index index = static_cast<Index>(hash) % _size;
        if (_table[index].first == static_cast<Hash>(0)) {
            _occupied++;
            _table[index] = Line(hash, entry);
        }
        if (_policy(_table[index].second, entry)) {
            _table[index] = Line(hash, entry);
        }
    }

    struct Iter {
        using iterator_category = std::forward_iterator_tag;
        using difference_type   = std::ptrdiff_t;
        using value_type        = Line;
        using pointer           = value_type*;
        using reference         = Entry&;

        Iter(pointer ptr) : _ptr{ptr} { };

        const Hash& hash() const { return _ptr->first; };
        const Entry& value() const { return _ptr->second; };

        // dereference operators
        reference operator*() { return _ptr->second; };
        pointer operator->() { return _ptr; };

        // comparison operators
        friend bool operator==(const Iter& lhs, const Iter& rhs) {
            return lhs._ptr == rhs._ptr;
        };
        friend bool operator!=(const Iter& lhs, const Iter& rhs) {
            return lhs._ptr != rhs._ptr;
        };

    private:
        pointer _ptr;
    };

    Iter begin() noexcept { return Iter(_table); }
    Iter end()   noexcept { return Iter(_table + _size); }

    Iter find(const Key& key) noexcept {
        if (_size) {
            Hash hash = _hash(key);
            if (hash == static_cast<Hash>(0)) {
                return Iter(_table + _size);
            }
            Index index = static_cast<Index>(hash) % _size;
            if (_table[index].first == hash) {
                return Iter(_table + index);
            }
        }
        return Iter(_table + _size);
    }

private:
    Index _size;
    Index _occupied;
    Line* _table;
    Hashing _hash;
    Policy _policy;
};

#endif /* UCI_GUARD_HASHTABLE_H */
