//Folder to contain all of the project related files
#pragma once

#include <map>
#include <memory>
#include <blizzard-archive-library/include/CASCArchive.hpp>
#include <blizzard-archive-library/include/ClientFile.hpp>
#include <blizzard-archive-library/include/Exception.hpp>
#include <blizzard-database-library/include/BlizzardDatabase.h>
// #include <noggit/application/NoggitApplication.hpp>
#include <noggit/application/Configuration/NoggitApplicationConfiguration.hpp>
#include <noggit/ui/windows/downloadFileDialog/DownloadFileDialog.h>
#include <noggit/TextureManager.h>
#include <external/tsl/robin_map.h>
#include <noggit/World.h>
#include <noggit/Log.h>
#include <QJsonDocument>
#include <QMessageBox>
#include <QJsonObject>
#include <QFile>
#include <filesystem>
#include <fstream>
#include <vector>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QFile>
#include <QString>
#include <QObject>
#include <QString>
#include <thread>
#include <chrono>
#include <cassert>
#include <glm/vec3.hpp>

#include "ApplicationProjectReader.h"
#include "ApplicationProjectWriter.h"

namespace Noggit::Project
{
  enum class ProjectVersion
  {
    VANILLA,
    BC,
    WOTLK,
    CATA,
    PANDARIA,
    WOD,
    LEGION,
    BFA,
    SL
  };

  struct ClientVersionFactory
  {
    static ProjectVersion mapToEnumVersion(std::string const& projectVersion)
    {
      if (projectVersion == "Wrath Of The Lich King")
        return ProjectVersion::WOTLK;
      if (projectVersion == "Shadowlands")
        return ProjectVersion::SL;

      assert(false);
    }

    static std::string MapToStringVersion(ProjectVersion const& projectVersion)
    {
      if (projectVersion == ProjectVersion::WOTLK)
        return std::string("Wrath Of The Lich King");
      if (projectVersion == ProjectVersion::SL)
        return std::string("Shadowlands");

      assert(false);
    }
  };

  struct NoggitProjectBookmarkMap
  {
    int map_id;
    std::string name;
    glm::vec3 position;
    float camera_yaw;
    float camera_pitch;
  };

  struct NoggitProjectPinnedMap
  {
    int MapId;
    std::string MapName;
  };

  struct NoggitExtraMapData
  {
  public:
      //Mists Heightmapping
      // Valid for every map that doesn't override its specific data
      tsl::robin_map< std::string, texture_heightmapping_data > TextureHeightData_Global;
      // MapID,TileX,TileY, SMTextureParams for this specific ADT, fallback to global otherwise.
      tsl::robin_map<int, 
            tsl::robin_map< int, tsl::robin_map<int , tsl::robin_map<std::string, texture_heightmapping_data> >>> TextureHeightData_ADT;
      
      void SetTextureHeightData_Global(const std::string& texture, texture_heightmapping_data data, World* worldToUpdate = nullptr); 
      void SetTextureHeightDataForADT(int mapID, const TileIndex& ti, const std::string& texture, texture_heightmapping_data data, World* worldToUpdate = nullptr);

      const texture_heightmapping_data GetTextureHeightDataForADT(int mapID, const TileIndex& ti, const std::string& texture) const;
  };
  
  struct NoggitProjectObjectPalette
  {
      int MapId;
      std::vector<std::string> Filepaths;
  };

  struct NoggitProjectTexturePalette
  {
      int MapId;
      std::vector<std::string> Filepaths;
  };

  struct NoggitProjectSelectionGroups
  {
      int MapId;
      // Might let the user name them later if they get some list UI
      std::vector<std::vector<unsigned int>> SelectionGroups;
  };

  class NoggitProject
  {
    std::shared_ptr<ApplicationProjectWriter> _projectWriter;
  public:
    std::string ProjectPath;
    std::string ProjectName;
    std::string ClientPath;
    ProjectVersion projectVersion;
    std::vector<NoggitProjectPinnedMap> PinnedMaps;
    std::vector<NoggitProjectBookmarkMap> Bookmarks;
    std::shared_ptr<BlizzardDatabaseLib::BlizzardDatabase> ClientDatabase;
    std::shared_ptr<BlizzardArchive::ClientData> ClientData;
    std::vector<NoggitProjectObjectPalette> ObjectPalettes;
    std::vector<NoggitProjectTexturePalette> TexturePalettes;
    std::vector<NoggitProjectSelectionGroups> ObjectSelectionGroups;

