#pragma once

#include <memory>
#include <cstdint>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <limits>

#include <boost/optional.hpp>

namespace Preserves {

    enum class ValueKind {
        Boolean,
        Float,
        Double,
        SignedInteger,
        String,
        ByteString,
        Symbol,

        Record,
        Sequence,
        Set,
        Dictionary,

        Embedded,
    };

    template <typename T = class GenericEmbedded> class Record;
    template <typename T = class GenericEmbedded> class ValueImpl;

    template <typename T = class GenericEmbedded>
    class Value {
        std::shared_ptr<ValueImpl<T>> p;

    public:
        Value(std::shared_ptr<ValueImpl<T>> const& p) : p(p) {}
        Value(ValueImpl<T> *p) : p(p) {}

        std::shared_ptr<ValueImpl<T>> _impl() const { return p; }

        static Value from_bool(bool b);
        static Value from_float(float f);
        static Value from_double(double d);
        static Value from_int(uint64_t i);
        static Value from_int(int64_t i);
        static Value from_string(std::string const& s);
        static Value from_bytes(std::vector<uint8_t> const& v);
        static Value from_bytes(std::vector<char> const& v);
        static Value from_bytes(void *p, size_t len);
        static Value from_symbol(std::string const& s);

        static Value record(Record<T> const& r);
        static Value record(Value const& label, std::vector<Value> const& fields);
        static Value sequence(std::vector<Value> const& items);
        static Value set(std::set<Value> const& items);
        static Value dictionary(std::map<Value, Value> const& entries);

        static Value from_embedded(std::shared_ptr<T> const& p);

        static Value from_unsigned(uint64_t i) { return from_int(i); }
        static Value from_signed(int64_t i) { return from_int(i); }
        static Value from_bignum(std::vector<uint8_t> const& v);

        static Value from_number(uint64_t i) { return from_int(i); }
        static Value from_number(int64_t i) { return from_int(i); }
        static Value from_number(float f) { return from_float(f); }
        static Value from_number(double d) { return from_double(d); }

        static Value from(bool b) { return from_bool(b); }
        static Value from(float f) { return from_float(f); }
        static Value from(double d) { return from_double(d); }
        static Value from(uint64_t i) { return from_int(i); }
        static Value from(unsigned i) { return from_int(uint64_t(i)); }
        static Value from(int64_t i) { return from_int(i); }
        static Value from(signed i) { return from_int(int64_t(i)); }
        static Value from(std::string const& s) { return from_string(s); }
        static Value from(char const* s) { return from_string(s); }
        static Value from(std::vector<uint8_t> const& v) { return from_bytes(v); }
        static Value from(std::vector<char> const& v) { return from_bytes(v); }
        static Value from(void *p, size_t len) { return from_bytes(p, len); }
        static Value from(Record<T> const& r) { return record(r); }
        static Value from(Value const& label, std::vector<Value> const& fields) { return record(label, fields); }
        static Value from(std::vector<Value> const& items) { return sequence(items); }
        static Value from(std::set<Value> const& items) { return set(items); }
        static Value from(std::map<Value, Value> const& entries) { return dictionary(entries); }

        ValueImpl<T>& operator*() const { return *p; }
        ValueImpl<T>* operator->() const { return p.get(); }

        ValueKind value_kind() const;
        bool is_mutable() const;

        bool is_bool() const { return value_kind() == ValueKind::Boolean; }
        bool is_float() const { return value_kind() == ValueKind::Float; }
        bool is_double() const { return value_kind() == ValueKind::Double; }
        bool is_int() const { return value_kind() == ValueKind::SignedInteger; }
        bool is_string() const { return value_kind() == ValueKind::String; }
        bool is_bytes() const { return value_kind() == ValueKind::ByteString; }
        bool is_symbol() const { return value_kind() == ValueKind::Symbol; }
        bool is_record() const { return value_kind() == ValueKind::Record; }
        bool is_sequence() const { return value_kind() == ValueKind::Sequence; }
        bool is_set() const { return value_kind() == ValueKind::Set; }
        bool is_dictionary() const { return value_kind() == ValueKind::Dictionary; }

        boost::optional<bool> as_bool() const;
        bool to_bool() const { return as_bool().value(); }

        boost::optional<float> as_float() const;
        float to_float() const { return as_float().value(); }

        boost::optional<double> as_double() const;
        double to_double() const { return as_double().value(); }

        boost::optional<uint64_t> as_unsigned() const;
        uint64_t to_unsigned() const { return as_unsigned().value(); }

