#pragma once

#include "scheme.h"

#include <util/stream/output.h>

namespace NSc {
    struct TValue::TScCore : TAtomicRefCount<TScCore, TDestructor>, TNonCopyable {
        TPoolPtr Pool;
        double FloatNumber = 0;
        i64 IntNumber = 0;
        TStringBuf String;
        TDict Dict;
        TArray Array;
        TValue::EType ValueType = TValue::EType::Null;

        TScCore(TPoolPtr& p)
            : Pool(p)
            , Dict(Pool->Get())
            , Array(Pool->Get())
        {
        }

        bool IsNull() const {
            return TValue::EType::Null == ValueType;
        }

        bool IsBool() const {
            return TValue::EType::Bool == ValueType;
        }

        bool IsIntNumber() const {
            return TValue::EType::IntNumber == ValueType || IsBool();
        }

        bool IsNumber() const {
            return TValue::EType::FloatNumber == ValueType || IsIntNumber();
        }

        bool IsString() const {
            return TValue::EType::String == ValueType;
        }

        bool IsArray() const {
            return TValue::EType::Array == ValueType;
        }

        bool IsDict() const {
            return TValue::EType::Dict == ValueType;
        }

        bool HasChildren() const {
            return GetDict().size() + GetArray().size();
        }

        void SetNull() {
            ValueType = TValue::EType::Null;
        }

        void SetArray() {
            if (Y_LIKELY(IsArray())) {
                return;
            }

            ValueType = TValue::EType::Array;
            Array.clear();
        }

        void SetDict() {
            if (Y_LIKELY(IsDict())) {
                return;
            }

            ValueType = TValue::EType::Dict;
            Dict.clear();
        }

        void SetNumber(double n) {
            ValueType = TValue::EType::FloatNumber;
            FloatNumber = n;
        }

        void SetIntNumber(i64 n) {
            ValueType = TValue::EType::IntNumber;
            IntNumber = n;
        }

        void SetBool(bool b) {
            ValueType = TValue::EType::Bool;
            IntNumber = b;
        }

        void SetString(TStringBuf s) {
            SetOwnedString(Pool->AppendBuf(s));
        }

        void SetOwnedString(TStringBuf s) {
            ValueType = TValue::EType::String;
            String = s;
        }

        double& GetNumberMutable(double defaultnum) {
            switch (ValueType) {
                case TValue::EType::Bool:
                    SetNumber(bool(IntNumber));
                    break;
                case TValue::EType::IntNumber:
                    SetNumber(IntNumber);
                    break;
                case TValue::EType::FloatNumber:
                    break;
                default:
                    SetNumber(defaultnum);
                    break;
            }

            return FloatNumber;
        }

        i64& GetIntNumberMutable(i64 defaultnum) {
            switch (ValueType) {
                case TValue::EType::Bool:
                    SetIntNumber(bool(IntNumber));
                    break;
                case TValue::EType::IntNumber:
                    break;
                case TValue::EType::FloatNumber:
                    SetIntNumber(FloatNumber);
                    break;
                default:
                    SetIntNumber(defaultnum);
                    break;
            }

            return IntNumber;
        }

        void ClearArray() {
            if (!IsArray()) {
                return;
            }

            Array.clear();
        }

        void ClearDict() {
            if (!IsDict()) {
                return;
            }

            Dict.clear();
        }

        double GetNumber(double d = 0) const {
            switch (ValueType) {
                case TValue::EType::Bool:
                    return (bool)IntNumber;
                case TValue::EType::IntNumber:
                    return IntNumber;
                case TValue::EType::FloatNumber:
                    return FloatNumber;
                default:
                    return d;
            }
        }

        i64 GetIntNumber(i64 n = 0) const {
            switch (ValueType) {
                case TValue::EType::Bool:
                    return (bool)IntNumber;
                case TValue::EType::IntNumber:
                    return IntNumber;
                case TValue::EType::FloatNumber:
                    return FloatNumber;
                default:
                    return n;
            }
        }

        bool GetBool(bool b = false) const {
            return GetIntNumber(b);
        }

        TStringBuf GetString(TStringBuf s = TStringBuf()) const {
            return IsString() ? String : s;
        }

        const TArray& GetArray() const {
            return IsArray() ? Array : TValue::DefaultArray();
        }

