#pragma once

#include "scimpl_defs.h"

#include "fwd.h"

#include <util/generic/maybe.h>

#include <iterator>
#include <utility>

namespace NSc {
#ifdef _MSC_VER
#pragma warning(disable : 4521 4522)
#endif

    struct TMergeOptions {
        enum class EArrayMergeMode {
            Replace,
            Merge
        };
        EArrayMergeMode ArrayMergeMode = EArrayMergeMode::Replace;
    };

    // todo: try to remove some rarely used methods
    class TValue {
    public:
        enum class EType {
            Null = 0 /* "Null" */,
            Bool /* "Bool" */,
            IntNumber /* "Int" */,
            FloatNumber /* "Float" */,
            String /* "String" */,
            Array /* "Array" */,
            Dict /* "Dict" */
        };

        struct TScCore;
        using TCorePtr = TIntrusivePtr<TScCore>;
        using TPoolPtr = TIntrusivePtr<NDefinitions::TPool>;

        using TArray = ::NSc::TArray;
        using TDict = ::NSc::TDict;

    private:                      // A TValue instance has only these 3 fields
        mutable TCorePtr TheCore; // a pointer to a refcounted (kind of) variant
        bool CopyOnWrite = false; // a flag that thevalue is a COW shallow copy and should produce a deep copy once modified

        // Thus all copies of a TValue are by default shallow. Use TValue::Clone to force a deep copy.
        // A COW copy will see changes in its parent, but no change in the COW copy will propagate to its parent.

    public:
        inline TValue();
        inline TValue(TValue& v);
        inline TValue(const TValue& v);
        inline TValue(TValue&& v) noexcept;

    public: // Operators
        inline TValue(double t);
        inline TValue(unsigned long long t);
        inline TValue(unsigned long t);
        inline TValue(unsigned t);
        inline TValue(long long t);
        inline TValue(long t);
        inline TValue(int t);
        //    inline TValue(bool b);

        inline TValue(TStringBuf t);
        inline TValue(const char*);

        inline operator double() const;
        inline operator float() const;
        inline operator long long() const;
        inline operator long() const;
        inline operator int() const;
        inline operator short() const;
        inline operator char() const;
        inline operator unsigned long long() const;
        inline operator unsigned long() const;
        inline operator unsigned() const;
        inline operator unsigned short() const;
        inline operator unsigned char() const;
        inline operator signed char() const;

        inline operator TStringBuf() const;

        inline operator const ::NSc::TArray&() const;
        inline operator const ::NSc::TDict&() const;

        inline TValue& operator=(double t);

        inline TValue& operator=(unsigned long long t);
        inline TValue& operator=(unsigned long t);
        inline TValue& operator=(unsigned t);

        inline TValue& operator=(long long t);
        inline TValue& operator=(long t);
        inline TValue& operator=(int t);
        //    inline TValue& operator=(bool t);

        inline TValue& operator=(TStringBuf t);
        inline TValue& operator=(const char* t);

        inline TValue& operator=(TValue& v) &;
        inline TValue& operator=(const TValue& v) &;
        inline TValue& operator=(TValue&& v) & noexcept;

        inline TValue& operator=(TValue& v) && = delete;
        inline TValue& operator=(const TValue& v) && = delete;
        inline TValue& operator=(TValue&& v) && = delete;

    public:
        template <class T> // ui16 or TStringBuf
        inline TValue& operator[](const T& idx) {
            return GetOrAdd(idx);
        }

        template <class T> // ui16 or TStringBuf
        inline const TValue& operator[](const T& idx) const {
            return Get(idx);
        }

    public: // Data methods ///////////////////////////////////////////////////////////
        inline EType GetType() const;

        inline bool IsNull() const;

        inline TValue& SetNull(); // returns self, will set type to Null

        TValue& Clear() {
            return ClearArray().ClearDict().SetNull();
        }

    public: // Number methods /////////////////////////////////////////////////////////
            // Bool, IntNumber and FloatNumber are all compatible.
            // If a TValue node has one of the types it may as well be used as another.
        // FloatNumber methods. Forces FloatNumber representation. Compatible with IntNumber and Bool

        inline bool IsNumber() const; // true if any of FloatNumber, IntNumber, Bool

