#include "scheme.h"
#include "scimpl_private.h"

#include <util/generic/algorithm.h>
#include <util/string/cast.h>

namespace NSc {
    TStringBufs& TValue::DictKeys(TStringBufs& vs, bool sorted) const {
        if (!IsDict()) {
            return vs;
        }

        const ::NSc::TDict& dict = GetDict();
        vs.reserve(vs.size() + dict.size());
        for (const auto& it : dict)
            vs.push_back(it.first);

        if (sorted) {
            Sort(vs.begin(), vs.end());
        }

        return vs;
    }

    TStringBufs TValue::DictKeys(bool sorted) const {
        TStringBufs bufs;
        DictKeys(bufs, sorted);
        return bufs;
    }

    TValue& TValue::MergeUpdate(const TValue& delta, TMaybe<TMergeOptions> mergeOptions) {
        return DoMerge(delta, false, mergeOptions);
    }

    TValue& TValue::ReverseMerge(const TValue& delta, TMaybe<TMergeOptions> mergeOptions) {
        return DoMerge(delta, true, mergeOptions);
    }

    TValue& TValue::MergeUpdateJson(TStringBuf data, TMaybe<TMergeOptions> mergeOptions) {
        return MergeUpdate(FromJson(data), mergeOptions);
    }

    TValue& TValue::ReverseMergeJson(TStringBuf data, TMaybe<TMergeOptions> mergeOptions) {
        return ReverseMerge(FromJson(data), mergeOptions);
    }

    bool TValue::MergeUpdateJson(TValue& v, TStringBuf data, TMaybe<TMergeOptions> mergeOptions) {
        NSc::TValue m;
        if (!FromJson(m, data)) {
            return false;
        }

        v.MergeUpdate(m, mergeOptions);
        return true;
    }

    bool TValue::ReverseMergeJson(TValue& v, TStringBuf data, TMaybe<TMergeOptions> mergeOptions) {
        NSc::TValue m;
        if (!FromJson(m, data)) {
            return false;
        }

        v.ReverseMerge(m, mergeOptions);
        return true;
    }

    TValue TValue::Clone() const {
        return TValue().CopyFrom(*this);
    }

    double TValue::ForceNumber(double deflt) const {
        const TScCore& core = Core();
        if (core.IsNumber()) {
            return core.GetNumber(deflt);
        }

        if (TStringBuf str = core.GetString(TStringBuf())) {
            {
                double result = 0;
                if (TryFromString<double>(str, result)) {
                    return result;
                }
            }
            {
                i64 result = 0;
                if (TryFromString<i64>(str, result)) {
                    return result;
                }
            }
            {
                ui64 result = 0;
                if (TryFromString<ui64>(str, result)) {
                    return result;
                }
            }
        }

        return deflt;
    }

    i64 TValue::ForceIntNumber(i64 deflt) const {
        const TScCore& core = Core();
        if (core.IsNumber()) {
            return core.GetIntNumber(deflt);
        }

        if (TStringBuf str = core.GetString(TStringBuf())) {
            {
                i64 result = 0;
                if (TryFromString<i64>(str, result)) {
                    return result;
                }
            }
            {
                ui64 result = 0;
                if (TryFromString<ui64>(str, result)) {
                    return result;
                }
            }
            {
                double result = 0;
                if (TryFromString<double>(str, result)) {
                    return result;
                }
            }
        }

        return deflt;
    }

    TString TValue::ForceString(const TString& deflt) const {
        const TScCore& core = Core();
        if (core.IsString()) {
            return ToString(core.GetString(TStringBuf()));
        }

        if (core.IsIntNumber()) {
            return ToString(core.GetIntNumber(0));
        }

        if (core.IsNumber()) {
            return ToString(core.GetNumber(0));
        }

        return deflt;
    }

    TValue& /*this*/ TValue::CopyFrom(const TValue& other) {
        if (Same(*this, other)) {
            return *this;
        }

        using namespace NImpl;
        return DoCopyFromImpl(other, GetTlsInstance<TSelfLoopContext>(), GetTlsInstance<TSelfOverrideContext>());
    }