    NoggitExtraMapData ExtraMapData;
    NoggitProject()
    {
      PinnedMaps = std::vector<NoggitProjectPinnedMap>();
      Bookmarks = std::vector<NoggitProjectBookmarkMap>();
      ObjectPalettes = std::vector<NoggitProjectObjectPalette>();
      TexturePalettes = std::vector<NoggitProjectTexturePalette>();
      ObjectSelectionGroups = std::vector<NoggitProjectSelectionGroups>();

      _projectWriter = std::make_shared<ApplicationProjectWriter>();
    }

    void createBookmark(const NoggitProjectBookmarkMap& bookmark)
    {
      Bookmarks.push_back(bookmark);

      _projectWriter->saveProject(this, std::filesystem::path(ProjectPath));
    }

    void deleteBookmark()
    {

    }

    void pinMap(int map_id, const std::string& map_name)
    {
      auto pinnedMap = NoggitProjectPinnedMap();
      pinnedMap.MapName = map_name;
      pinnedMap.MapId = map_id;

      auto pinnedMapFound = std::find_if(std::begin(PinnedMaps), std::end(PinnedMaps),
                                         [&](Project::NoggitProjectPinnedMap pinnedMap)
                                         {
                                           return pinnedMap.MapId == map_id;
                                         });

      if (pinnedMapFound != std::end(PinnedMaps))
        return;

      PinnedMaps.push_back(pinnedMap);

      _projectWriter->saveProject(this, std::filesystem::path(ProjectPath));
    }

    void unpinMap(int mapId)
    {
      PinnedMaps.erase(std::remove_if(PinnedMaps.begin(), PinnedMaps.end(),
                                      [=](NoggitProjectPinnedMap pinnedMap)
                                      {
                                        return pinnedMap.MapId == mapId;
                                      }),
                       PinnedMaps.end());

      _projectWriter->saveProject(this, std::filesystem::path(ProjectPath));
    }

    void saveTexturePalette(const NoggitProjectTexturePalette& new_texture_palette)
    {
        TexturePalettes.erase(std::remove_if(TexturePalettes.begin(), TexturePalettes.end(),
          [=](NoggitProjectTexturePalette texture_palette)
          {
              return texture_palette.MapId == new_texture_palette.MapId;
          }),
            TexturePalettes.end());

      TexturePalettes.push_back(new_texture_palette);

      _projectWriter->savePalettes(this, std::filesystem::path(ProjectPath));
    }

    void saveObjectPalette(const NoggitProjectObjectPalette& new_object_palette)
    {
        ObjectPalettes.erase(std::remove_if(ObjectPalettes.begin(), ObjectPalettes.end(),
            [=](NoggitProjectObjectPalette obj_palette)
            {
                return obj_palette.MapId == new_object_palette.MapId;
            }),
            ObjectPalettes.end());

        ObjectPalettes.push_back(new_object_palette);

        _projectWriter->savePalettes(this, std::filesystem::path(ProjectPath));
    }

    void saveObjectSelectionGroups(const NoggitProjectSelectionGroups& new_selection_groups)
    {
        ObjectSelectionGroups.erase(std::remove_if(ObjectSelectionGroups.begin(), ObjectSelectionGroups.end(),
            [=](NoggitProjectSelectionGroups proj_selection_group)
            {
                return proj_selection_group.MapId == new_selection_groups.MapId;
            }),
            ObjectSelectionGroups.end());

        ObjectSelectionGroups.push_back(new_selection_groups);

        _projectWriter->saveObjectSelectionGroups(this, std::filesystem::path(ProjectPath));
    }
  };

  class ApplicationProject
  {
    std::shared_ptr<NoggitProject> _active_project;
    std::shared_ptr<Application::NoggitApplicationConfiguration> _configuration;
  public:
    ApplicationProject(std::shared_ptr<Application::NoggitApplicationConfiguration> configuration)
    {
      _active_project = nullptr;
      _configuration = configuration;
    }