        inline double GetNumber(double defaultval = 0) const; // Compatible with Bool, IntNumber and FloatNumber types

        inline double& GetNumberMutable(double defaultval = 0); // Will switch the type to FloatNumber

        inline TValue& SetNumber(double val = 0); // returns self, will switch the type to FloatNumber

        double ForceNumber(double deflt = 0) const; // Best-effort cast to double (will do TryFromString if applicable)

        // IntNumber methods. Forces integer representation. Compatible with FloatNumber and Bool types.
        // Note: if you don't care about distinguishing bools, ints and doubles, use *Number methods above

        inline bool IsIntNumber() const; // true only if IntNumber or Bool

        inline i64 GetIntNumber(i64 defaultval = 0) const; // Compatible with Bool, IntNumber and FloatNumber types

        inline i64& GetIntNumberMutable(i64 defaultval = 0); // Will switch the type to IntNumber

        inline TValue& SetIntNumber(i64 val = 0); // returns self, will switch the type to IntNumber

        i64 ForceIntNumber(i64 deflt = 0) const; // Best-effort cast to i64 (will do TryFromString for String)

        // Bool methods. Forces bool representation. Compatible with Float Number and Int Number methods above.
        // Note: if you don't care about distinguishing Bool, IntNumber and FloatNumber, use *Number methods above

        inline bool IsBool() const; // true only if Bool

        inline bool GetBool(bool defaultval = false) const; // Compatible with Bool, IntNumber and FloatNumber types

        inline TValue& SetBool(bool val = false); // returns self, will switch the type to Bool

    public: // Arcadia-specific boolean representation support
        // Tests for explicit True, also checks for arcadia-specific boolean representation
        bool IsTrue() const {
            return IsNumber() ? GetNumber() : ::IsTrue(GetString());
        }

        // Tests for explicit False, also checks for arcadia-specific boolean representation
        bool IsExplicitFalse() const {
            return IsNumber() ? !GetNumber() : IsFalse(GetString());
        }

    public: // String methods /////////////////////////////////////////////////////////
        inline bool IsString() const;

        inline TStringBuf GetString(TStringBuf defaultval = TStringBuf()) const;

        inline TValue& SetString(TStringBuf val = TStringBuf()); // returns self

        TString ForceString(const TString& deflt = TString()) const; // Best-effort cast to TString (will do ToString for numeric types)

        // todo: remove
        inline bool StringEmpty() const;
        inline size_t StringSize() const;

    public: // Array methods //////////////////////////////////////////////////////////
        inline bool IsArray() const;

        inline const TArray& GetArray() const;
        inline TArray& GetArrayMutable();
        inline TValue& SetArray(); // turns into array if needed, returns self
        inline TValue& ClearArray();

        inline bool Has(size_t idx) const;

        inline const TValue& Get(size_t idx) const; // returns child or default
        inline TValue* GetNoAdd(size_t idx); // returns link to existing child or nullptr

        inline TValue& Push(); // returns new child

        template <class T>
        TValue& Push(T&& t) {
            return Push() = std::forward<T>(t);
        } // returns new child

        TValue& Insert(ui16 idx) {
            return InsertUnsafe(idx);
        } // creates missing values, returns new child

        template <class T>
        TValue& Insert(ui16 idx, T&& v) {
            return InsertUnsafe(idx, std::forward<T>(v));
        } // creates missing values, returns new child

        template <class TIt>
        inline TValue& AppendAll(TIt begin, TIt end); // Append(vec.begin(), vec.end())

        template <class TColl>
        inline TValue& AppendAll(TColl&& coll); // Append(vec)

        inline TValue& AppendAll(std::initializer_list<TValue> coll);

        TValue& GetOrAdd(ui16 idx) {
            return GetOrAddUnsafe(idx);
        } // creates missing values, returns new child

        inline TValue& InsertUnsafe(size_t idx); // creates missing values, returns new child

        template <class T>
        TValue& InsertUnsafe(size_t idx, T&& t) {
            return InsertUnsafe(idx) = std::forward<T>(t);
        } // creates missing values, returns new child

        inline TValue& GetOrAddUnsafe(size_t idx); // creates missing values, returns new child

        inline TValue Pop();              // returns popped value
        inline TValue Delete(size_t idx); // returns deleted value if it existed, NSc::Null() otherwise