    TValue& TValue::DoCopyFromImpl(const TValue& other,
                                   NImpl::TSelfLoopContext& otherLoopCtx,
                                   NImpl::TSelfOverrideContext& selfOverrideCtx) {
        if (Same(*this, other)) {
            return *this;
        }

        CoreMutableForSet(); // trigger COW

        TScCore& selfCore = *TheCore;
        const TScCore& otherCore = other.Core();

        NImpl::TSelfLoopContext::TGuard loopCheck(otherLoopCtx, otherCore);
        NImpl::TSelfOverrideContext::TGuard overrideGuard(selfOverrideCtx, selfCore);

        selfCore.SetNull();

        if (!loopCheck.Ok) {
            return *this; // a loop encountered (and asserted), skip the back reference
        }

        switch (otherCore.ValueType) {
            default:
                Y_ASSERT(false);
                [[fallthrough]];
            case EType::Null:
                break;
            case EType::Bool:
                selfCore.SetBool(otherCore.IntNumber);
                break;
            case EType::IntNumber:
                selfCore.SetIntNumber(otherCore.IntNumber);
                break;
            case EType::FloatNumber:
                selfCore.SetNumber(otherCore.FloatNumber);
                break;
            case EType::String:
                if (selfCore.Pool.Get() == otherCore.Pool.Get()) {
                    selfCore.SetOwnedString(otherCore.String);
                } else {
                    selfCore.SetString(otherCore.String);
                }
                break;
            case EType::Array:
                selfCore.SetArray();
                for (const TValue& e : otherCore.GetArray()) {
                    selfCore.Push().DoCopyFromImpl(e, otherLoopCtx, selfOverrideCtx);
                }
                break;
            case EType::Dict: {
                TCorePtr tmp = NewCore(selfCore.Pool);
                auto& tmpCore = *tmp;
                tmpCore.SetDict();
                const TDict& d = otherCore.GetDict();
                tmpCore.Dict.reserve(d.size());
                for (const TDict::value_type& e : d) {
                    tmpCore.Add(e.first).DoCopyFromImpl(e.second, otherLoopCtx, selfOverrideCtx);
                }
                TheCore = std::move(tmp);
                break;
            }
        }

        return *this;
    }

    TValue& TValue::Swap(TValue& v) {
        DoSwap(TheCore, v.TheCore);
        DoSwap(CopyOnWrite, v.CopyOnWrite);
        return *this;
    }

    bool TValue::Same(const TValue& a, const TValue& b) {
        return a.TheCore.Get() == b.TheCore.Get();
    }

    bool TValue::SamePool(const TValue& a, const TValue& b) {
        return Same(a, b) || a.TheCore->Pool.Get() == b.TheCore->Pool.Get();
    }

    bool TValue::Equal(const TValue& a, const TValue& b) {
        if (Same(a, b)) {
            return true;
        }

        const NSc::TValue::TScCore& coreA = a.Core();
        const NSc::TValue::TScCore& coreB = b.Core();

        if (coreA.IsNumber() && coreB.IsNumber()) {
            return coreA.GetIntNumber(0) == coreB.GetIntNumber(0) && coreA.GetNumber(0) == coreB.GetNumber(0);
        }

        if (coreA.ValueType != coreB.ValueType) {
            return false;
        }

        if (coreA.IsString()) {
            std::string_view strA = coreA.String;
            std::string_view strB = coreB.String;

            if (strA != strB) {
                return false;
            }
        } else if (coreA.IsArray()) {
            const TArray& arrA = coreA.Array;
            const TArray& arrB = coreB.Array;

            if (arrA.size() != arrB.size()) {
                return false;
            }

            for (size_t i = 0; i < arrA.size(); ++i) {
                if (!Equal(arrA[i], arrB[i])) {
                    return false;
                }
            }
        } else if (coreA.IsDict()) {
            const ::NSc::TDict& dictA = coreA.Dict;
            const ::NSc::TDict& dictB = coreB.Dict;

            if (dictA.size() != dictB.size()) {
                return false;
            }

            for (const auto& ita : dictA) {
                ::NSc::TDict::const_iterator itb = dictB.find(ita.first);

                if (itb == dictB.end() || !Equal(ita.second, itb->second)) {
                    return false;
                }
            }
        }

        return true;
    }