        TArray& GetArrayMutable() {
            SetArray();
            return Array;
        }

        TDict& GetDictMutable() {
            SetDict();
            return Dict;
        }

        const TDict& GetDict() const {
            return IsDict() ? Dict : TValue::DefaultDict();
        }

        static void DoPush(TPoolPtr& p, TArray& a) {
            a.push_back(TValue(p));
            a.back().CopyOnWrite = false;
        }

        TValue& Push() {
            SetArray();
            DoPush(Pool, Array);
            return Array.back();
        }

        TValue Pop() {
            if (!IsArray() || Array.empty()) {
                return TValue::DefaultValue();
            }

            TValue v = Array.back();
            Array.pop_back();
            return v;
        }

        const TValue& Get(size_t key) const {
            return IsArray() && Array.size() > key ? Array[key] : TValue::DefaultValue();
        }

        TValue* GetNoAdd(size_t key) {
            return IsArray() && Array.size() > key ? &Array[key] : nullptr;
        }

        TValue& GetOrAdd(size_t key) {
            SetArray();
            for (size_t i = Array.size(); i <= key; ++i) {
                DoPush(Pool, Array);
            }

            return Array[key];
        }

        TValue& Back() {
            SetArray();

            if (Array.empty()) {
                DoPush(Pool, Array);
            }

            return Array.back();
        }

        TValue& Insert(size_t key) {
            SetArray();

            if (Array.size() <= key) {
                return GetOrAdd(key);
            } else {
                Array.insert(Array.begin() + key, TValue(Pool));
                Array[key].CopyOnWrite = false;
                return Array[key];
            }
        }

        TValue Delete(size_t key) {
            if (!IsArray() || Array.size() <= key) {
                return TValue::DefaultValue();
            }

            TValue v = Array[key];
            Array.erase(Array.begin() + key);
            return v;
        }

        const TValue& Get(TStringBuf key) const {
            if (!IsDict()) {
                return TValue::DefaultValue();
            }

            TDict::const_iterator it = Dict.find(key);
            return it != Dict.end() ? it->second : TValue::DefaultValue();
        }

        TValue* GetNoAdd(TStringBuf key) {
            if (!IsDict()) {
                return nullptr;
            }

            return Dict.FindPtr(key);
        }

        TValue& Add(TStringBuf key) {
            SetDict();
            TDict::iterator it = Dict.insert(std::make_pair(Pool->AppendBuf(key), TValue(Pool))).first;
            it->second.CopyOnWrite = false;
            return it->second;
        }

        TValue& GetOrAdd(TStringBuf key) {
            SetDict();
            TDict::insert_ctx ctx;
            TDict::iterator it = Dict.find(key, ctx);

            if (it == Dict.end()) {
                it = Dict.insert_direct(std::make_pair(Pool->AppendBuf(key), TValue(Pool)), ctx);
                it->second.CopyOnWrite = false;
            }

            return it->second;
        }

        TValue Delete(TStringBuf key) {
            if (!IsDict()) {
                return TValue::DefaultValue();
            }

            TDict::iterator it = Dict.find(key);

            if (it == Dict.end()) {
                return TValue::DefaultValue();
            }

            TValue v = it->second;
            Dict.erase(key);
            return v;
        }
    };

    TValue::TScCore* TValue::NewCore(TPoolPtr& p) {
        return new (p->Pool.Allocate<TScCore>()) TScCore(p);
    }

    TValue::TValue() {
        auto p = TPoolPtr(new NDefinitions::TPool);
        TheCore = NewCore(p);
    }

    TValue::TValue(double t)
        : TValue()
    {
        SetNumber(t);
    }

    TValue::TValue(unsigned long long t)
        : TValue()
    {
        SetIntNumber(t);
    }

    TValue::TValue(unsigned long t)
        : TValue()
    {
        SetIntNumber(t);
    }

    TValue::TValue(unsigned t)
        : TValue()
    {
        SetIntNumber(t);
    }

    TValue::TValue(long long t)
        : TValue()
    {
        SetIntNumber(t);
    }

    TValue::TValue(long t)
        : TValue()
    {
        SetIntNumber(t);
    }

    TValue::TValue(int t)
        : TValue()
    {
        SetIntNumber(t);
    }

