#pragma once

#include "ccl/tools/JSON.h"
#include "xtr/doc/FileVersions.hpp"

#include "CArchiveAdapter.hpp"

using JSON = nlohmann::ordered_json;

inline void to_json(JSON& object, const CString str) {
  object = xtr::mfc::ToSTL(str);
}

inline void from_json(const JSON& object, CString& str) {
  const auto result = object.get<std::string>();
  str = xtr::mfc::ToMFC(result);
}

namespace xtr::io {

//! MFC serializable abstract base
template<typename Version>
class MFCSerializable {
  inline static const std::string jsonDocumentName = "document.json";

public:
  void Serialize(CArchive& ar) {
    CArchiveAdapter adapter{ ar };
    ASSERT(!ar.IsStoring());
    if (!LegacyLoad(adapter)) {
      AfxThrowArchiveException(CArchiveException::genericException);
    }
  }

  bool SaveJSON(const std::u8string& path) {
    auto json = ExtractData();
    json["version"] = static_cast<uint16_t>(Version::Latest());
    json["versionInfo"] = Version::LastestInfo();
    return WriteToFile(json.dump(4), path);
  }

  bool LoadJSON(const std::u8string& path) {
    const auto data = ReadFromFile(path);
    if (!data.has_value()) {
      return false;
    }
    const Version versionID = static_cast<Version::VersionID>(data->at("version").get<uint16_t>());
    return Version::Latest() >= versionID  && LoadData(data.value());
  }

private:
  virtual JSON ExtractData() const = 0;
  virtual bool LoadData(const JSON& object) = 0;
  virtual bool LegacyLoad(CArchiveAdapter& ar) = 0;

  bool WriteToFile(std::string_view data, const std::u8string& path) {
    try {
      std::ofstream outputFile(std::filesystem::path(path), std::ios::binary);
      Poco::Zip::Compress archive(outputFile, true);
      Poco::MemoryInputStream inputStream(data.data(), size(data));
      archive.addFile(inputStream, Poco::DateTime(), jsonDocumentName);
      archive.close();
      outputFile.close();
    } catch (const Poco::Exception& /*error*/) {
      return false;
    }
    return true;
  }

  std::optional<JSON> ReadFromFile(const std::u8string& path) {
    namespace fs = std::filesystem;
    auto tmpDirectory = fs::temp_directory_path() / "exteor" / fs::path(path).filename().replace_extension("");
    std::ifstream inputFile(fs::path(path), std::ios::binary);
    if (!inputFile) {
      return std::nullopt;
    }
    try {
      const auto u8folder = tmpDirectory.u8string();
      const auto outputFolder = Poco::Path(std::string{ begin(u8folder), end(u8folder) });
      Poco::Zip::Decompress(inputFile, outputFolder).decompressAllFiles();
      inputFile.close();
    } catch (const Poco::Exception& /*error*/) {
      return std::nullopt;
    }

    std::ifstream jsonFile(tmpDirectory / jsonDocumentName);
    if (!jsonFile) {
      fs::remove_all(tmpDirectory);
      return std::nullopt;
    } else {
      auto jsonData = JSON::parse(jsonFile);
      jsonFile.close();
      fs::remove_all(tmpDirectory);
      return jsonData;
    }
  }
};

} // namespace xtr::io