        boost::optional<int64_t> as_signed() const;
        int64_t to_signed() const { return as_signed().value(); }

        boost::optional<std::vector<uint8_t> const&> as_bignum() const;
        std::vector<uint8_t> const& to_bignum() const { return as_bignum().value(); }

        boost::optional<std::string const&> as_string() const;
        std::string const& to_string() const { return as_string().value(); }

        boost::optional<std::vector<uint8_t> const&> as_bytes() const;
        std::vector<uint8_t> const& to_bytes() const { return as_bytes().value(); }

        boost::optional<std::string const&> as_symbol() const;
        std::string const& to_symbol() const { return as_symbol().value(); }

        boost::optional<Record<T> const&> as_record() const;
        Record<T> const& to_record() const { return as_record().value(); };

        boost::optional<std::vector<Value> const&> as_sequence() const;
        std::vector<Value> const& to_sequence() const { return as_sequence().value(); }

        boost::optional<std::set<Value> const&> as_set() const;
        std::set<Value> const& to_set() const { return as_set().value(); }

        boost::optional<std::map<Value,Value> const&> as_dictionary() const;
        std::map<Value,Value> const& to_dictionary() const { return as_dictionary().value(); }

        boost::optional<std::shared_ptr<T>> as_embedded() const;
        std::shared_ptr<T> to_embedded() const { return as_embedded().value(); }

        Value annotate(Value const& ann) const;
        boost::optional<std::vector<Value> const&> annotations() const;

        boost::optional<Value> label() const;
        size_t size() const;
        bool contains(Value const& key) const;
        boost::optional<Value> get(Value const& key) const;
        Value operator[](Value const& key) const { return get(key).value(); }
        boost::optional<Value> get(size_t index) const;
        Value operator[](size_t index) const { return get(index).value(); }
    };

    class BinaryWriter; // forward declaration; see preserves_binary_writer.hpp

    template <typename T>
    class ValueImpl {
    protected:
        virtual Value<T> internal_annotate(std::shared_ptr<ValueImpl<T>> const& self, Value<T> const& ann);

        friend class Value<T>;
    public:
        virtual ~ValueImpl() {}

        virtual ValueKind value_kind() const = 0;
        virtual bool is_mutable() const { return false; }

        virtual boost::optional<bool> as_bool() const { return boost::none; }
        virtual boost::optional<float> as_float() const { return boost::none; }
        virtual boost::optional<double> as_double() const { return boost::none; }
        virtual boost::optional<uint64_t> as_unsigned() const { return boost::none; }
        virtual boost::optional<int64_t> as_signed() const { return boost::none; }
        virtual boost::optional<std::vector<uint8_t> const&> as_bignum() const { return boost::none; }
        virtual boost::optional<std::string const&> as_string() const { return boost::none; }
        virtual boost::optional<std::vector<uint8_t> const&> as_bytes() const { return boost::none; }
        virtual boost::optional<std::string const&> as_symbol() const { return boost::none; }
        virtual boost::optional<Record<T> const&> as_record() const { return boost::none; }
        virtual boost::optional<std::vector<Value<T>> const&> as_sequence() const { return boost::none; }
        virtual boost::optional<std::set<Value<T>> const&> as_set() const { return boost::none; }
        virtual boost::optional<std::map<Value<T>,Value<T>> const&> as_dictionary() const { return boost::none; }
        virtual boost::optional<std::shared_ptr<T>> as_embedded() const { return boost::none; }

        virtual boost::optional<Value<T>> label() const { return boost::none; } 
        virtual size_t size() const { return 0; }
        virtual bool contains(Value<T> const& /* key */) const { return false; }
        virtual boost::optional<Value<T>> get(Value<T> const& /* key */) const { return boost::none; }
        virtual boost::optional<Value<T>> get(size_t /* index */) const { return boost::none; }

        virtual bool add(Value<T> const& /* item */) {
            throw std::runtime_error("Cannot add item to Preserves value");
        }
        virtual bool set(Value<T> const& /* key */, Value<T> const& /* value */) {
            throw std::runtime_error("Cannot set item by key in Preserves value");
        }
        virtual bool set(size_t /* index */, Value<T> const& /* value */) {
            throw std::runtime_error("Cannot set item by index in Preserves value");
        }
        virtual bool erase(Value<T> const& /* key */) {
            throw std::runtime_error("Cannot erase item in Preserves value");
        }

        virtual boost::optional<std::vector<Value<T>> const&> annotations() const { return boost::none; }

        virtual BinaryWriter& write(BinaryWriter& w) const = 0;
    };