    TValue& TValue::DoMerge(const TValue& delta, bool lowPriorityDelta, TMaybe<TMergeOptions> mergeOptions) {
        if (Same(*this, delta)) {
            return *this;
        }

        using namespace NImpl;
        return DoMergeImpl(delta, lowPriorityDelta, mergeOptions, GetTlsInstance<TSelfLoopContext>(), GetTlsInstance<TSelfOverrideContext>());
    }

    TValue& TValue::DoMergeImpl(const TValue& delta, bool lowPriorityDelta, TMaybe<TMergeOptions> mergeOptions,
                                NImpl::TSelfLoopContext& otherLoopCtx,
                                NImpl::TSelfOverrideContext& selfOverrideGuard) {
        if (Same(*this, delta)) {
            return *this;
        }

        bool allowMergeArray = mergeOptions.Defined() && mergeOptions->ArrayMergeMode == TMergeOptions::EArrayMergeMode::Merge;

        if (delta.IsDict() && (!lowPriorityDelta || IsDict() || IsNull())) {
            TScCore& core = CoreMutable();
            const TScCore& deltaCore = delta.Core();

            NImpl::TSelfLoopContext::TGuard loopCheck(otherLoopCtx, deltaCore);

            if (!loopCheck.Ok) {
                return *this; // a loop encountered (and asserted), skip the back reference
            }

            if (!lowPriorityDelta || IsNull()) {
                SetDict();
            }

            const TDict& ddelta = deltaCore.Dict;

            for (const auto& dit : ddelta) {
                core.GetOrAdd(dit.first).DoMergeImpl(dit.second, lowPriorityDelta, mergeOptions, otherLoopCtx, selfOverrideGuard);
            }
        } else if (delta.IsArray() && allowMergeArray && (!lowPriorityDelta || IsArray() || IsNull())) {
            TScCore& core = CoreMutable();
            const TScCore& deltaCore = delta.Core();

            NImpl::TSelfLoopContext::TGuard loopCheck(otherLoopCtx, deltaCore);

            if (!loopCheck.Ok) {
                return *this; // a loop encountered (and asserted), skip the back reference
            }

            if (!lowPriorityDelta || IsNull()) {
                SetArray();
            }

            Y_ASSERT(IsArray());

            const TArray& adelta = deltaCore.Array;
            if (adelta.size() > core.Array.size()) {
                core.Array.resize(adelta.size());
            }
            for (size_t i = 0; i < adelta.size(); ++i) {
                core.Array[i].DoMergeImpl(adelta[i], lowPriorityDelta, mergeOptions, otherLoopCtx, selfOverrideGuard);
            }
        } else if (!delta.IsNull() && (!lowPriorityDelta || IsNull())) {
            DoCopyFromImpl(delta, otherLoopCtx, selfOverrideGuard);
        }

        return *this;
    }

    NJson::TJsonValue TValue::ToJsonValue() const {
        using namespace NImpl;
        return ToJsonValueImpl(GetTlsInstance<TSelfLoopContext>());
    }