    void createProject(std::filesystem::path const& project_path, std::filesystem::path const& client_path,
                       std::string const& client_version, std::string const& project_name)
    {
      if (!std::filesystem::exists(project_path))
        std::filesystem::create_directory(project_path);

      auto project = NoggitProject();
      project.ProjectName = project_name;
      project.projectVersion = ClientVersionFactory::mapToEnumVersion(client_version);
      project.ClientPath = client_path.generic_string();
      project.ProjectPath = project_path.generic_string();

      auto project_writer = ApplicationProjectWriter();
      project_writer.saveProject(&project, project_path);


    }

    std::shared_ptr<NoggitProject> loadProject(std::filesystem::path const& project_path)
    {
      ApplicationProjectReader project_reader{};
      auto project = project_reader.readProject(project_path);

      if (!project.has_value())
      {
        LogError << "loadProject() failed, Project is null" << std::endl;
        return {};
      }
      else
      {
          Log << "loadProject(): Loading Project Data" << std::endl;
      }


      project_reader.readPalettes(&project.value());
      project_reader.readObjectSelectionGroups(&project.value());

      std::string dbd_file_directory = _configuration->ApplicationDatabaseDefinitionsPath;

      BlizzardDatabaseLib::Structures::Build client_build("3.3.5.12340");
      auto client_archive_version = BlizzardArchive::ClientVersion::WOTLK;
      auto client_archive_locale = BlizzardArchive::Locale::AUTO;
      if (project->projectVersion == ProjectVersion::SL)
      {
        client_archive_version = BlizzardArchive::ClientVersion::SL;
        client_build = BlizzardDatabaseLib::Structures::Build("9.1.0.39584");
        client_archive_locale = BlizzardArchive::Locale::enUS;
      }

      else if (project->projectVersion == ProjectVersion::WOTLK)
      {
        client_archive_version = BlizzardArchive::ClientVersion::WOTLK;
        client_build = BlizzardDatabaseLib::Structures::Build("3.3.5.12340");
        client_archive_locale = BlizzardArchive::Locale::AUTO;
      }

      else
      {
          LogError << "Unsupported project version" << std::endl;
          return {};
      }

      project->ClientDatabase = std::make_shared<BlizzardDatabaseLib::BlizzardDatabase>(dbd_file_directory, client_build);

      Log << "Loading Client Path : " << project->ClientPath << std::endl;

      try
      {
        project->ClientData = std::make_shared<BlizzardArchive::ClientData>(
            project->ClientPath, client_archive_version, client_archive_locale, project_path.generic_string());
      }
      catch (BlizzardArchive::Exceptions::Locale::LocaleNotFoundError& e)
      {
        LogError << e.what() << std::endl;
        QMessageBox::critical(nullptr, "Error", e.what());
        return {};
      }
      catch (BlizzardArchive::Exceptions::Locale::IncorrectLocaleModeError& e)
      {
        LogError << e.what() << std::endl;
        QMessageBox::critical(nullptr, "Error", e.what());
        return {};
      }
      catch (BlizzardArchive::Exceptions::Archive::ArchiveOpenError& e)
      {
        LogError << e.what() << std::endl;
        QMessageBox::critical(nullptr, "Error", e.what());
        return {};
      }
      catch (...)
      {
          LogError << "Failed loading Client data. Unhandled exception." << std::endl;
          return {};
      }

      if (!project->ClientData)
      {
          LogError << "Failed loading Client data." << std::endl;
          return {};
      }

      // Log << "Client Version: " << static_cast<int>(project->ClientData->version()) << std::endl;

      Log << "Client Locale: " << project->ClientData->locale_name() << std::endl;

      for (auto const loaded_achive : *project->ClientData->loadedArchives())
      {
        Log << "Loaded client Archive: " << loaded_achive->path() << std::endl;
      }
      
      // QSettings settings;
      // bool modern_features = settings.value("modern_features", false).toBool();
      bool modern_features = _configuration->modern_features;
	    if (modern_features)
	    {
		    Log << "Modern Features Enabled" << std::endl;
            loadExtraData(project.value());
	    }
	    else
	    {
		    Log << "Modern Features Disabled" << std::endl;
	    }
        return std::make_shared<NoggitProject>(project.value());
      }

    void loadExtraData(NoggitProject& project);
  };
}
