/*
 * GDevelop Core
 * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
 * reserved. This project is released under the MIT License.
 */

#include "Project.h"

#include <stdio.h>
#include <stdlib.h>

#include <SFML/System/Utf.hpp>
#include <cctype>
#include <fstream>
#include <map>
#include <vector>

#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
#include "GDCore/IDE/PlatformManager.h"
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/ExternalLayout.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectGroupsContainer.h"
#include "GDCore/Project/ResourcesManager.h"
#include "GDCore/Project/SourceFile.h"
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/TinyXml/tinyxml.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Tools/Log.h"
#include "GDCore/Tools/PolymorphicClone.h"
#include "GDCore/Tools/UUID/UUID.h"
#include "GDCore/Tools/VersionWrapper.h"
#include "GDCore/Utf8/utf8.h"

using namespace std;

#undef CreateEvent

namespace gd {

Project::Project()
    : name(_("Project")),
      version("1.0.0"),
      packageName("com.example.gamename"),
      orientation("landscape"),
      folderProject(false),
      windowWidth(800),
      windowHeight(600),
      maxFPS(60),
      minFPS(20),
      verticalSync(false),
      scaleMode("linear"),
      pixelsRounding(false),
      adaptGameResolutionAtRuntime(true),
      sizeOnStartupMode("adaptWidth"),
      projectUuid(""),
      useDeprecatedZeroAsDefaultZOrder(false),
      useExternalSourceFiles(false),
      currentPlatform(NULL),
      gdMajorVersion(gd::VersionWrapper::Major()),
      gdMinorVersion(gd::VersionWrapper::Minor()),
      gdBuildVersion(gd::VersionWrapper::Build()) {}

Project::~Project() {}

void Project::ResetProjectUuid() { projectUuid = UUID::MakeUuid4(); }

std::unique_ptr<gd::Object> Project::CreateObject(
    const gd::String& type,
    const gd::String& name,
    const gd::String& platformName) {
  for (std::size_t i = 0; i < platforms.size(); ++i) {
    if (!platformName.empty() && platforms[i]->GetName() != platformName)
      continue;

    std::unique_ptr<gd::Object> object = platforms[i]->CreateObject(
        type, name);  // Create a base object if the type can't be found in the
                      // platform
    if (object && object->GetType() == type)
      return object;  // If the object is valid and has the good type (not a
                      // base object), return it
  }

  return nullptr;
}

std::shared_ptr<gd::BaseEvent> Project::CreateEvent(
    const gd::String& type, const gd::String& platformName) {
  for (std::size_t i = 0; i < platforms.size(); ++i) {
    if (!platformName.empty() && platforms[i]->GetName() != platformName)
      continue;

    std::shared_ptr<gd::BaseEvent> event = platforms[i]->CreateEvent(type);
    if (event) return event;
  }

  return std::shared_ptr<gd::BaseEvent>();
}

Platform& Project::GetCurrentPlatform() const {
  if (currentPlatform == NULL)
    std::cout << "FATAL ERROR: Project has no assigned current platform. GD "
                 "will crash."
              << std::endl;

  return *currentPlatform;
}

void Project::AddPlatform(Platform& platform) {
  for (std::size_t i = 0; i < platforms.size(); ++i) {
    if (platforms[i] == &platform) return;
  }

  // Add the platform and make it the current one if the game has no other
  // platform.
  platforms.push_back(&platform);
  if (currentPlatform == NULL) currentPlatform = &platform;
}

void Project::SetCurrentPlatform(const gd::String& platformName) {
  for (std::size_t i = 0; i < platforms.size(); ++i) {
    if (platforms[i]->GetName() == platformName) {
      currentPlatform = platforms[i];
      return;
    }
  }
}

bool Project::RemovePlatform(const gd::String& platformName) {
  if (platforms.size() <= 1) return false;

  for (std::size_t i = 0; i < platforms.size(); ++i) {
    if (platforms[i]->GetName() == platformName) {
      // Remove the platform, ensuring that currentPlatform remains correct.
      if (currentPlatform == platforms[i]) currentPlatform = platforms.back();
      if (currentPlatform == platforms[i]) currentPlatform = platforms[0];
      platforms.erase(platforms.begin() + i);

      return true;
    }
  }

  return false;
}

bool Project::HasLayoutNamed(const gd::String& name) const {
  return (find_if(scenes.begin(),
                  scenes.end(),
                  bind2nd(gd::LayoutHasName(), name)) != scenes.end());
}
gd::Layout& Project::GetLayout(const gd::String& name) {
  return *(*find_if(
      scenes.begin(), scenes.end(), bind2nd(gd::LayoutHasName(), name)));
}
const gd::Layout& Project::GetLayout(const gd::String& name) const {
  return *(*find_if(
      scenes.begin(), scenes.end(), bind2nd(gd::LayoutHasName(), name)));
}
gd::Layout& Project::GetLayout(std::size_t index) { return *scenes[index]; }
const gd::Layout& Project::GetLayout(std::size_t index) const {
  return *scenes[index];
}
std::size_t Project::GetLayoutPosition(const gd::String& name) const {
  for (std::size_t i = 0; i < scenes.size(); ++i) {
    if (scenes[i]->GetName() == name) return i;
  }
  return gd::String::npos;
}
std::size_t Project::GetLayoutsCount() const { return scenes.size(); }

void Project::SwapLayouts(std::size_t first, std::size_t second) {
  if (first >= scenes.size() || second >= scenes.size()) return;

  std::iter_swap(scenes.begin() + first, scenes.begin() + second);
}

gd::Layout& Project::InsertNewLayout(const gd::String& name,
                                     std::size_t position) {
  gd::Layout& newlyInsertedLayout = *(*(scenes.emplace(
      position < scenes.size() ? scenes.begin() + position : scenes.end(),
      new Layout())));

  newlyInsertedLayout.SetName(name);
  newlyInsertedLayout.UpdateBehaviorsSharedData(*this);

  return newlyInsertedLayout;
}

gd::Layout& Project::InsertLayout(const gd::Layout& layout,
                                  std::size_t position) {
  gd::Layout& newlyInsertedLayout = *(*(scenes.emplace(
      position < scenes.size() ? scenes.begin() + position : scenes.end(),
      new Layout(layout))));

  newlyInsertedLayout.UpdateBehaviorsSharedData(*this);

  return newlyInsertedLayout;
}

void Project::RemoveLayout(const gd::String& name) {
  std::vector<std::unique_ptr<gd::Layout> >::iterator scene =
      find_if(scenes.begin(), scenes.end(), bind2nd(gd::LayoutHasName(), name));
  if (scene == scenes.end()) return;

  scenes.erase(scene);
}

bool Project::HasExternalEventsNamed(const gd::String& name) const {
  return (find_if(externalEvents.begin(),
                  externalEvents.end(),
                  bind2nd(gd::ExternalEventsHasName(), name)) !=
          externalEvents.end());
}
gd::ExternalEvents& Project::GetExternalEvents(const gd::String& name) {
  return *(*find_if(externalEvents.begin(),
                    externalEvents.end(),
                    bind2nd(gd::ExternalEventsHasName(), name)));
}
const gd::ExternalEvents& Project::GetExternalEvents(
    const gd::String& name) const {
  return *(*find_if(externalEvents.begin(),
                    externalEvents.end(),
                    bind2nd(gd::ExternalEventsHasName(), name)));
}
gd::ExternalEvents& Project::GetExternalEvents(std::size_t index) {
  return *externalEvents[index];
}
const gd::ExternalEvents& Project::GetExternalEvents(std::size_t index) const {
  return *externalEvents[index];
}
std::size_t Project::GetExternalEventsPosition(const gd::String& name) const {
  for (std::size_t i = 0; i < externalEvents.size(); ++i) {
    if (externalEvents[i]->GetName() == name) return i;
  }
  return gd::String::npos;
}
std::size_t Project::GetExternalEventsCount() const {
  return externalEvents.size();
}

gd::ExternalEvents& Project::InsertNewExternalEvents(const gd::String& name,
                                                     std::size_t position) {
  gd::ExternalEvents& newlyInsertedExternalEvents = *(*(externalEvents.emplace(
      position < externalEvents.size() ? externalEvents.begin() + position
                                       : externalEvents.end(),
      new gd::ExternalEvents())));

  newlyInsertedExternalEvents.SetName(name);

  return newlyInsertedExternalEvents;
}

gd::ExternalEvents& Project::InsertExternalEvents(
    const gd::ExternalEvents& events, std::size_t position) {
  gd::ExternalEvents& newlyInsertedExternalEvents = *(*(externalEvents.emplace(
      position < externalEvents.size() ? externalEvents.begin() + position
                                       : externalEvents.end(),
      new gd::ExternalEvents(events))));

  return newlyInsertedExternalEvents;
}

void Project::RemoveExternalEvents(const gd::String& name) {
  std::vector<std::unique_ptr<gd::ExternalEvents> >::iterator events =
      find_if(externalEvents.begin(),
              externalEvents.end(),
              bind2nd(gd::ExternalEventsHasName(), name));
  if (events == externalEvents.end()) return;

  externalEvents.erase(events);
}

void Project::SwapExternalEvents(std::size_t first, std::size_t second) {
  if (first >= externalEvents.size() || second >= externalEvents.size()) return;

  std::iter_swap(externalEvents.begin() + first,
                 externalEvents.begin() + second);
}

void Project::SwapExternalLayouts(std::size_t first, std::size_t second) {
  if (first >= externalLayouts.size() || second >= externalLayouts.size())
    return;

  std::iter_swap(externalLayouts.begin() + first,
                 externalLayouts.begin() + second);
}
bool Project::HasExternalLayoutNamed(const gd::String& name) const {
  return (find_if(externalLayouts.begin(),
                  externalLayouts.end(),
                  bind2nd(gd::ExternalLayoutHasName(), name)) !=
          externalLayouts.end());
}
gd::ExternalLayout& Project::GetExternalLayout(const gd::String& name) {
  return *(*find_if(externalLayouts.begin(),
                    externalLayouts.end(),
                    bind2nd(gd::ExternalLayoutHasName(), name)));
}
const gd::ExternalLayout& Project::GetExternalLayout(
    const gd::String& name) const {
  return *(*find_if(externalLayouts.begin(),
                    externalLayouts.end(),
                    bind2nd(gd::ExternalLayoutHasName(), name)));
}
gd::ExternalLayout& Project::GetExternalLayout(std::size_t index) {
  return *externalLayouts[index];
}
const gd::ExternalLayout& Project::GetExternalLayout(std::size_t index) const {
  return *externalLayouts[index];
}
std::size_t Project::GetExternalLayoutPosition(const gd::String& name) const {
  for (std::size_t i = 0; i < externalLayouts.size(); ++i) {
    if (externalLayouts[i]->GetName() == name) return i;
  }
  return gd::String::npos;
}

std::size_t Project::GetExternalLayoutsCount() const {
  return externalLayouts.size();
}

gd::ExternalLayout& Project::InsertNewExternalLayout(const gd::String& name,
                                                     std::size_t position) {
  gd::ExternalLayout& newlyInsertedExternalLayout = *(*(externalLayouts.emplace(
      position < externalLayouts.size() ? externalLayouts.begin() + position
                                        : externalLayouts.end(),
      new gd::ExternalLayout())));

  newlyInsertedExternalLayout.SetName(name);
  return newlyInsertedExternalLayout;
}

gd::ExternalLayout& Project::InsertExternalLayout(
    const gd::ExternalLayout& layout, std::size_t position) {
  gd::ExternalLayout& newlyInsertedExternalLayout = *(*(externalLayouts.emplace(
      position < externalLayouts.size() ? externalLayouts.begin() + position
                                        : externalLayouts.end(),
      new gd::ExternalLayout(layout))));

  return newlyInsertedExternalLayout;
}

void Project::RemoveExternalLayout(const gd::String& name) {
  std::vector<std::unique_ptr<gd::ExternalLayout> >::iterator externalLayout =
      find_if(externalLayouts.begin(),
              externalLayouts.end(),
              bind2nd(gd::ExternalLayoutHasName(), name));
  if (externalLayout == externalLayouts.end()) return;

  externalLayouts.erase(externalLayout);
}

void Project::SwapEventsFunctionsExtensions(std::size_t first,
                                            std::size_t second) {
  if (first >= eventsFunctionsExtensions.size() ||
      second >= eventsFunctionsExtensions.size())
    return;

  std::iter_swap(eventsFunctionsExtensions.begin() + first,
                 eventsFunctionsExtensions.begin() + second);
}
bool Project::HasEventsFunctionsExtensionNamed(const gd::String& name) const {
  return (
      find_if(
          eventsFunctionsExtensions.begin(),
          eventsFunctionsExtensions.end(),
          [&name](
              const std::unique_ptr<gd::EventsFunctionsExtension>& extension) {
            return extension->GetName() == name;
          }) != eventsFunctionsExtensions.end());
}
gd::EventsFunctionsExtension& Project::GetEventsFunctionsExtension(
    const gd::String& name) {
  return *(*find_if(
      eventsFunctionsExtensions.begin(),
      eventsFunctionsExtensions.end(),
      [&name](const std::unique_ptr<gd::EventsFunctionsExtension>& extension) {
        return extension->GetName() == name;
      }));
}
const gd::EventsFunctionsExtension& Project::GetEventsFunctionsExtension(
    const gd::String& name) const {
  return *(*find_if(
      eventsFunctionsExtensions.begin(),
      eventsFunctionsExtensions.end(),
      [&name](const std::unique_ptr<gd::EventsFunctionsExtension>& extension) {
        return extension->GetName() == name;
      }));
}
gd::EventsFunctionsExtension& Project::GetEventsFunctionsExtension(
    std::size_t index) {
  return *eventsFunctionsExtensions[index];
}
const gd::EventsFunctionsExtension& Project::GetEventsFunctionsExtension(
    std::size_t index) const {
  return *eventsFunctionsExtensions[index];
}
std::size_t Project::GetEventsFunctionsExtensionPosition(
    const gd::String& name) const {
  for (std::size_t i = 0; i < eventsFunctionsExtensions.size(); ++i) {
    if (eventsFunctionsExtensions[i]->GetName() == name) return i;
  }
  return gd::String::npos;
}

std::size_t Project::GetEventsFunctionsExtensionsCount() const {
  return eventsFunctionsExtensions.size();
}

gd::EventsFunctionsExtension& Project::InsertNewEventsFunctionsExtension(
    const gd::String& name, std::size_t position) {
  gd::EventsFunctionsExtension& newlyInsertedEventsFunctionsExtension =
      *(*(eventsFunctionsExtensions.emplace(
          position < eventsFunctionsExtensions.size()
              ? eventsFunctionsExtensions.begin() + position
              : eventsFunctionsExtensions.end(),
          new gd::EventsFunctionsExtension())));

  newlyInsertedEventsFunctionsExtension.SetName(name);
  return newlyInsertedEventsFunctionsExtension;
}

gd::EventsFunctionsExtension& Project::InsertEventsFunctionsExtension(
    const gd::EventsFunctionsExtension& extension, std::size_t position) {
  gd::EventsFunctionsExtension& newlyInsertedEventsFunctionsExtension =
      *(*(eventsFunctionsExtensions.emplace(
          position < eventsFunctionsExtensions.size()
              ? eventsFunctionsExtensions.begin() + position
              : eventsFunctionsExtensions.end(),
          new gd::EventsFunctionsExtension(extension))));

  return newlyInsertedEventsFunctionsExtension;
}

void Project::RemoveEventsFunctionsExtension(const gd::String& name) {
  std::vector<std::unique_ptr<gd::EventsFunctionsExtension> >::iterator
      eventsFunctionExtension = find_if(
          eventsFunctionsExtensions.begin(),
          eventsFunctionsExtensions.end(),
          [&name](
              const std::unique_ptr<gd::EventsFunctionsExtension>& extension) {
            return extension->GetName() == name;
          });
  if (eventsFunctionExtension == eventsFunctionsExtensions.end()) return;

  eventsFunctionsExtensions.erase(eventsFunctionExtension);
}
void Project::ClearEventsFunctionsExtensions() {
  eventsFunctionsExtensions.clear();
}

void Project::UnserializeFrom(const SerializerElement& element) {
  const SerializerElement& gdVersionElement =
      element.GetChild("gdVersion", 0, "GDVersion");
  gdMajorVersion =
      gdVersionElement.GetIntAttribute("major", gdMajorVersion, "Major");
  gdMinorVersion =
      gdVersionElement.GetIntAttribute("minor", gdMinorVersion, "Minor");
  gdBuildVersion = gdVersionElement.GetIntAttribute("build", 0, "Build");
  int revision = gdVersionElement.GetIntAttribute("revision", 0, "Revision");

  if (gdMajorVersion > gd::VersionWrapper::Major())
    gd::LogWarning(
        "The version of GDevelop used to create this game seems to be a new "
        "version.\nGDevelop may fail to open the game, or data may be "
        "missing.\nYou should check if a new version of GDevelop is "
        "available.");
  else {
    if ((gdMajorVersion == gd::VersionWrapper::Major() &&
         gdMinorVersion > gd::VersionWrapper::Minor()) ||
        (gdMajorVersion == gd::VersionWrapper::Major() &&
         gdMinorVersion == gd::VersionWrapper::Minor() &&
         gdBuildVersion > gd::VersionWrapper::Build()) ||
        (gdMajorVersion == gd::VersionWrapper::Major() &&
         gdMinorVersion == gd::VersionWrapper::Minor() &&
         gdBuildVersion == gd::VersionWrapper::Build() &&
         revision > gd::VersionWrapper::Revision())) {
      gd::LogWarning(
          "The version of GDevelop used to create this game seems to be "
          "greater.\nGDevelop may fail to open the game, or data may be "
          "missing.\nYou should check if a new version of GDevelop is "
          "available.");
    }
  }

  const SerializerElement& propElement =
      element.GetChild("properties", 0, "Info");
  SetName(propElement.GetChild("name", 0, "Nom").GetValue().GetString());
  SetVersion(propElement.GetStringAttribute("version", "1.0.0"));
  SetGameResolutionSize(
      propElement.GetChild("windowWidth", 0, "WindowW").GetValue().GetInt(),
      propElement.GetChild("windowHeight", 0, "WindowH").GetValue().GetInt());
  SetMaximumFPS(
      propElement.GetChild("maxFPS", 0, "FPSmax").GetValue().GetInt());
  SetMinimumFPS(
      propElement.GetChild("minFPS", 0, "FPSmin").GetValue().GetInt());
  SetVerticalSyncActivatedByDefault(
      propElement.GetChild("verticalSync").GetValue().GetBool());
  SetScaleMode(propElement.GetStringAttribute("scaleMode", "linear"));
  SetPixelsRounding(propElement.GetBoolAttribute("pixelsRounding", false));
  SetAdaptGameResolutionAtRuntime(
      propElement.GetBoolAttribute("adaptGameResolutionAtRuntime", false));
  SetSizeOnStartupMode(propElement.GetStringAttribute("sizeOnStartupMode", ""));
  SetProjectUuid(propElement.GetStringAttribute("projectUuid", ""));
  SetAuthor(propElement.GetChild("author", 0, "Auteur").GetValue().GetString());
  SetPackageName(propElement.GetStringAttribute("packageName"));
  SetOrientation(propElement.GetStringAttribute("orientation", "default"));
  SetFolderProject(propElement.GetBoolAttribute("folderProject"));
  SetLastCompilationDirectory(propElement
                                  .GetChild("latestCompilationDirectory",
                                            0,
                                            "LatestCompilationDirectory")
                                  .GetValue()
                                  .GetString());
  platformSpecificAssets.UnserializeFrom(
      propElement.GetChild("platformSpecificAssets"));
  loadingScreen.UnserializeFrom(propElement.GetChild("loadingScreen"));

  useExternalSourceFiles =
      propElement.GetBoolAttribute("useExternalSourceFiles");

  authorIds.clear();
  auto& authorIdsElement = propElement.GetChild("authorIds");
  authorIdsElement.ConsiderAsArray();
  for (std::size_t i = 0; i < authorIdsElement.GetChildrenCount(); ++i) {
    authorIds.push_back(authorIdsElement.GetChild(i).GetStringValue());
  }

  // Compatibility with GD <= 5.0.0-beta101
  if (VersionWrapper::IsOlderOrEqual(
          gdMajorVersion, gdMinorVersion, gdBuildVersion, 0, 4, 0, 98, 0) &&
      !propElement.HasAttribute("useDeprecatedZeroAsDefaultZOrder")) {
    useDeprecatedZeroAsDefaultZOrder = true;
  } else {
    useDeprecatedZeroAsDefaultZOrder =
        propElement.GetBoolAttribute("useDeprecatedZeroAsDefaultZOrder", false);
  }
  // end of compatibility code

  // Compatibility with GD <= 5.0.0-beta101
  if (!propElement.HasAttribute("projectUuid") &&
      !propElement.HasChild("projectUuid")) {
    ResetProjectUuid();
  }
  // end of compatibility code

  extensionProperties.UnserializeFrom(
      propElement.GetChild("extensionProperties"));

  // Compatibility with GD <= 5.0.0-beta98
  // Move AdMob App ID from project property to extension property.
  if (propElement.GetStringAttribute("adMobAppId", "") != "") {
    extensionProperties.SetValue(
        "AdMob",
        "AdMobAppId",
        propElement.GetStringAttribute("adMobAppId", ""));
  }
  // end of compatibility code

  currentPlatform = NULL;
  gd::String currentPlatformName =
      propElement.GetChild("currentPlatform").GetValue().GetString();
  // Compatibility code
  if (VersionWrapper::IsOlderOrEqual(
          gdMajorVersion, gdMajorVersion, gdMinorVersion, 0, 3, 4, 73, 0)) {
    if (currentPlatformName == "Game Develop C++ platform")
      currentPlatformName = "GDevelop C++ platform";
    if (currentPlatformName == "Game Develop JS platform")
      currentPlatformName = "GDevelop JS platform";
  }
  // End of Compatibility code

  const SerializerElement& platformsElement =
      propElement.GetChild("platforms", 0, "Platforms");
  platformsElement.ConsiderAsArrayOf("platform", "Platform");
  for (std::size_t i = 0; i < platformsElement.GetChildrenCount(); ++i) {
    gd::String name = platformsElement.GetChild(i).GetStringAttribute("name");
    // Compatibility code
    if (VersionWrapper::IsOlderOrEqual(
            gdMajorVersion, gdMajorVersion, gdMinorVersion, 0, 3, 4, 73, 0)) {
      if (name == "Game Develop C++ platform") name = "GDevelop C++ platform";
      if (name == "Game Develop JS platform") name = "GDevelop JS platform";
    }
    // End of Compatibility code

    gd::Platform* platform = gd::PlatformManager::Get()->GetPlatform(name);

    if (platform) {
      AddPlatform(*platform);
      if (platform->GetName() == currentPlatformName ||
          currentPlatformName.empty())
        currentPlatform = platform;
    } else {
      std::cout << "Platform \"" << name << "\" is unknown." << std::endl;
    }
  }

  // Compatibility code
  if (platformsElement.GetChildrenCount() == 0) {
    // Compatibility with GD2.x
    platforms.push_back(
        gd::PlatformManager::Get()->GetPlatform("GDevelop C++ platform"));
    currentPlatform = platforms.back();
  }
  // End of Compatibility code

  if (currentPlatform == NULL && !platforms.empty())
    currentPlatform = platforms.back();

  GetObjectGroups().UnserializeFrom(
      element.GetChild("objectsGroups", 0, "ObjectGroups"));
  resourcesManager.UnserializeFrom(
      element.GetChild("resources", 0, "Resources"));
  UnserializeObjectsFrom(*this, element.GetChild("objects", 0, "Objects"));
  GetVariables().UnserializeFrom(element.GetChild("variables", 0, "Variables"));

  scenes.clear();
  const SerializerElement& layoutsElement =
      element.GetChild("layouts", 0, "Scenes");
  layoutsElement.ConsiderAsArrayOf("layout", "Scene");
  for (std::size_t i = 0; i < layoutsElement.GetChildrenCount(); ++i) {
    const SerializerElement& layoutElement = layoutsElement.GetChild(i);

    gd::Layout& layout = InsertNewLayout(
        layoutElement.GetStringAttribute("name", "", "nom"), -1);
    layout.UnserializeFrom(*this, layoutElement);
  }

  externalEvents.clear();
  const SerializerElement& externalEventsElement =
      element.GetChild("externalEvents", 0, "ExternalEvents");
  externalEventsElement.ConsiderAsArrayOf("externalEvents", "ExternalEvents");
  for (std::size_t i = 0; i < externalEventsElement.GetChildrenCount(); ++i) {
    const SerializerElement& externalEventElement =
        externalEventsElement.GetChild(i);

    gd::ExternalEvents& externalEvents = InsertNewExternalEvents(
        externalEventElement.GetStringAttribute("name", "", "Name"),
        GetExternalEventsCount());
    externalEvents.UnserializeFrom(*this, externalEventElement);
  }

  eventsFunctionsExtensions.clear();
  const SerializerElement& eventsFunctionsExtensionsElement =
      element.GetChild("eventsFunctionsExtensions");
  eventsFunctionsExtensionsElement.ConsiderAsArrayOf(
      "eventsFunctionsExtension");
  for (std::size_t i = 0;
       i < eventsFunctionsExtensionsElement.GetChildrenCount();
       ++i) {
    const SerializerElement& eventsFunctionsExtensionElement =
        eventsFunctionsExtensionsElement.GetChild(i);

    gd::EventsFunctionsExtension& newEventsFunctionsExtension =
        InsertNewEventsFunctionsExtension("",
                                          GetEventsFunctionsExtensionsCount());
    newEventsFunctionsExtension.UnserializeFrom(
        *this, eventsFunctionsExtensionElement);
  }

  externalLayouts.clear();
  const SerializerElement& externalLayoutsElement =
      element.GetChild("externalLayouts", 0, "ExternalLayouts");
  externalLayoutsElement.ConsiderAsArrayOf("externalLayout", "ExternalLayout");
  for (std::size_t i = 0; i < externalLayoutsElement.GetChildrenCount(); ++i) {
    const SerializerElement& externalLayoutElement =
        externalLayoutsElement.GetChild(i);

    gd::ExternalLayout& newExternalLayout =
        InsertNewExternalLayout("", GetExternalLayoutsCount());
    newExternalLayout.UnserializeFrom(externalLayoutElement);
  }

  externalSourceFiles.clear();
  const SerializerElement& externalSourceFilesElement =
      element.GetChild("externalSourceFiles", 0, "ExternalSourceFiles");
  externalSourceFilesElement.ConsiderAsArrayOf("sourceFile", "SourceFile");
  for (std::size_t i = 0; i < externalSourceFilesElement.GetChildrenCount();
       ++i) {
    const SerializerElement& sourceFileElement =
        externalSourceFilesElement.GetChild(i);

    gd::SourceFile& newSourceFile = InsertNewSourceFile("", "");
    newSourceFile.UnserializeFrom(sourceFileElement);
  }
}

void Project::SerializeTo(SerializerElement& element) const {
  SerializerElement& versionElement = element.AddChild("gdVersion");
  versionElement.SetAttribute("major", gd::VersionWrapper::Major());
  versionElement.SetAttribute("minor", gd::VersionWrapper::Minor());
  versionElement.SetAttribute("build", gd::VersionWrapper::Build());
  versionElement.SetAttribute("revision", gd::VersionWrapper::Revision());

  SerializerElement& propElement = element.AddChild("properties");
  propElement.AddChild("name").SetValue(GetName());
  propElement.SetAttribute("version", GetVersion());
  propElement.AddChild("author").SetValue(GetAuthor());
  propElement.AddChild("windowWidth").SetValue(GetGameResolutionWidth());
  propElement.AddChild("windowHeight").SetValue(GetGameResolutionHeight());
  propElement.AddChild("latestCompilationDirectory")
      .SetValue(GetLastCompilationDirectory());
  propElement.AddChild("maxFPS").SetValue(GetMaximumFPS());
  propElement.AddChild("minFPS").SetValue(GetMinimumFPS());
  propElement.AddChild("verticalSync")
      .SetValue(IsVerticalSynchronizationEnabledByDefault());
  propElement.SetAttribute("scaleMode", scaleMode);
  propElement.SetAttribute("pixelsRounding", pixelsRounding);
  propElement.SetAttribute("adaptGameResolutionAtRuntime",
                           adaptGameResolutionAtRuntime);
  propElement.SetAttribute("sizeOnStartupMode", sizeOnStartupMode);
  propElement.SetAttribute("projectUuid", projectUuid);
  propElement.SetAttribute("folderProject", folderProject);
  propElement.SetAttribute("packageName", packageName);
  propElement.SetAttribute("orientation", orientation);
  platformSpecificAssets.SerializeTo(
      propElement.AddChild("platformSpecificAssets"));
  loadingScreen.SerializeTo(propElement.AddChild("loadingScreen"));
  propElement.SetAttribute("useExternalSourceFiles", useExternalSourceFiles);

  auto& authorIdsElement = propElement.AddChild("authorIds");
  authorIdsElement.ConsiderAsArray();
  for (const auto& authorId : authorIds) {
    authorIdsElement.AddChild("").SetStringValue(authorId);
  }

  // Compatibility with GD <= 5.0.0-beta101
  if (useDeprecatedZeroAsDefaultZOrder) {
    propElement.SetAttribute("useDeprecatedZeroAsDefaultZOrder", true);
  }
  // end of compatibility code

  extensionProperties.SerializeTo(propElement.AddChild("extensionProperties"));

  SerializerElement& platformsElement = propElement.AddChild("platforms");
  platformsElement.ConsiderAsArrayOf("platform");
  for (std::size_t i = 0; i < platforms.size(); ++i) {
    if (platforms[i] == NULL) {
      std::cout << "ERROR: The project has a platform which is NULL.";
      continue;
    }

    platformsElement.AddChild("platform")
        .SetAttribute("name", platforms[i]->GetName());
  }
  if (currentPlatform != NULL)
    propElement.AddChild("currentPlatform")
        .SetValue(currentPlatform->GetName());
  else
    std::cout << "ERROR: The project current platform is NULL.";

  resourcesManager.SerializeTo(element.AddChild("resources"));
  SerializeObjectsTo(element.AddChild("objects"));
  GetObjectGroups().SerializeTo(element.AddChild("objectsGroups"));
  GetVariables().SerializeTo(element.AddChild("variables"));

  element.SetAttribute("firstLayout", firstLayout);
  gd::SerializerElement& layoutsElement = element.AddChild("layouts");
  layoutsElement.ConsiderAsArrayOf("layout");
  for (std::size_t i = 0; i < GetLayoutsCount(); i++)
    GetLayout(i).SerializeTo(layoutsElement.AddChild("layout"));

  SerializerElement& externalEventsElement = element.AddChild("externalEvents");
  externalEventsElement.ConsiderAsArrayOf("externalEvents");
  for (std::size_t i = 0; i < GetExternalEventsCount(); ++i)
    GetExternalEvents(i).SerializeTo(
        externalEventsElement.AddChild("externalEvents"));

  SerializerElement& eventsFunctionsExtensionsElement =
      element.AddChild("eventsFunctionsExtensions");
  eventsFunctionsExtensionsElement.ConsiderAsArrayOf(
      "eventsFunctionsExtension");
  for (std::size_t i = 0; i < eventsFunctionsExtensions.size(); ++i)
    eventsFunctionsExtensions[i]->SerializeTo(
        eventsFunctionsExtensionsElement.AddChild("eventsFunctionsExtension"));

  SerializerElement& externalLayoutsElement =
      element.AddChild("externalLayouts");
  externalLayoutsElement.ConsiderAsArrayOf("externalLayout");
  for (std::size_t i = 0; i < externalLayouts.size(); ++i)
    externalLayouts[i]->SerializeTo(
        externalLayoutsElement.AddChild("externalLayout"));

  SerializerElement& externalSourceFilesElement =
      element.AddChild("externalSourceFiles");
  externalSourceFilesElement.ConsiderAsArrayOf("sourceFile");
  for (std::size_t i = 0; i < externalSourceFiles.size(); ++i)
    externalSourceFiles[i]->SerializeTo(
        externalSourceFilesElement.AddChild("sourceFile"));
}

bool Project::ValidateName(const gd::String& name) {
  if (name.empty()) return false;

  if (isdigit(name[0])) return false;

  gd::String allowedCharacters =
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
  return !(name.find_first_not_of(allowedCharacters) != gd::String::npos);
}

void Project::ExposeResources(gd::ArbitraryResourceWorker& worker) {
  // See also gd::WholeProjectRefactorer::ExposeProjectEvents for a method that
  // traverse the whole project (this time for events) and ExposeProjectEffects
  // (this time for effects). Ideally, this method could be moved outside of
  // gd::Project.

  // Add project resources
  worker.ExposeResources(&GetResourcesManager());
  platformSpecificAssets.ExposeResources(worker);

  // Add layouts resources
  for (std::size_t s = 0; s < GetLayoutsCount(); s++) {
    for (std::size_t j = 0; j < GetLayout(s).GetObjectsCount();
         ++j)  // Add objects resources
      GetLayout(s).GetObject(j).ExposeResources(worker);

    LaunchResourceWorkerOnEvents(*this, GetLayout(s).GetEvents(), worker);
  }
  // Add external events resources
  for (std::size_t s = 0; s < GetExternalEventsCount(); s++) {
    LaunchResourceWorkerOnEvents(
        *this, GetExternalEvents(s).GetEvents(), worker);
  }
  // Add events functions extensions resources
  for (std::size_t e = 0; e < GetEventsFunctionsExtensionsCount(); e++) {
    auto& eventsFunctionsExtension = GetEventsFunctionsExtension(e);
    for (auto&& eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
      LaunchResourceWorkerOnEvents(*this, eventsFunction->GetEvents(), worker);
    }
  }

  // Add global objects resources
  for (std::size_t j = 0; j < GetObjectsCount(); ++j) {
    GetObject(j).ExposeResources(worker);
  }

  // Add loading screen background image if present
  if (loadingScreen.GetBackgroundImageResourceName() != "")
    worker.ExposeImage(loadingScreen.GetBackgroundImageResourceName());
}

bool Project::HasSourceFile(gd::String name, gd::String language) const {
  vector<std::unique_ptr<SourceFile> >::const_iterator sourceFile =
      find_if(externalSourceFiles.begin(),
              externalSourceFiles.end(),
              bind2nd(gd::ExternalSourceFileHasName(), name));

  if (sourceFile == externalSourceFiles.end()) return false;

  return language.empty() || (*sourceFile)->GetLanguage() == language;
}

gd::SourceFile& Project::GetSourceFile(const gd::String& name) {
  return *(*find_if(externalSourceFiles.begin(),
                    externalSourceFiles.end(),
                    bind2nd(gd::ExternalSourceFileHasName(), name)));
}

const gd::SourceFile& Project::GetSourceFile(const gd::String& name) const {
  return *(*find_if(externalSourceFiles.begin(),
                    externalSourceFiles.end(),
                    bind2nd(gd::ExternalSourceFileHasName(), name)));
}

void Project::RemoveSourceFile(const gd::String& name) {
  std::vector<std::unique_ptr<gd::SourceFile> >::iterator sourceFile =
      find_if(externalSourceFiles.begin(),
              externalSourceFiles.end(),
              bind2nd(gd::ExternalSourceFileHasName(), name));
  if (sourceFile == externalSourceFiles.end()) return;

  externalSourceFiles.erase(sourceFile);
}

gd::SourceFile& Project::InsertNewSourceFile(const gd::String& name,
                                             const gd::String& language,
                                             std::size_t position) {
  if (HasSourceFile(name, language)) return GetSourceFile(name);

  gd::SourceFile& newlyInsertedSourceFile = *(
      *(externalSourceFiles.emplace(position < externalSourceFiles.size()
                                        ? externalSourceFiles.begin() + position
                                        : externalSourceFiles.end(),
                                    new SourceFile())));
  newlyInsertedSourceFile.SetLanguage(language);
  newlyInsertedSourceFile.SetFileName(name);

  return newlyInsertedSourceFile;
}

Project::Project(const Project& other) { Init(other); }

Project& Project::operator=(const Project& other) {
  if (this != &other) Init(other);

  return *this;
}

void Project::Init(const gd::Project& game) {
  name = game.name;
  version = game.version;
  windowWidth = game.windowWidth;
  windowHeight = game.windowHeight;
  maxFPS = game.maxFPS;
  minFPS = game.minFPS;
  verticalSync = game.verticalSync;
  scaleMode = game.scaleMode;
  pixelsRounding = game.pixelsRounding;
  adaptGameResolutionAtRuntime = game.adaptGameResolutionAtRuntime;
  sizeOnStartupMode = game.sizeOnStartupMode;
  projectUuid = game.projectUuid;
  useDeprecatedZeroAsDefaultZOrder = game.useDeprecatedZeroAsDefaultZOrder;

  author = game.author;
  authorIds = game.authorIds;
  packageName = game.packageName;
  orientation = game.orientation;
  folderProject = game.folderProject;
  latestCompilationDirectory = game.latestCompilationDirectory;
  platformSpecificAssets = game.platformSpecificAssets;
  loadingScreen = game.loadingScreen;
  objectGroups = game.objectGroups;

  extensionProperties = game.extensionProperties;

  gdMajorVersion = game.gdMajorVersion;
  gdMinorVersion = game.gdMinorVersion;
  gdBuildVersion = game.gdBuildVersion;

  currentPlatform = game.currentPlatform;
  platforms = game.platforms;

  resourcesManager = game.resourcesManager;

  initialObjects = gd::Clone(game.initialObjects);

  scenes = gd::Clone(game.scenes);

  externalEvents = gd::Clone(game.externalEvents);

  externalLayouts = gd::Clone(game.externalLayouts);
  eventsFunctionsExtensions = gd::Clone(game.eventsFunctionsExtensions);

  useExternalSourceFiles = game.useExternalSourceFiles;

  externalSourceFiles = gd::Clone(game.externalSourceFiles);

  variables = game.GetVariables();

  projectFile = game.GetProjectFile();
}

}  // namespace gd