    NJson::TJsonValue TValue::ToJsonValueImpl(NImpl::TSelfLoopContext& loopCtx) const {
        const TScCore& core = Core();

        switch (core.ValueType) {
            default:
            case EType::Null:
                return NJson::TJsonValue(NJson::JSON_NULL);
            case EType::Bool:
                return NJson::TJsonValue(core.GetBool());
            case EType::IntNumber:
                return NJson::TJsonValue(core.GetIntNumber());
            case EType::FloatNumber:
                return NJson::TJsonValue(core.GetNumber());
            case EType::String:
                return NJson::TJsonValue(core.String);
            case EType::Array: {
                NImpl::TSelfLoopContext::TGuard loopGuard(loopCtx, core);

                if (!loopGuard.Ok) {
                    return NJson::TJsonValue(NJson::JSON_NULL);
                }

                NJson::TJsonValue result(NJson::JSON_ARRAY);
                const TArray& arr = core.Array;

                for (const auto& item : arr) {
                    result.AppendValue(NJson::TJsonValue::UNDEFINED) = item.ToJsonValueImpl(loopCtx);
                }

                return result;
            }
            case EType::Dict: {
                NImpl::TSelfLoopContext::TGuard loopGuard(loopCtx, core);

                if (!loopGuard.Ok) {
                    return NJson::TJsonValue(NJson::JSON_NULL);
                }

                NJson::TJsonValue result(NJson::JSON_MAP);
                const TDict& dict = core.Dict;

                for (const auto& item : dict) {
                    result.InsertValue(item.first, NJson::TJsonValue::UNDEFINED) = item.second.ToJsonValueImpl(loopCtx);
                }

                return result;
            }
        }
    }

    TValue TValue::FromJsonValue(const NJson::TJsonValue& val) {
        TValue result;
        FromJsonValue(result, val);
        return result;
    }

    TValue& TValue::FromJsonValue(TValue& res, const NJson::TJsonValue& val) {
        TScCore& core = res.CoreMutableForSet();
        core.SetNull();

        switch (val.GetType()) {
            default:
            case NJson::JSON_UNDEFINED:
            case NJson::JSON_NULL:
                break;
            case NJson::JSON_BOOLEAN:
                core.SetBool(val.GetBoolean());
                break;
            case NJson::JSON_INTEGER:
                core.SetIntNumber(val.GetInteger());
                break;
            case NJson::JSON_UINTEGER:
                core.SetIntNumber(val.GetUInteger());
                break;
            case NJson::JSON_DOUBLE:
                core.SetNumber(val.GetDouble());
                break;
            case NJson::JSON_STRING:
                core.SetString(val.GetString());
                break;
            case NJson::JSON_ARRAY: {
                core.SetArray();
                for (const auto& item : val.GetArray()) {
                    FromJsonValue(core.Push(), item);
                }
                break;
            }
            case NJson::JSON_MAP: {
                core.SetDict();
                for (const auto& item : val.GetMap()) {
                    FromJsonValue(core.Add(item.first), item.second);
                }
                break;
            }
        }

        return res;
    }

    struct TDefaults {
        TValue::TPoolPtr Pool = MakeIntrusive<NDefinitions::TPool>();
        TValue::TScCore Core{Pool};
    };

    const TValue::TScCore& TValue::DefaultCore() {
        return Default<TDefaults>().Core;
    }

    const TArray& TValue::DefaultArray() {
        return Default<TDefaults>().Core.Array;
    }

    const TDict& TValue::DefaultDict() {
        return Default<TDefaults>().Core.Dict;
    }

    const TValue& TValue::DefaultValue() {
        return *FastTlsSingleton<TValue>();
    }

    bool TValue::IsSameOrAncestorOf(const TValue& other) const {
        using namespace NImpl;
        return IsSameOrAncestorOfImpl(other.Core(), GetTlsInstance<TSelfLoopContext>());
    }

    bool TValue::IsSameOrAncestorOfImpl(const TScCore& other, NImpl::TSelfLoopContext& loopCtx) const {
        const TScCore& core = Core();

        if (&core == &other) {
            return true;
        }

        switch (core.ValueType) {
        default:
            return false;
        case EType::Array: {
            NImpl::TSelfLoopContext::TGuard loopGuard(loopCtx, core);

            if (!loopGuard.Ok) {
                return false;
            }

            for (const auto& item : core.Array) {
                if (item.IsSameOrAncestorOfImpl(other, loopCtx)) {
                    return true;
                }
            }

            return false;
        }
        case EType::Dict: {
            NImpl::TSelfLoopContext::TGuard loopGuard(loopCtx, core);

            if (!loopGuard.Ok) {
                return false;
            }

            for (const auto& item : core.Dict) {
                if (item.second.IsSameOrAncestorOfImpl(other, loopCtx)) {
                    return true;
                }
            }

            return false;
        }
        }
    }

