/*
 * GDevelop Core
 * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
 * reserved. This project is released under the MIT License.
 */
/**
 * @file Tests covering serialization to JSON.
 */
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Builtin/StandardEvent.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Serialization.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/Variable.h"
#include "GDCore/Tools/SystemStats.h"
#include "GDCore/Tools/VersionWrapper.h"
#include "catch.hpp"

using namespace gd;

TEST_CASE("SerializerElement", "[common]") {
  SECTION("Basics and copying") {
    SerializerElement element;
    element.AddChild("child1").SetStringValue("value123");
    element.AddChild("child2").SetDoubleValue(45.6);
    element.SetStringAttribute("attr1", "attr123");

    SerializerElement copiedElement = element;
    REQUIRE(element.GetChild("child1").GetStringValue() == "value123");
    REQUIRE(element.GetChild("child2").GetDoubleValue() == 45.6);
    REQUIRE(element.GetStringAttribute("attr1") == "attr123");
    REQUIRE(copiedElement.GetChild("child1").GetStringValue() == "value123");
    REQUIRE(copiedElement.GetChild("child2").GetDoubleValue() == 45.6);
    REQUIRE(copiedElement.GetStringAttribute("attr1") == "attr123");

    element.GetChild("child1").SetStringValue("value123 modified");
    copiedElement.GetChild("child2").SetDoubleValue(45.678);
    copiedElement.SetStringAttribute("attr1", "attr123 modified");
    REQUIRE(element.GetChild("child1").GetStringValue() == "value123 modified");
    REQUIRE(element.GetChild("child2").GetDoubleValue() == 45.6);
    REQUIRE(element.GetStringAttribute("attr1") == "attr123");
    REQUIRE(copiedElement.GetChild("child1").GetStringValue() == "value123");
    REQUIRE(copiedElement.GetChild("child2").GetDoubleValue() == 45.678);
    REQUIRE(copiedElement.GetStringAttribute("attr1") == "attr123 modified");
  }

  SECTION("Accessing already existing children, in objects") {
    SerializerElement element;
    element.AddChild("child1").SetStringValue("value123");
    element.AddChild("child1").SetStringValue("value456");
    element.AddChild("child2").SetDoubleValue(45.6);

    REQUIRE(element.GetChild("child1").GetStringValue() == "value456");
    REQUIRE(element.GetChild("child2").GetDoubleValue() == 45.6);
  }

  SECTION("Adding multiple named children, in arrays") {
    SerializerElement element;
    element.ConsiderAsArrayOf("namedElement");
    element.AddChild("namedElement").SetStringValue("value123");
    element.AddChild("namedElement").SetStringValue("value456");
    element.AddChild("namedElement").SetDoubleValue(45.6);

    REQUIRE(element.GetChild(0).GetStringValue() == "value123");
    REQUIRE(element.GetChild(1).GetStringValue() == "value456");
    REQUIRE(element.GetChild(2).GetDoubleValue() == 45.6);
  }

  SECTION("Adding multiple unnamed children, in arrays") {
    SerializerElement element;
    element.ConsiderAsArray();
    element.AddChild("").SetStringValue("value123");
    element.AddChild("").SetStringValue("value456");
    element.AddChild("").SetDoubleValue(45.6);

    REQUIRE(element.GetChild(0).GetStringValue() == "value123");
    REQUIRE(element.GetChild(1).GetStringValue() == "value456");
    REQUIRE(element.GetChild(2).GetDoubleValue() == 45.6);
  }

  SECTION("(Deprecated) attributes") {
    SerializerElement element;
    element.AddChild("child1").SetStringValue("value123");
    element.AddChild("child2").SetDoubleValue(45.6);
    element.SetStringAttribute("attr1", "attr123");

    // Setting a string attribute with the same name as a child
    // will remove this child. This is because, even though in the future
    // only child should be used (and only children are loaded from JSON),
    // we still support attributes, so that
    // setting one should take precedence over any children that was loaded.
    element.SetStringAttribute("child1", "value456");

    REQUIRE(element.HasChild("child1") == false);
    REQUIRE(element.HasChild("child2") == true);
    REQUIRE(element.GetStringAttribute("attr1") == "attr123");
    REQUIRE(element.GetStringAttribute("child1") == "value456");
  }
}