        inline TValue& Front() {
            return GetOrAdd(0);
        } // creates missing value, returns child
        inline const TValue& Front() const {
            return Get(0);
        } // returns child or default

        inline TValue& Back();             // creates missing value, returns child
        inline const TValue& Back() const; // returns child or default

        // todo: remove
        inline bool ArrayEmpty() const;
        inline size_t ArraySize() const;

    public: // Dict methods
        inline bool IsDict() const;

        inline const TDict& GetDict() const;
        inline TDict& GetDictMutable();
        inline TValue& SetDict(); // turns into dict if not one, returns self
        inline TValue& ClearDict();

        inline bool Has(TStringBuf idx) const;

        inline const TValue& Get(TStringBuf idx) const;
        inline TValue* GetNoAdd(TStringBuf idx); // returns link to existing child or nullptr

        TValue& Add(TStringBuf idx) {
            return GetOrAdd(idx);
        }

        template <class T>
        TValue& Add(TStringBuf idx, T&& t) {
            return Add(idx) = std::forward<T>(t);
        }

        inline TValue& GetOrAdd(TStringBuf idx); // creates missing value, returns child

        inline TValue Delete(TStringBuf idx); // returns deleted value

        inline TValue& AddAll(std::initializer_list<std::pair<TStringBuf, TValue>> t);

        TStringBufs DictKeys(bool sorted = true) const;
        TStringBufs& DictKeys(TStringBufs&, bool sorted = true) const;

        // todo: remove
        inline bool DictEmpty() const;
        inline size_t DictSize() const;

    public: // Json methods ////////////////////////////////////////////////
        using TJsonOpts = NSc::TJsonOpts;
        using EJsonOpts = TJsonOpts::EJsonOpts;
        static const EJsonOpts JO_DEFAULT = TJsonOpts::JO_DEFAULT;
        static const EJsonOpts JO_SORT_KEYS = TJsonOpts::JO_SORT_KEYS;
        static const EJsonOpts JO_SKIP_UNSAFE = TJsonOpts::JO_SKIP_UNSAFE; // skip non-utf8 strings
        static const EJsonOpts JO_PRETTY = TJsonOpts::JO_PRETTY;
        static const EJsonOpts JO_SAFE = TJsonOpts::JO_SAFE;                                               // JO_SORT_KEYS | JO_SKIP_UNSAFE
        static const EJsonOpts JO_PARSER_STRICT_WITH_COMMENTS = TJsonOpts::JO_PARSER_STRICT_WITH_COMMENTS; // strict json + strict utf8
        static const EJsonOpts JO_PARSER_STRICT = TJsonOpts::JO_PARSER_STRICT;                             // strict json + strict utf8 + comments are disallowed
        static const EJsonOpts JO_PARSER_DISALLOW_DUPLICATE_KEYS = TJsonOpts::JO_PARSER_DISALLOW_DUPLICATE_KEYS;

        [[nodiscard]] static TValue FromJson(TStringBuf, const TJsonOpts& = TJsonOpts());
        [[nodiscard]] static TValue FromJsonThrow(TStringBuf, const TJsonOpts& = TJsonOpts());
        static bool FromJson(TValue&, TStringBuf, const TJsonOpts& = TJsonOpts());

        // TODO: Переименовать ToJson в ToJsonUnsafe, а ToJsonSafe в ToJson
        TString ToJson(const TJsonOpts& = TJsonOpts()) const;
        const TValue& ToJson(IOutputStream&, const TJsonOpts& = TJsonOpts()) const; // returns self

        // ToJson(JO_SORT_KEYS | JO_SKIP_UNSAFE)
        TString ToJsonSafe(const TJsonOpts& = TJsonOpts()) const;
        const TValue& ToJsonSafe(IOutputStream&, const TJsonOpts& = TJsonOpts()) const;

        // ToJson(JO_SORT_KEYS | JO_PRETTY | JO_SKIP_UNSAFE)
        TString ToJsonPretty(const TJsonOpts& = TJsonOpts()) const;
        const TValue& ToJsonPretty(IOutputStream&, const TJsonOpts& = TJsonOpts()) const;

        NJson::TJsonValue ToJsonValue() const;

