#pragma once
#include <xs/Sv.h>
#include <iterator>
#include <xs/Scalar.h>
#include <xs/KeyProxy.h>
#include <xs/HashEntry.h>
#include <panda/string_view.h>

namespace xs {

struct Hash : Sv {
    static Hash create () { return Hash(newHV(), NONE); }

    static Hash create (size_t cap) {
        auto ret = create();
        ret.reserve(cap);
        return ret;
    }

    static Hash create (const std::initializer_list<std::pair<panda::string_view, Scalar>>& l) { return Hash(l); }

    static Hash noinc  (SV* val) { return Hash(val, NONE); }
    static Hash noinc  (HV* val) { return Hash(val, NONE); }

    Hash (std::nullptr_t = nullptr) {}
    Hash (SV* sv, bool policy = INCREMENT) : Sv(sv, policy) { _validate(); }
    Hash (HV* sv, bool policy = INCREMENT) : Sv(sv, policy) {}

    Hash (const Hash& oth) : Sv(oth)            {}
    Hash (Hash&&      oth) : Sv(std::move(oth)) {}
    Hash (const Sv&   oth) : Hash(oth.get())    {}
    Hash (Sv&&        oth) : Sv(std::move(oth)) { _validate(); }

    Hash (const Simple&) = delete;
    Hash (const Array&)  = delete;
    Hash (const Sub&)    = delete;
    Hash (const Glob&)   = delete;
    Hash (const Io&)     = delete;

    Hash (const std::initializer_list<std::pair<panda::string_view, Scalar>>&);

    Hash& operator= (SV* val)         { Sv::operator=(val); _validate(); return *this; }
    Hash& operator= (HV* val)         { Sv::operator=(val); return *this; }
    Hash& operator= (std::nullptr_t)  { Sv::operator=(nullptr); return *this; }
    Hash& operator= (const Hash& oth) { Sv::operator=(oth); return *this; }
    Hash& operator= (Hash&& oth)      { Sv::operator=(std::move(oth)); return *this; }
    Hash& operator= (const Sv& oth)   { return operator=(oth.get()); }
    Hash& operator= (Sv&& oth)        { Sv::operator=(std::move(oth)); _validate(); return *this; }
    Hash& operator= (const Simple&)   = delete;
    Hash& operator= (const Array&)    = delete;
    Hash& operator= (const Sub&)      = delete;
    Hash& operator= (const Glob&)     = delete;
    Hash& operator= (const Io&)       = delete;

    void set (SV* val) { Sv::operator=(val); }

    operator AV* () const = delete;
    operator HV* () const { return (HV*)sv; }
    operator CV* () const = delete;
    operator GV* () const = delete;
    operator IO* () const = delete;

    HV* operator-> () const { return (HV*)sv; }

    template <typename T = SV> panda::enable_if_one_of_t<T,SV,HV>* get () const { return (T*)sv; }

    Scalar fetch (const panda::string_view& key) const {
        if (!sv) return Scalar();
        SV** ref = hv_fetch((HV*)sv, key.data(), key.length(), 0);
        Scalar ret;
        if (ref) ret.set(*ref);
        return ret;
    }

    Scalar at (const panda::string_view& key) const {
        Scalar ret = fetch(key);
        if (!ret) throw std::out_of_range("at: no key");
        return ret;
    }

    Scalar operator[] (const panda::string_view& key) const { return fetch(key); }

    void store (const panda::string_view& key, const Scalar& val, U32 hash = 0);
    void store (const panda::string_view& key, std::nullptr_t,    U32 hash = 0) { store(key, Scalar(), hash); }
    void store (const panda::string_view& key, SV* v,             U32 hash = 0) { store(key, Scalar(v), hash); }
    void store (const panda::string_view& key, const Sv& v,       U32 hash = 0) { store(key, Scalar(v), hash); }
    void store (const panda::string_view& key, const Array&,      U32 hash = 0) = delete;
    void store (const panda::string_view& key, const Hash&,       U32 hash = 0) = delete;
    void store (const panda::string_view& key, const Sub&,        U32 hash = 0) = delete;
    void store (const panda::string_view& key, const Io&,         U32 hash = 0) = delete;