    //TValue::TValue(bool t)
    //    : TValue()
    //{
    //    SetBool(t);
    //}

    TValue::TValue(TStringBuf t)
        : TValue()
    {
        SetString(t);
    }

    TValue::TValue(const char* t)
        : TValue()
    {
        SetString(t);
    }

    TValue::TValue(TValue& v)
        : TheCore(v.TheCore)
        , CopyOnWrite(v.CopyOnWrite)
    {
    }

    TValue::TValue(const TValue& v)
        : TheCore(v.TheCore)
        , CopyOnWrite(true)
    {
    }

    TValue::TValue(TValue&& v) noexcept
        : TheCore(std::move(v.TheCore))
        , CopyOnWrite(v.CopyOnWrite)
    {}

    TValue::operator double() const {
        return GetNumber();
    }

    TValue::operator float() const {
        return GetNumber();
    }

    TValue::operator long long() const {
        return GetIntNumber();
    }

    TValue::operator long() const {
        return GetIntNumber();
    }

    TValue::operator int() const {
        return GetIntNumber();
    }

    TValue::operator short() const {
        return GetIntNumber();
    }

    TValue::operator char() const {
        return GetIntNumber();
    }

    TValue::operator unsigned long long() const {
        return GetIntNumber();
    }

    TValue::operator unsigned long() const {
        return GetIntNumber();
    }

    TValue::operator unsigned() const {
        return GetIntNumber();
    }

    TValue::operator unsigned short() const {
        return GetIntNumber();
    }

    TValue::operator unsigned char() const {
        return GetIntNumber();
    }

    TValue::operator signed char() const {
        return GetIntNumber();
    }

    TValue::operator TStringBuf() const {
        return GetString();
    }

    TValue::operator const ::NSc::TArray&() const {
        return GetArray();
    }

    TValue::operator const ::NSc::TDict&() const {
        return GetDict();
    }

    TValue& TValue::operator=(double t) {
        return SetNumber(t);
    }

    TValue& TValue::operator=(unsigned long long t) {
        return SetIntNumber(t);
    }

    TValue& TValue::operator=(unsigned long t) {
        return SetIntNumber(t);
    }

    TValue& TValue::operator=(unsigned t) {
        return SetIntNumber(t);
    }

    TValue& TValue::operator=(long long t) {
        return SetIntNumber(t);
    }

    TValue& TValue::operator=(long t) {
        return SetIntNumber(t);
    }

    TValue& TValue::operator=(int t) {
        return SetIntNumber(t);
    }

    //TValue& TValue::operator=(bool t) {
    //    return SetBool(t);
    //}

    TValue& TValue::operator=(TStringBuf t) {
        return SetString(t);
    }

    TValue& TValue::operator=(const char* t) {
        return SetString(t);
    }

    TValue& TValue::operator=(TValue& v) & {
        if (!Same(*this, v)) {
            //Extend TheCore lifetime not to trigger possible v deletion via parent-child chain
            auto tmpCore = TheCore;
            TheCore = v.TheCore;
            CopyOnWrite = v.CopyOnWrite;
        }
        return *this;
    }

    TValue& TValue::operator=(const TValue& v) & {
        if (!Same(*this, v)) {
            //Extend TheCore lifetime not to trigger possible v deletion via parent-child chain
            auto tmpCore = TheCore;
            TheCore = v.TheCore;
            CopyOnWrite = true;
        }
        return *this;
    }

    TValue& TValue::operator=(TValue&& v) & noexcept {
        if (!Same(*this, v)) {
            //Extend TheCore lifetime not to trigger possible v deletion via parent-child chain
            auto tmpCore = TheCore;
            TheCore = std::move(v.TheCore);
            CopyOnWrite = v.CopyOnWrite;
        }
        return *this;
    }

    bool TValue::Has(size_t idx) const {
        return IsArray() && GetArray().size() > idx;
    }

    bool TValue::Has(TStringBuf s) const {
        return GetDict().contains(s);
    }

    TValue& TValue::GetOrAddUnsafe(size_t idx) {
        return CoreMutable().GetOrAdd(idx);
    }

    TValue& TValue::GetOrAdd(TStringBuf idx) {
        return CoreMutable().GetOrAdd(idx);
    }

