/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2022 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#ifndef XENIA_PATCH_DB_H_
#define XENIA_PATCH_DB_H_

#include <cstring>
#include <map>
#include <optional>
#include <regex>

#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wabsolute-value"
#endif
#include "third_party/tomlplusplus/toml.hpp"
#if defined(__clang__)
#pragma clang diagnostic pop
#endif

namespace xe {
namespace patcher {

struct PatchDataValue {
  const size_t alloc_size;
  std::vector<uint8_t> patch_data;

  template <typename T>
  PatchDataValue(const size_t size, const T value) : alloc_size(size) {
    patch_data.resize(alloc_size);
    std::memcpy(patch_data.data(), &value, alloc_size);
  };

  PatchDataValue(const std::vector<uint8_t> value) : alloc_size(value.size()) {
    patch_data.resize(alloc_size);
    std::memcpy(patch_data.data(), value.data(), alloc_size);
  };

  PatchDataValue(const std::string value) : alloc_size(value.size()) {
    patch_data.resize(alloc_size);
    std::memcpy(patch_data.data(), value.c_str(), alloc_size);
  };

  PatchDataValue(const std::u16string value) : alloc_size(value.size() * 2) {
    patch_data.resize(alloc_size);
    std::memcpy(patch_data.data(), value.c_str(), alloc_size);
  };
};

struct PatchDataEntry {
  const uint32_t address;
  const PatchDataValue data;

  PatchDataEntry(const uint32_t memory_address, const PatchDataValue patch_data)
      : address(memory_address), data(patch_data) {};
};

struct PatchInfoEntry {
  uint32_t id;
  std::string patch_name;
  std::string patch_desc;
  std::string patch_author;
  std::vector<PatchDataEntry> patch_data;
  bool is_enabled;
};

struct PatchFileEntry {
  uint32_t title_id;
  std::string title_name;
  std::vector<uint64_t> hashes;
  std::vector<PatchInfoEntry> patch_info;
};

enum class PatchDataType {
  kBE8,
  kBE16,
  kBE32,
  kBE64,
  kF32,
  kF64,
  kString,
  kU16String,
  kByteArray
};

struct PatchData {
  uint8_t size;
  PatchDataType type;

  PatchData(uint8_t size_, PatchDataType type_) : size(size_), type(type_) {};
};

class PatchDB {
 public:
  PatchDB(const std::filesystem::path patches_root);
  ~PatchDB();

  void LoadPatches();

  PatchFileEntry ReadPatchFile(const std::filesystem::path& file_path) const;

  std::vector<PatchFileEntry> GetTitlePatches(
      const uint32_t title_id, const std::optional<uint64_t> hash);
  std::vector<PatchFileEntry>& GetAllPatches() { return loaded_patches_; }

 private:
  void ReadHashes(PatchFileEntry& patch_entry,
                  const toml::node* patch_toml_fields) const;
  void ReadPatchHeader(PatchInfoEntry& patch_info,
                       const toml::table* patch_fields) const;
  bool ReadPatchData(std::vector<PatchDataEntry>& patch_data,
                     const std::pair<std::string, PatchData> data_type,
                     const toml::table* patch_fields) const;

  inline static const std::regex patch_filename_regex_ =
      std::regex("^[A-Fa-f0-9]{8}.*\\.patch\\.toml$");

  const std::map<std::string, PatchData> patch_data_types_size_ = {
      {"string", PatchData(0, PatchDataType::kString)},
      {"u16string", PatchData(0, PatchDataType::kU16String)},
      {"array", PatchData(0, PatchDataType::kByteArray)},
      {"f64", PatchData(sizeof(uint64_t), PatchDataType::kF64)},
      {"f32", PatchData(sizeof(uint32_t), PatchDataType::kF32)},
      {"be64", PatchData(sizeof(uint64_t), PatchDataType::kBE64)},
      {"be32", PatchData(sizeof(uint32_t), PatchDataType::kBE32)},
      {"be16", PatchData(sizeof(uint16_t), PatchDataType::kBE16)},
      {"be8", PatchData(sizeof(uint8_t), PatchDataType::kBE8)}};

  std::vector<PatchFileEntry> loaded_patches_;
  std::filesystem::path patches_root_;
};
}  // namespace patcher
}  // namespace xe

#endif  // XENIA_PATCH_LOADER_H_