        static TValue FromJsonValue(const NJson::TJsonValue&);
        static TValue& FromJsonValue(TValue&, const NJson::TJsonValue&); // returns self

        static TJsonOpts MakeOptsSafeForSerializer(TJsonOpts = TJsonOpts());
        static TJsonOpts MakeOptsPrettyForSerializer(TJsonOpts = TJsonOpts());

    public: // Merge methods ////////////////////////////////////////////////
        /*
     * LHS.MergeUpdate(RHS):
     *     1. Dict <- Dict:
     *            - Copy all nonconflicting key-value pairs from RHS to LHS.
     *            - For every pair of conflicting values apply LHS[key].MergeUpdate(RHS[key]).
     *     2. Anything <- Null:
     *            - Do nothing.
     *     3. Other conflicts:
     *            - Copy RHS over LHS.
     *
     * LHS.ReverseMerge(RHS):
     *     1. Dict <- Dict:
     *            - Copy all nonconflicting key-value pairs from RHS to LHS.
     *            - For every pair of conflicting values apply LHS[key].ReverseMerge(RHS[key]).
     *     2. Null <- Anything:
     *            - Copy RHS over LHS.
     *     3. Other conflicts:
     *            - Do nothing.
     */

        TValue& MergeUpdateJson(TStringBuf json, TMaybe<TMergeOptions> mergeOptions = {});  // returns self
        TValue& ReverseMergeJson(TStringBuf json, TMaybe<TMergeOptions> mergeOptions = {}); // returns self

        static bool MergeUpdateJson(TValue&, TStringBuf json, TMaybe<TMergeOptions> mergeOptions = {});  // returns true unless failed to parse the json
        static bool ReverseMergeJson(TValue&, TStringBuf json, TMaybe<TMergeOptions> mergeOptions = {}); // returns true unless failed to parse the json

        TValue& MergeUpdate(const TValue& delta, TMaybe<TMergeOptions> mergeOptions = {});  // return self
        TValue& ReverseMerge(const TValue& delta, TMaybe<TMergeOptions> mergeOptions = {}); // return self

    public: // Path methods /////////////////////////////////////////////////////////
        // TODO: add throwing variants
        // make sure to properly escape the tokens

        static TString EscapeForPath(TStringBuf rawKey); // converts a raw dict key into a valid token for a selector path

        static bool PathValid(TStringBuf path); // returns true if the path is syntactically valid

        bool PathExists(TStringBuf path) const; // returns true if the path is syntactically valid and the target value exists

        const TValue& TrySelect(TStringBuf path) const; // returns the target value
                                                        // if the path is syntactically valid and the target value exists
                                                        // otherwise returns NSc::Null()

        TValue* TrySelectOrAdd(TStringBuf path); // returns the target value if it exists or creates if not
                                                // if the path is syntactically valid
                                                // otherwise returns NSc::Null()

        TValue TrySelectAndDelete(TStringBuf path); // deletes and returns the target value
                                                    // if the path is syntactically valid and the target value existed
                                                    // otherwise returns NSc::Null()

    public:                                    // Copy methods /////////////////////////////////////////////////////////
        TValue Clone() const;                  // returns deep copy of self (on the separate pool)
        TValue& CopyFrom(const TValue& other); // deep copy other value into self, returns self

        TValue& Swap(TValue& v);

        static bool Same(const TValue&, const TValue&);     // point to the same core
        static bool Equal(const TValue&, const TValue&);    // recursively equal
        static bool SamePool(const TValue&, const TValue&); // share arena

    public:
        // very specific methods useful in very specific corner cases

        static TValue From(const ::google::protobuf::Message&, bool mapAsDict = false);

        void To(::google::protobuf::Message&, const TProtoOpts& opts = {}) const;

    public:
        inline explicit TValue(TPoolPtr&);

        static const TScCore& DefaultCore();
        static const TArray& DefaultArray();
        static const TDict& DefaultDict();
        static const TValue& DefaultValue();
        static const TValue& Null() {
            return DefaultValue();
        }

        void DoWriteJsonImpl(IOutputStream&, const TJsonOpts&, NImpl::TKeySortContext&, NImpl::TSelfLoopContext&) const;

        bool IsSameOrAncestorOf(const TValue& other) const;