    template <typename T> ValueKind Value<T>::value_kind() const { return p->value_kind(); }
    template <typename T> bool Value<T>::is_mutable() const { return p->is_mutable(); }

#define PRESERVES_DELEGATE_CAST(t, name) \
    template <typename T> boost::optional<t> Value<T>::name() const { return p->name(); }
    PRESERVES_DELEGATE_CAST(bool, as_bool);
    PRESERVES_DELEGATE_CAST(float, as_float);
    PRESERVES_DELEGATE_CAST(double, as_double);
    PRESERVES_DELEGATE_CAST(uint64_t, as_unsigned);
    PRESERVES_DELEGATE_CAST(int64_t, as_signed);
    PRESERVES_DELEGATE_CAST(std::vector<uint8_t> const&, as_bignum);
    PRESERVES_DELEGATE_CAST(std::string const&, as_string);
    PRESERVES_DELEGATE_CAST(std::vector<uint8_t> const&, as_bytes);
    PRESERVES_DELEGATE_CAST(std::string const&, as_symbol);
    PRESERVES_DELEGATE_CAST(Record<T> const&, as_record);
    PRESERVES_DELEGATE_CAST(std::vector<Value<T>> const&, as_sequence);
    PRESERVES_DELEGATE_CAST(std::set<Value<T>> const&, as_set);
    #define COMMA ,
    PRESERVES_DELEGATE_CAST(std::map<Value<T> COMMA Value<T>> const&, as_dictionary);
    #undef COMMA
    PRESERVES_DELEGATE_CAST(std::shared_ptr<T>, as_embedded);
#undef PRESERVES_DELEGATE_CAST

    template <typename T> boost::optional<Value<T>> Value<T>::label() const { return p->label(); }
    template <typename T> bool Value<T>::contains(Value const& key) const { return p->contains(key); }
    template <typename T> boost::optional<Value<T>> Value<T>::get(Value<T> const& key) const { return p->get(key); }
    template <typename T> boost::optional<Value<T>> Value<T>::get(size_t index) const { return p->get(index); }
    template <typename T> size_t Value<T>::size() const { return p->size(); }

    inline bool bignum_lt(std::vector<uint8_t> const& a, std::vector<uint8_t> const& b) {
        bool aNegative = (a.size() > 0) && (a[0] & 0x80);
        bool bNegative = (b.size() > 0) && (b[0] & 0x80);
        if (aNegative != bNegative) return aNegative;
        if (aNegative) {
            if (a.size() > b.size()) return true;
            if (a.size() < b.size()) return false;
            return a < b;
        } else {
            if (a.size() > b.size()) return false;
            if (a.size() < b.size()) return true;
            return a < b;
        }
    }

    template <typename T>
    bool operator<(Value<T> const& a, Value<T> const &b) {
        auto aKind = a.value_kind();
        auto bKind = b.value_kind();
        if (aKind < bKind) return true;
        if (bKind < aKind) return false;
        switch (aKind) {
            case ValueKind::Boolean: return a.to_bool() < b.to_bool();
            case ValueKind::Float: return a.to_float() < b.to_float();
            case ValueKind::Double: return a.to_double() < b.to_double();
            case ValueKind::SignedInteger: {
                if (auto av = a.as_signed()) {
                    if (auto bv = b.as_signed()) {
                        return *av < *bv;
                    } else {
                        return true;
                    }
                } else {
                    if (auto bv = b.as_signed()) {
                        return false;
                    } else {
                        if (auto av = a.as_unsigned()) {
                            if (auto bv = b.as_unsigned()) {
                                return *av < *bv;
                            } else {
                                return true;
                            }
                        } else {
                            if (auto bv = b.as_unsigned()) {
                                return false;
                            } else {
                                return bignum_lt(a.to_bignum(), b.to_bignum());
                            }
                        }
                    }
                }
            }
            case ValueKind::String: return a.to_string() < b.to_string();
            case ValueKind::ByteString: return a.to_bytes() < b.to_bytes();
            case ValueKind::Symbol: return a.to_symbol() < b.to_symbol();
            case ValueKind::Record: return a.to_record() < b.to_record();
            case ValueKind::Sequence: return a.to_sequence() < b.to_sequence();
            case ValueKind::Set: return a.to_set() < b.to_set();
            case ValueKind::Dictionary: return a.to_dictionary() < b.to_dictionary();
            case ValueKind::Embedded: return *a.to_embedded() < *b.to_embedded();
            default: throw std::runtime_error("Invalid ValueKind");
        }
    }
}