    const TValue& TValue::Get(size_t idx) const {
        return Core().Get(idx);
    }

    TValue* TValue::GetNoAdd(size_t idx) {
        return CoreMutable().GetNoAdd(idx);
    }

    const TValue& TValue::Get(TStringBuf idx) const {
        return Core().Get(idx);
    }

    TValue* TValue::GetNoAdd(TStringBuf key) {
        return CoreMutable().GetNoAdd(key);
    }

    TValue& TValue::Back() {
        return CoreMutable().Back();
    }

    const TValue& TValue::Back() const {
        const TArray& arr = GetArray();
        return arr.empty() ? DefaultValue() : arr.back();
    }

    TValue TValue::Delete(size_t idx) {
        return CoreMutable().Delete(idx);
    }

    TValue TValue::Delete(TStringBuf idx) {
        return CoreMutable().Delete(idx);
    }

    TValue& TValue::AddAll(std::initializer_list<std::pair<TStringBuf, TValue>> t) {
        for (const auto& el : t) {
            Add(el.first) = el.second;
        }
        return *this;
    }

    TValue& TValue::InsertUnsafe(size_t idx) {
        return CoreMutable().Insert(idx);
    }

    template <class TIt>
    TValue& TValue::AppendAll(TIt begin, TIt end) {
        GetArrayMutable().AppendAll(begin, end);
        return *this;
    }

    template <class TColl>
    TValue& TValue::AppendAll(TColl&& coll) {
        return AppendAll(std::begin(coll), std::end(coll));
    }

    TValue& TValue::AppendAll(std::initializer_list<TValue> coll) {
        return AppendAll(coll.begin(), coll.end());
    }

    TValue& TValue::Push() {
        return CoreMutable().Push();
    }

    TValue TValue::Pop() {
        return CoreMutable().Pop();
    }

    TValue::EType TValue::GetType() const {
        return Core().ValueType;
    }

    bool TValue::IsNull() const {
        return Core().IsNull();
    }

    bool TValue::IsNumber() const {
        return Core().IsNumber();
    }

    bool TValue::IsIntNumber() const {
        return Core().IsIntNumber();
    }

    bool TValue::IsBool() const {
        return Core().IsBool();
    }

    bool TValue::IsString() const {
        return Core().IsString();
    }

    bool TValue::IsArray() const {
        return Core().IsArray();
    }

    bool TValue::IsDict() const {
        return Core().IsDict();
    }

    TValue& TValue::SetNumber(double i) {
        CoreMutableForSet().SetNumber(i);
        return *this;
    }

    TValue& TValue::SetIntNumber(i64 n) {
        CoreMutableForSet().SetIntNumber(n);
        return *this;
    }

    TValue& TValue::SetBool(bool val) {
        CoreMutableForSet().SetBool(val);
        return *this;
    }

    TValue& TValue::SetString(TStringBuf s) {
        CoreMutableForSet().SetString(s);
        return *this;
    }

    double TValue::GetNumber(double d) const {
        return Core().GetNumber(d);
    }

    i64 TValue::GetIntNumber(i64 n) const {
        return Core().GetIntNumber(n);
    }

    bool TValue::GetBool(bool b) const {
        return Core().GetBool(b);
    }

    double& TValue::GetNumberMutable(double defaultval) {
        return CoreMutable().GetNumberMutable(defaultval);
    }

    i64& TValue::GetIntNumberMutable(i64 defaultval) {
        return CoreMutable().GetIntNumberMutable(defaultval);
    }

    TStringBuf TValue::GetString(TStringBuf d) const {
        return Core().GetString(d);
    }

    TValue& TValue::SetArray() {
        CoreMutable().SetArray();
        return *this;
    }

    TValue& TValue::ClearArray() {
        CoreMutable().ClearArray();
        return *this;
    }

    TValue& TValue::SetDict() {
        CoreMutable().SetDict();
        return *this;
    }

    TValue& TValue::ClearDict() {
        CoreMutable().ClearDict();
        return *this;
    }

    const TArray& TValue::GetArray() const {
        return Core().GetArray();
    }

    TArray& TValue::GetArrayMutable() {
        return CoreMutable().GetArrayMutable();
    }