    KeyProxy operator[] (const panda::string_view& key) { return KeyProxy(hv_fetch((HV*)sv, key.data(), key.length(), 1), false); }

    Scalar erase (const panda::string_view& key) {
        Scalar ret;
        ret.set(hv_delete((HV*)sv, key.data(), key.length(), 0));
        return ret;
    }

    bool contains (const panda::string_view& key) const { return exists(key); }
    bool exists   (const panda::string_view& key) const {
        if (!sv) return false;
        return hv_exists((HV*)sv, key.data(), key.length());
    }

    size_t size     () const { return sv ? HvUSEDKEYS(sv) : 0; }
    size_t capacity () const { return sv ? HvMAX(sv)+1 : 0; }

    void reserve (size_t newcap) { hv_ksplit((HV*)sv, newcap); }

    void undef () { if (sv) hv_undef((HV*)sv); }
    void clear () { if (sv) hv_clear((HV*)sv); }

    struct const_iterator {
        using difference_type = std::ptrdiff_t;
        using value_type = const HashEntry;
        using pointer = value_type*;
        using reference = value_type&;
        using iterator_category = std::forward_iterator_tag;

        const_iterator () : arr(NULL), end(NULL), cur(HashEntry()) {}

        const_iterator (HV* hv) : arr(HvARRAY(hv)), end(arr ? arr + HvMAX(hv) + 1 : nullptr), cur(HashEntry()) {
            if (HvUSEDKEYS(hv)) operator++();
        }

        const_iterator (const const_iterator& oth) : arr(oth.arr), end(oth.end), cur(oth.cur) {}

        const_iterator& operator++ () {
            if (cur) {
                cur = HeNEXT(cur);
                if (cur) return *this;
            }
            while (!cur && arr != end) cur = *arr++;
            return *this;
        }

        const_iterator operator++ (int) {
            const_iterator ret = *this;
            operator++();
            return ret;
        }

        bool operator== (const const_iterator& oth) const { return cur == oth.cur; }
        bool operator!= (const const_iterator& oth) const { return cur != oth.cur; }

        const HashEntry* operator-> () { return &cur; }
        const HashEntry& operator*  () { return cur; }

        const_iterator& operator= (const const_iterator& oth) {
            arr = oth.arr;
            end = oth.end;
            cur = oth.cur;
            return *this;
        }

    protected:
        HE**      arr;
        HE**      end;
        HashEntry cur;
    };

    struct iterator : const_iterator {
        using const_iterator::const_iterator;

        using value_type = HashEntry;
        using pointer = value_type*;
        using reference = value_type&;

        HashEntry* operator-> () { return &cur; }
        HashEntry& operator*  () { return cur; }
    };

    const_iterator cbegin () const { return sv ? const_iterator((HV*)sv) : const_iterator(); }
    const_iterator cend   () const { return const_iterator(); }
    const_iterator begin  () const { return cbegin(); }
    const_iterator end    () const { return cend(); }
    iterator       begin  ()       { return sv ? iterator((HV*)sv) : iterator(); }
    iterator       end    ()       { return iterator(); }

    U32 push_on_stack (SV** sp) const;

private:
    void _validate () {
        if (!sv) return;
        if (SvTYPE(sv) == SVt_PVHV) return;
        if (SvROK(sv)) {           // reference to hash?
            SV* val = SvRV(sv);
            if (SvTYPE(val) == SVt_PVHV) {
                Sv::operator=(val);
                return;
            }
        }
        if (is_undef()) return reset();
        reset();
        throw std::invalid_argument("SV is not a Hash or Hash reference");
    }
};

}