    namespace NPrivate {
        int CompareStr(const NSc::TValue& a, TStringBuf b) {
            return a.GetString().compare(b);
        }

        int CompareInt(const NSc::TValue& a, i64 r) {
            i64 l = a.GetIntNumber();
            return l < r ? -1 : l > r ? 1 : 0;
        }

        int CompareFloat(const NSc::TValue& a, double r) {
            double l = a.GetNumber();
            return l < r ? -1 : l > r ? 1 : 0;
        }

    }

    bool operator==(const NSc::TValue& a, const NSc::TValue& b) {
        return NSc::TValue::Equal(a, b);
    }

    bool operator!=(const NSc::TValue& a, const NSc::TValue& b) {
        return !NSc::TValue::Equal(a, b);
    }

#define LIBRARY_SCHEME_DECLARE_TVALUE_OPS_IMPL(T, Impl) \
    bool operator==(const NSc::TValue& a, T b) {        \
        return NPrivate::Impl(a, b) == 0;               \
    }                                                   \
    bool operator==(T b, const NSc::TValue& a) {        \
        return NPrivate::Impl(a, b) == 0;               \
    }                                                   \
    bool operator!=(const NSc::TValue& a, T b) {        \
        return NPrivate::Impl(a, b) != 0;               \
    }                                                   \
    bool operator!=(T b, const NSc::TValue& a) {        \
        return NPrivate::Impl(a, b) != 0;               \
    }                                                   \
    bool operator<=(const NSc::TValue& a, T b) {        \
        return NPrivate::Impl(a, b) <= 0;               \
    }                                                   \
    bool operator<=(T b, const NSc::TValue& a) {        \
        return NPrivate::Impl(a, b) >= 0;               \
    }                                                   \
    bool operator>=(const NSc::TValue& a, T b) {        \
        return NPrivate::Impl(a, b) >= 0;               \
    }                                                   \
    bool operator>=(T b, const NSc::TValue& a) {        \
        return NPrivate::Impl(a, b) <= 0;               \
    }                                                   \
    bool operator<(const NSc::TValue& a, T b) {         \
        return NPrivate::Impl(a, b) < 0;                \
    }                                                   \
    bool operator<(T b, const NSc::TValue& a) {         \
        return NPrivate::Impl(a, b) > 0;                \
    }                                                   \
    bool operator>(const NSc::TValue& a, T b) {         \
        return NPrivate::Impl(a, b) > 0;                \
    }                                                   \
    bool operator>(T b, const NSc::TValue& a) {         \
        return NPrivate::Impl(a, b) < 0;                \
    }

#define LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS_IMPL(T)            \
    LIBRARY_SCHEME_DECLARE_TVALUE_OPS_IMPL(signed T, CompareInt) \
    LIBRARY_SCHEME_DECLARE_TVALUE_OPS_IMPL(unsigned T, CompareInt)

    //LIBRARY_SCHEME_DECLARE_TVALUE_OPS_IMPL(bool, CompareInt)
    LIBRARY_SCHEME_DECLARE_TVALUE_OPS_IMPL(char, CompareInt)
    LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS_IMPL(char)
    LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS_IMPL(short)
    LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS_IMPL(int)
    LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS_IMPL(long)
    LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS_IMPL(long long)

    LIBRARY_SCHEME_DECLARE_TVALUE_OPS_IMPL(float, CompareFloat)
    LIBRARY_SCHEME_DECLARE_TVALUE_OPS_IMPL(double, CompareFloat)

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

#undef LIBRARY_SCHEME_DECLARE_TVALUE_OPS_IMPL
#undef LIBRARY_SCHEME_DECLARE_TVALUE_INT_OPS_IMPL

}

template <>
void Out<NSc::TValue>(IOutputStream& o, TTypeTraits<NSc::TValue>::TFuncParam v) {
    o.Write(v.ToJson(true));
}