TEST_CASE("Serializer", "[common]") {
  SECTION("JSON basics") {
    gd::String originalJSON = "{\"ok\":true,\"hello\":\"world\"}";
    SerializerElement element = Serializer::FromJSON(originalJSON);
    REQUIRE(element.GetChild("ok").GetBoolValue() == true);
    REQUIRE(element.GetChild("hello").GetStringValue() == "world");

    gd::String json = Serializer::ToJSON(element);
    REQUIRE(json == originalJSON);
  }

  SECTION("Quotes and special characters") {
    gd::String originalJSON =
        "{\"\\\"hello\\\"\":\" \\\"quote\\\" \",\"caret-prop\":"
        "1,\"special-\\b\\f\\n\\r\\t\\\"\":\"\\b\\f\\n\\r\\t\"}";
    SerializerElement element = Serializer::FromJSON(originalJSON);
    REQUIRE(element.GetChild("caret-prop").GetBoolValue() == true);
    REQUIRE(element.GetChild("\"hello\"").GetStringValue() ==
            " \"quote\" ");
    REQUIRE(element.GetChild("special-\b\f\n\r\t\"").GetStringValue() ==
            "\b\f\n\r\t");

    gd::String json = Serializer::ToJSON(element);
    REQUIRE(json == originalJSON);
  }

  SECTION("UTF8 characters") {
    gd::String originalJSON =
        u8"{\"Ich heiße GDevelop\":\"Gut!\",\"Bonjour à tout le monde\":"
        u8"1,\"Hello 官话 world\":\"官话\"}";
    SerializerElement element = Serializer::FromJSON(originalJSON);
    REQUIRE(
        element.GetChild(u8"Bonjour à tout le monde").GetBoolValue() ==
        true);
    REQUIRE(element.GetChild(u8"Ich heiße GDevelop").GetStringValue() ==
            "Gut!");
    REQUIRE(element.GetChild(u8"Hello 官话 world").GetStringValue() ==
            u8"官话");

    gd::String json = Serializer::ToJSON(element);
    REQUIRE(json == originalJSON);
  }

  SECTION("Idempotency of unserializing and serializing again") {
    auto unserializeAndSerializeToJSON = [](const gd::String& originalJSON) {
      SerializerElement element = Serializer::FromJSON(originalJSON);
      return Serializer::ToJSON(element);
    };

    SECTION("Strings and numbers") {
      gd::String test1 = "\"\"";
      REQUIRE(unserializeAndSerializeToJSON(test1) == test1);
      gd::String test2 = "123.455";
      REQUIRE(unserializeAndSerializeToJSON(test2) == test2);
    }
    SECTION("Objects") {
      gd::String test1 = "{}";
      REQUIRE(unserializeAndSerializeToJSON(test1) == test1);
      gd::String test2 = "{\"a\":1}";
      REQUIRE(unserializeAndSerializeToJSON(test2) == test2);
      gd::String test3 = "{\"a\":1,\"b\":{\"c\":2}}";
      REQUIRE(unserializeAndSerializeToJSON(test3) == test3);
    }
    SECTION("Arrays") {
      gd::String test1 = "[]";
      REQUIRE(unserializeAndSerializeToJSON(test1) == test1);
      gd::String test2 = "[1,2]";
      REQUIRE(unserializeAndSerializeToJSON(test2) == test2);
    }
    SECTION("Mixed") {
      gd::String test1 =
          "{\"hello\":{\"world\":[{},[],3,\"4\"],\"world2\":[-1,\"-2\","
          "{\"-3\":[-4]}]}}";
      REQUIRE(unserializeAndSerializeToJSON(test1) == test1);
      gd::String test2 =
          "{\"hello\":{\"world\":[{},[],3,4],\"world2\":[-1,\"-2\","
          "{\"-3\":[-4]}]}}";
      REQUIRE(unserializeAndSerializeToJSON(test2) == test2);
    }
  }

  SECTION("(Deprecated) attributes") {
    gd::String originalJSON = "{\"ok\":true,\"hello\":\"world\"}";
    SerializerElement element = Serializer::FromJSON(originalJSON);
    REQUIRE(element.GetChild("ok").GetBoolValue() == true);
    REQUIRE(element.GetChild("hello").GetStringValue() == "world");

    // Attributes are deprecated, but can still be used to access
    // children:
    REQUIRE(element.GetBoolAttribute("ok") == true);
    REQUIRE(element.GetStringAttribute("hello") == "world");
    REQUIRE(element.GetStringAttribute("idontexists") == "");

    // Add a new children and changes an attribute. Even though "hello"
    // was loaded as a child, attributes are still supported so the child
    // with this name should be removed.
    element.AddChild("hello2").SetStringValue("world2");
    element.SetStringAttribute("hello", "world1");

    gd::String json = Serializer::ToJSON(element);
    REQUIRE(json == "{\"hello\":\"world1\",\"ok\":true,\"hello2\":\"world2\"}");
  }
}