    private:
        TValue& DoMerge(const TValue& delta, bool olddelta, TMaybe<TMergeOptions> mergeOptions);
        TValue& DoMergeImpl(const TValue& delta, bool olddelta, TMaybe<TMergeOptions> mergeOptions, NImpl::TSelfLoopContext&, NImpl::TSelfOverrideContext&);
        TValue& DoCopyFromImpl(const TValue& other, NImpl::TSelfLoopContext&, NImpl::TSelfOverrideContext&);
        NJson::TJsonValue ToJsonValueImpl(NImpl::TSelfLoopContext&) const;

        bool IsSameOrAncestorOfImpl(const TScCore& other, NImpl::TSelfLoopContext& loopCtx) const;

        inline TScCore& CoreMutable();
        inline TScCore& CoreMutableForSet();
        inline const TScCore& Core() const;

        static inline TScCore* NewCore(TPoolPtr&);

        static TValue FromField(const ::google::protobuf::Message&, const ::google::protobuf::FieldDescriptor*);
        static TValue FromRepeatedField(const ::google::protobuf::Message&, const ::google::protobuf::FieldDescriptor*, int index);

        void ValueToField(const TValue& value, ::google::protobuf::Message&, const ::google::protobuf::FieldDescriptor*, const TProtoOpts& opts) const;
        void ToField(::google::protobuf::Message&, const ::google::protobuf::FieldDescriptor*, const TProtoOpts& opts) const;
        void ToEnumField(::google::protobuf::Message&, const ::google::protobuf::FieldDescriptor*, const TProtoOpts& opts) const;
        void ToRepeatedField(::google::protobuf::Message&, const ::google::protobuf::FieldDescriptor*, const TProtoOpts& opts) const;
        void ToMapField(::google::protobuf::Message&, const ::google::protobuf::FieldDescriptor*, const TProtoOpts& opts) const;
    };


    inline const TValue& Null() {
        return TValue::DefaultValue();
    }


    class TArray: public TDeque<TValue, TPoolAllocator>, TNonCopyable {
        using TParent = TDeque<TValue, TPoolAllocator>;

    public:
        TArray(TMemoryPool* p)
            : TParent(p)
        {
        }

        template <class TIt>
        void AppendAll(TIt begin, TIt end) {
            TParent::insert(TParent::end(), begin, end);
        }

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

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

        const TValue& operator[](size_t i) const {
            return EnsureIndex(i);
        }

        TValue& operator[](size_t i) {
            return EnsureIndex(i);
        }

        const TValue& front() const {
            return EnsureIndex(0);
        }

        TValue& front() {
            return EnsureIndex(0);
        }

        const TValue& back() const {
            return EnsureIndex(LastIndex());
        }

        TValue& back() {
            return EnsureIndex(LastIndex());
        }

        void pop_back() {
            if (empty())
                return;
            TParent::pop_back();
        }

        void pop_front() {
            if (empty())
                return;
            TParent::pop_front();
        }

    private:
        size_t LastIndex() const {
            return ::Max<size_t>(size(), 1) - 1;
        }

        TValue& EnsureIndex(size_t i) {
            if (i >= size())
                resize(::Min<size_t>(i + 1, ::Max<ui16>()), TValue::DefaultValue());
            return TParent::operator[](i);
        }

        const TValue& EnsureIndex(size_t i) const {
            return i < size() ? TParent::operator[](i) : TValue::DefaultValue();
        }
    };


    // todo: densehashtable
    // todo: allow insertions
    // todo: make TDict methods safe
    class TDict: public THashMap<TStringBuf, TValue, THash<TStringBuf>, TEqualTo<TStringBuf>, TPoolAllocator>, TNonCopyable {
        using TParent = THashMap<TStringBuf, TValue, THash<TStringBuf>, TEqualTo<TStringBuf>, TPoolAllocator>;

    public:
        TDict(TMemoryPool* p)
            : TParent(p)
        {
        }

        template <class TStr>
        const TValue& Get(const TStr& key) const {
            const_iterator it = find(key);
            return it != end() ? it->second : TValue::DefaultValue();
        }
    };
}

#include "scimpl.h"
#include "scheme_cast.h"

#ifdef _MSC_VER
#pragma warning(default : 4521 4522)
#endif