    const TDict& TValue::GetDict() const {
        return Core().GetDict();
    }

    TDict& TValue::GetDictMutable() {
        return CoreMutable().GetDictMutable();
    }

    size_t TValue::StringSize() const {
        return GetString().size();
    }

    size_t TValue::ArraySize() const {
        return GetArray().size();
    }

    size_t TValue::DictSize() const {
        return GetDict().size();
    }

    bool TValue::StringEmpty() const {
        return GetString().empty();
    }

    bool TValue::ArrayEmpty() const {
        return GetArray().empty();
    }

    bool TValue::DictEmpty() const {
        return GetDict().empty();
    }

    TValue::TValue(TPoolPtr& p)
        : TheCore(NewCore(p))
    {
    }

    TValue::TScCore& TValue::CoreMutable() {
        if (Y_UNLIKELY(!TheCore)) {
            *this = TValue();
        } else if (Y_UNLIKELY(CopyOnWrite) && Y_UNLIKELY(TheCore->RefCount() > 1)) {
            *this = Clone();
        }

        CopyOnWrite = false;

        return *TheCore;
    }

    TValue::TScCore& TValue::CoreMutableForSet() {
        if (Y_UNLIKELY(!TheCore) || Y_UNLIKELY(CopyOnWrite) && Y_UNLIKELY(TheCore->RefCount() > 1)) {
            *this = TValue();
        }

        CopyOnWrite = false;

        return *TheCore;
    }

    const TValue::TScCore& TValue::Core() const {
        return TheCore ? *TheCore : DefaultCore();
    }

    TValue& TValue::SetNull() {
        CoreMutableForSet().SetNull();
        return *this;
    }

    namespace NPrivate {
        int CompareStr(const NSc::TValue& a, TStringBuf b);

        int CompareInt(const NSc::TValue& a, i64 r);

        int CompareFloat(const NSc::TValue& a, double r);
    }

    bool operator==(const TValue& a, const TValue& b);

    bool operator!=(const TValue& a, const TValue& b);

    bool operator<=(const TValue&, const TValue&) = delete;
    bool operator>=(const TValue&, const TValue&) = delete;
    bool operator<(const TValue&, const TValue&) = delete;
    bool operator>(const TValue&, const TValue&) = delete;

#define LIBRARY_SCHEME_DECLARE_TVALUE_OPS(T, Impl) \
    bool operator==(const NSc::TValue& a, T b);    \
    bool operator==(T b, const NSc::TValue& a);    \
    bool operator!=(const NSc::TValue& a, T b);    \
    bool operator!=(T b, const NSc::TValue& a);    \
    bool operator<=(const NSc::TValue& a, T b);    \
    bool operator<=(T b, const NSc::TValue& a);    \
    bool operator>=(const NSc::TValue& a, T b);    \
    bool operator>=(T b, const NSc::TValue& a);    \
    bool operator<(const NSc::TValue& a, T b);     \
    bool operator<(T b, const NSc::TValue& a);     \
    bool operator>(const NSc::TValue& a, T b);     \
    bool operator>(T b, const NSc::TValue& a);

#define LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS(T)            \
    LIBRARY_SCHEME_DECLARE_TVALUE_OPS(signed T, CompareInt) \
    LIBRARY_SCHEME_DECLARE_TVALUE_OPS(unsigned T, CompareInt)

    //LIBRARY_SCHEME_DECLARE_TVALUE_OPS(bool, CompareInt)
    LIBRARY_SCHEME_DECLARE_TVALUE_OPS(char, CompareInt)
    LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS(char)
    LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS(short)
    LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS(int)
    LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS(long)
    LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS(long long)

    LIBRARY_SCHEME_DECLARE_TVALUE_OPS(float, CompareFloat)
    LIBRARY_SCHEME_DECLARE_TVALUE_OPS(double, CompareFloat)

    LIBRARY_SCHEME_DECLARE_TVALUE_OPS(TStringBuf, CompareStr)
    LIBRARY_SCHEME_DECLARE_TVALUE_OPS(const TString&, CompareStr)
    LIBRARY_SCHEME_DECLARE_TVALUE_OPS(const char* const, CompareStr)

#undef LIBRARY_SCHEME_DECLARE_TVALUE_OPS
#undef LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS

}