#include "json_value.h"

#include <library/cpp/unittest/registar.h>

#include <util/stream/input.h>

using namespace NJson;

Y_UNIT_TEST_SUITE(TJsonValueTest) {
    Y_UNIT_TEST(UndefTest) {
        TJsonValue undef;
        TJsonValue null(JSON_NULL);
        TJsonValue _false(false);
        TJsonValue zeroInt(0);
        TJsonValue zeroDouble(0.0);
        TJsonValue emptyStr("");
        TJsonValue emptyArray(JSON_ARRAY);
        TJsonValue emptyMap(JSON_MAP);

        UNIT_ASSERT(!undef.IsDefined());
        UNIT_ASSERT(!null.IsDefined()); // json NULL is undefined too!
        UNIT_ASSERT(_false.IsDefined());
        UNIT_ASSERT(zeroInt.IsDefined());
        UNIT_ASSERT(zeroDouble.IsDefined());
        UNIT_ASSERT(emptyStr.IsDefined());
        UNIT_ASSERT(emptyArray.IsDefined());
        UNIT_ASSERT(emptyMap.IsDefined());

        UNIT_ASSERT(undef == TJsonValue());
        UNIT_ASSERT(undef != null);
        UNIT_ASSERT(undef != _false);
        UNIT_ASSERT(undef != zeroInt);
        UNIT_ASSERT(undef != zeroDouble);
        UNIT_ASSERT(undef != emptyStr);
        UNIT_ASSERT(undef != emptyArray);
        UNIT_ASSERT(undef != emptyMap);
    }

    Y_UNIT_TEST(DefaultCompareTest) {
        {
            TJsonValue lhs;
            TJsonValue rhs;
            UNIT_ASSERT(lhs == rhs);
            UNIT_ASSERT(rhs == lhs);
        }

        {
            TJsonValue lhs;
            TJsonValue rhs(JSON_NULL);
            UNIT_ASSERT(lhs != rhs);
            UNIT_ASSERT(rhs != lhs);
        }
    }

    Y_UNIT_TEST(NullCompareTest) {
        TJsonValue lhs(JSON_NULL);
        TJsonValue rhs(JSON_NULL);
        UNIT_ASSERT(lhs == rhs);
        UNIT_ASSERT(rhs == lhs);
    }

    Y_UNIT_TEST(StringCompareTest) {
        {
            TJsonValue lhs(JSON_STRING);
            TJsonValue rhs(JSON_STRING);
            UNIT_ASSERT(lhs == rhs);
            UNIT_ASSERT(rhs == lhs);
        }

        {
            TJsonValue lhs("");
            TJsonValue rhs("");
            UNIT_ASSERT(lhs == rhs);
            UNIT_ASSERT(rhs == lhs);
        }

        {
            TJsonValue lhs("abc");
            TJsonValue rhs("abc");
            UNIT_ASSERT(lhs == rhs);
            UNIT_ASSERT(rhs == lhs);
        }

        {
            TJsonValue lhs("1");
            TJsonValue rhs(1);
            UNIT_ASSERT(lhs != rhs);
            UNIT_ASSERT(rhs != lhs);
        }
    }

    Y_UNIT_TEST(ArrayCompareTest) {
        {
            TJsonValue lhs(JSON_ARRAY);
            TJsonValue rhs(JSON_ARRAY);
            UNIT_ASSERT(lhs == rhs);
            UNIT_ASSERT(rhs == lhs);
        }

        {
            TJsonValue lhs;
            TJsonValue rhs;

            lhs.AppendValue(TJsonValue());

            UNIT_ASSERT(lhs != rhs);
            UNIT_ASSERT(rhs != lhs);
        }

        {
            TJsonValue lhs;
            TJsonValue rhs;

            lhs.AppendValue(1);
            lhs.AppendValue("2");
            lhs.AppendValue(3.0);
            lhs.AppendValue(TJsonValue());
            lhs.AppendValue(TJsonValue(JSON_NULL));

            rhs.AppendValue(1);
            rhs.AppendValue("2");
            rhs.AppendValue(3.0);
            rhs.AppendValue(TJsonValue());
            rhs.AppendValue(TJsonValue(JSON_NULL));

            UNIT_ASSERT(lhs == rhs);
            UNIT_ASSERT(rhs == lhs);
        }

        {
            TJsonValue lhs;
            TJsonValue rhs;

            lhs.AppendValue(1);
            rhs.AppendValue("1");

            UNIT_ASSERT(lhs != rhs);
            UNIT_ASSERT(rhs != lhs);
        }
    }

    Y_UNIT_TEST(CompareTest) {
        {
            TJsonValue lhs;
            lhs.InsertValue("null value", TJsonValue(JSON_NULL));
            lhs.InsertValue("int key", TJsonValue(10));
            lhs.InsertValue("double key", TJsonValue(11.11));
            lhs.InsertValue("string key", TJsonValue("string"));

            TJsonValue array;
            array.AppendValue(1);
            array.AppendValue(2);
            array.AppendValue(3);
            array.AppendValue("string");
            lhs.InsertValue("array", array);

            lhs.InsertValue("bool key", TJsonValue(true));

            TJsonValue rhs;
            rhs = lhs;

            UNIT_ASSERT(lhs == rhs);
            UNIT_ASSERT(rhs == lhs);
        }

        {
            // Insert keys in different orders
            const int NUM_KEYS = 1000;

            TJsonValue lhs;
            for (int i = 0; i < NUM_KEYS; ++i)
                lhs.InsertValue(ToString(i), i);

            TJsonValue rhs;
            for (int i = 0; i < NUM_KEYS; i += 2)
                rhs.InsertValue(ToString(i), i);
            for (int i = 1; i < NUM_KEYS; i += 2)
                rhs.InsertValue(ToString(i), i);

            UNIT_ASSERT(lhs == rhs);
            UNIT_ASSERT(rhs == lhs);
        }

        {
            TJsonValue lhs;
            lhs.InsertValue("null value", TJsonValue(JSON_NULL));
            lhs.InsertValue("int key", TJsonValue(10));
            lhs.InsertValue("double key", TJsonValue(11.11));
            lhs.InsertValue("string key", TJsonValue("string"));

            TJsonValue array;
            array.AppendValue(1);
            array.AppendValue(2);
            array.AppendValue(3);
            array.AppendValue("string");
            lhs.InsertValue("array", array);

            lhs.InsertValue("bool key", TJsonValue(true));

            TJsonValue rhs;
            rhs.InsertValue("null value", TJsonValue(JSON_NULL));
            rhs.InsertValue("int key", TJsonValue(10));
            rhs.InsertValue("double key", TJsonValue(11.11));
            rhs.InsertValue("string key", TJsonValue("string"));
            rhs.InsertValue("bool key", TJsonValue(true));

            UNIT_ASSERT(lhs != rhs);
            UNIT_ASSERT(rhs != lhs);
        }
    }

    Y_UNIT_TEST(SwapTest) {
        {
            TJsonValue lhs;
            lhs.InsertValue("a", "b");
            TJsonValue lhsCopy = lhs;

            TJsonValue rhs(JSON_NULL);
            TJsonValue rhsCopy = rhs;

            UNIT_ASSERT(lhs == lhsCopy);
            UNIT_ASSERT(rhs == rhsCopy);

            lhs.Swap(rhs);

            UNIT_ASSERT(rhs == lhsCopy);
            UNIT_ASSERT(lhs == rhsCopy);

            lhs.Swap(rhs);

            UNIT_ASSERT(lhs == lhsCopy);
            UNIT_ASSERT(rhs == rhsCopy);
        }
    }

    Y_UNIT_TEST(GetValueByPathTest) {
        {
            TJsonValue lhs;
            TJsonValue first;
            TJsonValue second;
            TJsonValue last;
            first.InsertValue("e", "f");
            second.InsertValue("c", first);
            last.InsertValue("a", second);
            lhs.InsertValue("l", last);

            TJsonValue result;
            UNIT_ASSERT(lhs.GetValueByPath("l/a/c/e", result, '/'));
            UNIT_ASSERT(result.GetStringRobust() == "f");
            UNIT_ASSERT(!lhs.GetValueByPath("l/a/c/se", result, '/'));
            UNIT_ASSERT(lhs.GetValueByPath("l/a/c", result, '/'));
            UNIT_ASSERT(result.GetStringRobust() == "{\"e\":\"f\"}");

            // faster TStringBuf version
            UNIT_ASSERT_EQUAL(*lhs.GetValueByPath("l", '/'), last);
            UNIT_ASSERT_EQUAL(*lhs.GetValueByPath("l/a", '/'), second);
            UNIT_ASSERT_EQUAL(*lhs.GetValueByPath("l/a/c", '/'), first);
            UNIT_ASSERT_EQUAL(*lhs.GetValueByPath("l.a.c.e", '.'), "f");
            UNIT_ASSERT_EQUAL(lhs.GetValueByPath("l/a/c/e/x", '/'), NULL);
            UNIT_ASSERT_EQUAL(lhs.GetValueByPath("a/c/e/x", '/'), NULL);
            UNIT_ASSERT_EQUAL(lhs.GetValueByPath("nokey", '/'), NULL);
            UNIT_ASSERT_EQUAL(*lhs.GetValueByPath("", '/'), lhs); // itself

            TJsonValue array;
            TJsonValue third;
            array[0] = first;
            array[1] = second;
            third["t"] = array;

            UNIT_ASSERT(array.GetValueByPath("[0].e", result));
            UNIT_ASSERT(result.GetStringRobust() == "f");
            UNIT_ASSERT(third.GetValueByPath("t.[0].e", result));
            UNIT_ASSERT(result.GetStringRobust() == "f");
            UNIT_ASSERT(third.GetValueByPath("t.[1].c.e", result));
            UNIT_ASSERT(result.GetStringRobust() == "f");
            UNIT_ASSERT(!third.GetValueByPath("t.[2]", result));

            UNIT_ASSERT(third.SetValueByPath("t.[2]", "g"));
            UNIT_ASSERT(third.GetValueByPath("t.[2]", result));
            UNIT_ASSERT(result.GetStringRobust() == "g");

            UNIT_ASSERT(lhs.SetValueByPath("l/a/c/se", "h", '/'));
            UNIT_ASSERT(lhs.GetValueByPath("l/a/c/se", result, '/'));
            UNIT_ASSERT(result.GetStringRobust() == "h");
        }
    }

    Y_UNIT_TEST(GetValueByPathConstTest) {
        TJsonValue lhs;
        TJsonValue first;
        TJsonValue second;
        TJsonValue last;
        first.InsertValue("e", "f");
        second.InsertValue("c", first);
        last.InsertValue("a", second);
        lhs.InsertValue("l", last);

        {
            const TJsonValue* result = lhs.GetValueByPath("l", '/');
            UNIT_ASSERT_EQUAL(*result, last);
        }
        {
            const TJsonValue* result = lhs.GetValueByPath("l/a", '/');
            UNIT_ASSERT_EQUAL(*result, second);
        }
        {
            const TJsonValue* result = lhs.GetValueByPath("l/a/c", '/');
            UNIT_ASSERT_EQUAL(*result, first);
        }
        {
            const TJsonValue* result = lhs.GetValueByPath("l.a.c.e", '.');
            UNIT_ASSERT_EQUAL(*result, "f");
        }
        {
            const TJsonValue* result = lhs.GetValueByPath("l/a/c/e/x", '/');
            UNIT_ASSERT_EQUAL(result, nullptr);
        }
        {
            const TJsonValue* result = lhs.GetValueByPath("a/c/e/x", '/');
            UNIT_ASSERT_EQUAL(result, nullptr);
        }
        {
            const TJsonValue* result = lhs.GetValueByPath("nokey", '/');
            UNIT_ASSERT_EQUAL(result, nullptr);
        }
        {
            const TJsonValue* result = lhs.GetValueByPath("", '/');
            UNIT_ASSERT_EQUAL(*result, lhs); // itself
        }

        TJsonValue array;
        TJsonValue third;
        array[0] = first;
        array[1] = second;
        third["t"] = array;

        UNIT_ASSERT(array.GetValueByPath("[0].e", '.')->GetStringRobust() == "f");
        UNIT_ASSERT(third.GetValueByPath("t.[0].e", '.')->GetStringRobust() == "f");
        UNIT_ASSERT(third.GetValueByPath("t.[1].c.e", '.')->GetStringRobust() == "f");
    }

    Y_UNIT_TEST(EraseValueFromArray) {
        {
            TJsonValue vec;
            vec.AppendValue(TJsonValue(0));
            vec.AppendValue(TJsonValue(1));
            vec.AppendValue(TJsonValue("2"));
            vec.AppendValue(TJsonValue("3.14"));

            TJsonValue vec1;
            vec1.AppendValue(TJsonValue(0));
            vec1.AppendValue(TJsonValue("2"));
            vec1.AppendValue(TJsonValue("3.14"));

            TJsonValue vec2;
            vec2.AppendValue(TJsonValue(0));
            vec2.AppendValue(TJsonValue("2"));

            TJsonValue vec3;
            vec3.AppendValue(TJsonValue("2"));

            TJsonValue vec4(JSON_ARRAY);

            UNIT_ASSERT(vec.IsArray());
            UNIT_ASSERT(vec.GetArray().size() == 4);
            vec.EraseValue(1);
            UNIT_ASSERT(vec.GetArray().size() == 3);
            UNIT_ASSERT(vec == vec1);
            vec.EraseValue(2);
            UNIT_ASSERT(vec.GetArray().size() == 2);
            UNIT_ASSERT(vec == vec2);
            vec.EraseValue(0);
            UNIT_ASSERT(vec.GetArray().size() == 1);
            UNIT_ASSERT(vec == vec3);
            vec.EraseValue(0);
            UNIT_ASSERT(vec.GetArray().size() == 0);
            UNIT_ASSERT(vec == vec4);
        }
    }

    Y_UNIT_TEST(NonConstMethodsTest) {
        {
            TJsonValue src;
            TJsonValue value1;
            value1.AppendValue(1);
            value1.AppendValue(2);
            src.InsertValue("key", value1);
            src.InsertValue("key1", "HI!");

            TJsonValue dst;
            TJsonValue value2;
            value2.AppendValue(1);
            value2.AppendValue(2);
            value2.AppendValue(3);
            dst.InsertValue("key", value2);

            src.GetValueByPath("key", '.')->AppendValue(3);
            src.EraseValue("key1");
            UNIT_ASSERT(src == dst);

            dst.GetValueByPath("key", '.')->EraseValue(0);
            UNIT_ASSERT(src != dst);
            src.GetValueByPath("key", '.')->EraseValue(0);
            UNIT_ASSERT(src == dst);
        }

        {
            TJsonValue src;
            TJsonValue value1;
            TJsonValue arr1;
            value1.InsertValue("key", "value");
            arr1.AppendValue(value1);
            arr1.AppendValue(value1);
            arr1.AppendValue(value1);
            src.InsertValue("arr", arr1);

            TJsonValue dst;
            TJsonValue value2;
            TJsonValue arr2;
            value2.InsertValue("key", "value");
            value2.InsertValue("yek", "eulav");
            arr2.AppendValue(value2);
            arr2.AppendValue(value2);
            arr2.AppendValue(value2);
            arr2.AppendValue(value2);
            dst.InsertValue("arr", arr2);

            src["arr"].AppendValue(value1);
            for (auto& node : src["arr"].GetArraySafe()) {
                node.InsertValue("yek", "eulav");
            }
            UNIT_ASSERT(src == dst);
        }

        {
            TJsonValue src;
            TJsonValue value1;
            TJsonValue arr1;
            value1.InsertValue("key", "value");
            arr1.AppendValue(value1);
            arr1.AppendValue(value1);
            arr1.AppendValue(value1);
            src.InsertValue("arr", arr1);

            TJsonValue dst;
            TJsonValue value2;
            TJsonValue arr2;
            value2.InsertValue("key", "value");
            value2.InsertValue("yek", "eulav");
            arr2.AppendValue(value2);
            arr2.AppendValue(value2);
            arr2.AppendValue(value2);
            arr2.AppendValue(value2);
            dst.InsertValue("arr", arr2);

            src["arr"].AppendValue(value1);
            for (auto& node : src.GetValueByPath("arr", '.')->GetArraySafe()) {
                node.InsertValue("yek", "eulav");
            }
            UNIT_ASSERT(src == dst);
        }

        {
            TJsonValue json;
            json.InsertValue("key", "value");
            try {
                json.GetArraySafe();
                UNIT_ASSERT(false);
            } catch (const TJsonException&) {
            }

            const TJsonValue constJson(json);
            try {
                constJson.GetArray();
            } catch (...) {
                UNIT_ASSERT(false);
            }
        }

        {
            // Check non-const GetArraySafe()
            TJsonValue json{JSON_ARRAY};
            json.GetArraySafe().push_back(TJsonValue{"foo"});

            TJsonValue expectedJson;
            expectedJson.AppendValue(TJsonValue{"foo"});
            UNIT_ASSERT(json == expectedJson);

            TJsonValue::TArray jsonArray = std::move(json.GetArraySafe());
            TJsonValue::TArray expectedArray = {TJsonValue{"foo"}};
            UNIT_ASSERT(jsonArray == expectedArray);
        }

        {
            // Check non-const GetMap()
            TJsonValue json{JSON_MAP};
            json.GetMapSafe()["foo"] = "bar";

            TJsonValue expectedJson;
            expectedJson.InsertValue("foo", "bar");
            UNIT_ASSERT(json == expectedJson);

            TJsonValue::TMapType jsonMap = std::move(json.GetMapSafe());
            TJsonValue::TMapType expectedMap = {{"foo", TJsonValue{"bar"}}};
            UNIT_ASSERT(jsonMap == expectedMap);
        }
    }

    Y_UNIT_TEST(NonexistentFieldAccessTest) {
        {
            TJsonValue json;
            json.InsertValue("some", "key");

            UNIT_ASSERT(!json["some"]["weird"]["access"]["sequence"].Has("value"));
            UNIT_ASSERT(!json["some"]["weird"]["access"]["sequence"].IsDefined());

            UNIT_ASSERT(json["some"].GetType() == JSON_MAP);
        }
    }

    Y_UNIT_TEST(DefaultValuesTest) {
        {
            TJsonValue json;
            json.InsertValue("some", "key");
            json.InsertValue("existing", 1.2);

            UNIT_ASSERT_VALUES_EQUAL(json["existing"].GetDoubleSafe(), 1.2);
            UNIT_ASSERT_VALUES_EQUAL(json["existing"].GetDoubleSafe(15), 1.2);

            UNIT_ASSERT_EXCEPTION(json["some"].GetUIntegerSafe(), yexception);
            UNIT_ASSERT_EXCEPTION(json["some"].GetUIntegerSafe(12), yexception);

            UNIT_ASSERT_EXCEPTION(json["nonexistent"].GetUIntegerSafe(), yexception);
            UNIT_ASSERT_VALUES_EQUAL(json["nonexistent"].GetUIntegerSafe(12), 12);
            UNIT_ASSERT_VALUES_EQUAL(json["nonexistent"]["more_nonexistent"].GetUIntegerSafe(12), 12);

            json.InsertValue("map", TJsonValue(JSON_MAP));

            UNIT_ASSERT_VALUES_EQUAL(json["map"]["nonexistent"].GetUIntegerSafe(12), 12);
        }
    }

    Y_UNIT_TEST(GetArrayPointerInArrayTest) {
        TJsonValue outer;
        {
            TJsonValue json;
            json.AppendValue(1);
            json.AppendValue(2);
            json.AppendValue(3);

            outer.AppendValue(json);
        }
        const TJsonValue::TArray* array = nullptr;
        GetArrayPointer(outer, 0, &array);
        UNIT_ASSERT_VALUES_EQUAL((*array)[1], 2);
    }

    Y_UNIT_TEST(GetArrayPointerInMapTest) {
        TJsonValue outer;
        {
            TJsonValue json;
            json.AppendValue(1);
            json.AppendValue(2);
            json.AppendValue(3);

            outer.InsertValue("x", json);
        }
        const TJsonValue::TArray* array = nullptr;
        GetArrayPointer(outer, "x", &array);
        UNIT_ASSERT_VALUES_EQUAL((*array)[1], 2);
    }

    Y_UNIT_TEST(GetMapPointerInArrayTest) {
        TJsonValue outer;
        {
            TJsonValue json;
            json.InsertValue("a", 1);
            json.InsertValue("b", 2);
            json.InsertValue("c", 3);

            outer.AppendValue(json);
        }
        const TJsonValue::TMapType* map = nullptr;
        GetMapPointer(outer, 0, &map);
        UNIT_ASSERT_VALUES_EQUAL((*map).at("b"), 2);
    }

    Y_UNIT_TEST(GetMapPointerInMapTest) {
        TJsonValue outer;
        {
            TJsonValue json;
            json.InsertValue("a", 1);
            json.InsertValue("b", 2);
            json.InsertValue("c", 3);

            outer.InsertValue("x", json);
        }
        const TJsonValue::TMapType* map = nullptr;
        GetMapPointer(outer, "x", &map);
        UNIT_ASSERT_VALUES_EQUAL((*map).at("b"), 2);
    }

    Y_UNIT_TEST(GetIntegerRobustBignumStringTest) {
        TString value = "1626862681464633683";
        TJsonValue json(value);
        UNIT_ASSERT_VALUES_EQUAL(json.GetUIntegerRobust(), FromString<ui64>(value));
        UNIT_ASSERT_VALUES_EQUAL(json.GetIntegerRobust(), FromString<i64>(value));
    }

    Y_UNIT_TEST(MoveSubpartToSelf) {
        TJsonValue json;
        json[0] = "testing 0";
        json[1] = "testing 1";
        json[2] = "testing 2";
        json = std::move(json[1]);
        UNIT_ASSERT_VALUES_EQUAL(json.GetString(), "testing 1");

        const char* longTestString =
            "Testing TJsonValue& operator=(TJsonValue&&) subpart self moving "
            "after TJsonValue was constrcuted from TString&&.";

        json["hello"] = TString{longTestString};
        json = std::move(json["hello"]);
        UNIT_ASSERT_VALUES_EQUAL(json.GetString(), longTestString);
    }

    Y_UNIT_TEST(TJsonArrayMapConstructor) {
        TJsonMap emptyMap;
        UNIT_ASSERT_VALUES_EQUAL(emptyMap.GetType(), JSON_MAP);
        UNIT_ASSERT_VALUES_EQUAL(emptyMap.GetMapSafe().size(), 0);

        TJsonArray emptyArray;
        UNIT_ASSERT_VALUES_EQUAL(emptyArray.GetType(), JSON_ARRAY);
        UNIT_ASSERT_VALUES_EQUAL(emptyArray.GetArraySafe().size(), 0);

        TJsonMap filled = {
            {"1", 1},
            {"2", "2"},
            {"3", TJsonArray{3}},
            {"4", TJsonMap{{"5", 5}}},
        };
        UNIT_ASSERT_VALUES_EQUAL(filled.GetType(), JSON_MAP);
        UNIT_ASSERT_VALUES_EQUAL(filled["1"], TJsonValue{1});
        UNIT_ASSERT_VALUES_EQUAL(filled["2"], TJsonValue{"2"});
        UNIT_ASSERT_VALUES_EQUAL(filled["3"].GetArraySafe().size(), 1);
        UNIT_ASSERT_VALUES_EQUAL(filled["3"][0], TJsonValue{3});
        UNIT_ASSERT_VALUES_EQUAL(filled["4"].GetMapSafe().size(), 1);
        UNIT_ASSERT_VALUES_EQUAL(filled["4"]["5"], TJsonValue{5});
    }
} // TJsonValueTest
