#ifndef CORE_HASHMAP_H
#define CORE_HASHMAP_H

#include "data/LinkedList.h"
#include "data/List.h"
#include "utils/ArrayString.h"
#include "utils/HashCode.h"

namespace Core {
    template<typename K, typename V>
    struct HashMap final {
        class Node final {
            friend HashMap;
            friend List<Node>;
            friend LinkedList<Node>;
            K key;

        public:
            V value;

            const K& getKey() const {
                return key;
            }

            template<typename String>
            check_return Error toString(String& s) const {
                CORE_RETURN_ERROR(s.append(key));
                CORE_RETURN_ERROR(s.append(" = "));
                return s.append(value);
            }

        private:
            template<typename... Args>
            Node(const K& key_, Args&&... args)
                : key(key_), value(Core::forward<Args>(args)...) {
            }
        };

    private:
        using NodePointer = LinkedList<Node>::Node*;
        using NodePointerList = List<NodePointer>;
        using NodeIterator = LinkedList<Node>::Iterator;
        using ConstNodeIterator = LinkedList<Node>::ConstIterator;

        template<typename N, typename I, typename R, R& (*A)(I&)>
        class Iterator final {
            N iterator;

        public:
            Iterator(N iterator_) : iterator(iterator_) {
            }

            Iterator& operator++() {
                ++iterator;
                return *this;
            }

            bool operator!=(const Iterator& other) const {
                return iterator != other.iterator;
            }

            R& operator*() const {
                return A(*iterator);
            }
        };

        template<typename R>
        static R& access(R& node) {
            return node;
        }

        template<typename I, typename R>
        static R& accessValue(I& node) {
            return node.value;
        }

        static const K& accessKey(const Node& node) {
            return node.getKey();
        }

        template<typename N, typename R>
        using BaseEntryIterator = Iterator<N, R, R, access<R>>;
        using EntryIterator = BaseEntryIterator<NodeIterator, Node>;
        using ConstEntryIterator =
            BaseEntryIterator<ConstNodeIterator, const Node>;

        template<typename N, typename I, typename R>
        using BaseValueIterator = Iterator<N, I, R, accessValue<I, R>>;
        using ValueIterator = BaseValueIterator<NodeIterator, Node, V>;
        using ConstValueIterator =
            BaseValueIterator<ConstNodeIterator, const Node, const V>;

        using ConstKeyIterator =
            Iterator<ConstNodeIterator, const Node, const K, accessKey>;

        template<typename M, typename I>
        struct IteratorAdapter final {
            M& map;

            I begin() const {
                return I(map.nodes.begin());
            }

            I end() const {
                return I(map.nodes.end());
            }
        };

        using ValueIteratorAdapter = IteratorAdapter<HashMap, ValueIterator>;
        using ConstValueIteratorAdapter =
            IteratorAdapter<const HashMap, ConstValueIterator>;

        using ConstKeyIteratorAdapter =
            IteratorAdapter<const HashMap, ConstKeyIterator>;

    private:
        LinkedList<Node> nodes;
        List<NodePointerList> nodePointers;

    public:
        check_return Error copyFrom(const HashMap& other) {
            HashMap copy;
            for(const auto& en : other) {
                CORE_RETURN_ERROR(copy.add(en.getKey(), en.value));
            }
            swap(copy.nodes, nodes);
            swap(copy.nodePointers, nodePointers);
            return Error::NONE;
        }

        check_return Error rehash(int minCapacity) {
            if(minCapacity <= nodePointers.getLength()) {
                return Error::NONE;
            }
            HashMap<K, V> map;
            int l = Core::Math::max(1 << Math::roundUpLog2(minCapacity), 8);
            CORE_RETURN_ERROR(map.nodePointers.resize(l));
            for(NodePointerList& list : nodePointers) {
                for(NodePointer& n : list) {
                    int h = map.hashIndex(n->data.key);
                    CORE_RETURN_ERROR(map.nodePointers[h].add(n));
                }
            }
            Core::swap(map.nodePointers, nodePointers);
            return Error::NONE;
        }

        template<typename... Args>
        check_return Error tryEmplace(V*& v, const K& key, Args&&... args) {
            CORE_RETURN_ERROR(rehash(nodes.getLength() + 1));
            int h = hashIndex(key);
            v = searchList(key, h);
            if(v != nullptr) {
                return Error::EXISTING_KEY;
            }
            NodePointer np = nullptr;
            CORE_RETURN_ERROR(nodes.put(np, key, Core::forward<Args>(args)...));
            Error e = Error::NONE;
            if(checkError(e, nodePointers[h].add(np))) {
                nodes.remove(np);
                return e;
            }
            v = &(np->data.value);
            return Error::NONE;
        }

        template<typename VA>
        check_return Error put(V*& v, const K& key, VA&& value) {
            CORE_RETURN_ERROR(rehash(nodes.getLength() + 1));
            int h = hashIndex(key);
            v = searchList(key, h);
            if(v != nullptr) {
                *v = Core::forward<VA>(value);
                return Error::NONE;
            }
            NodePointer np = nullptr;
            CORE_RETURN_ERROR(nodes.put(np, key, Core::forward<VA>(value)));
            Error e = Error::NONE;
            if(checkError(e, nodePointers[h].add(np))) {
                nodes.remove(np);
                return e;
            }
            v = &(np->data.value);
            return Error::NONE;
        }

        template<typename VA>
        check_return Error add(const K& key, VA&& value) {
            V* v = nullptr;
            return put(v, key, Core::forward<VA>(value));
        }

        check_return Error remove(const K& key) {
            NodePointerList& list = nodePointers[hashIndex(key)];
            for(int i = 0; i < list.getLength(); i++) {
                if(list[i]->data.key == key) {
                    nodes.remove(list[i]);
                    return list.removeBySwap(i);
                }
            }
            return Error::NOT_FOUND;
        }

        const V* search(const K& key) const {
            return searchList(key, hashIndex(key));
        }

        V* search(const K& key) {
            return searchList(key, hashIndex(key));
        }

        bool contains(const K& key) const {
            return search(key) != nullptr;
        }

        HashMap& clear() {
            nodes.clear();
            for(NodePointerList& n : nodePointers) {
                n.clear();
            }
            return *this;
        }

        ConstKeyIteratorAdapter getKeys() const {
            return {*this};
        }

        ValueIteratorAdapter getValues() {
            return {*this};
        }

        ConstValueIteratorAdapter getValues() const {
            return {*this};
        }

        EntryIterator begin() {
            return EntryIterator(nodes.begin());
        }

        EntryIterator end() {
            return EntryIterator(nodes.end());
        }

        ConstEntryIterator begin() const {
            return ConstEntryIterator(nodes.begin());
        }

        ConstEntryIterator end() const {
            return ConstEntryIterator(nodes.end());
        }

        template<typename String>
        check_return Error toString(String& s) const {
            return Core::toString(s, *this);
        }

    private:
        template<typename H>
        int hashIndex(const H& key) const {
            return static_cast<int>(hashCode(key)) &
                   (nodePointers.getLength() - 1);
        }

        const V* searchList(const K& key, int h) const {
            if(nodePointers.getLength() == 0) {
                return nullptr;
            }
            for(const NodePointer& n : nodePointers[h]) {
                if(n->data.key == key) {
                    return &n->data.value;
                }
            }
            return nullptr;
        }

        V* searchList(const K& key, int h) {
            return const_cast<V*>(
                static_cast<const HashMap*>(this)->searchList(key, h));
        }
    };
}

#endif