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

#ifndef XENIA_KERNEL_XAM_XDBF_SPA_INFO_H_
#define XENIA_KERNEL_XAM_XDBF_SPA_INFO_H_

#include "xenia/kernel/xam/xdbf/xdbf_io.h"

#include <map>
#include <numeric>
#include <optional>
#include <set>
#include <string>
#include <vector>

#include "xenia/base/memory.h"
#include "xenia/xbox.h"

namespace xe {
namespace kernel {
namespace xam {

// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.h
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/XEX/SPA.cpp

enum class SpaSection : uint16_t {
  kMetadata = 0x0001,
  kImage = 0x0002,
  kStringTable = 0x0003,
};

enum class TitleType : uint32_t {
  kSystem = 0,
  kFull = 1,
  kDemo = 2,
  kDownload = 3,
  kUnknown = 4,
  kApp = 5
};

enum class TitleFlags {
  kAlwaysIncludeInProfile = 1,
  kNeverIncludeInProfile = 2,
};

constexpr uint32_t kViewTypeMask = 0xF;

enum class ViewType : uint32_t {
  kLeaderboard = 0,
  kContextByProperty = 1,
  kContextByContext = 2
};

enum class StatsViewFlags : uint16_t {
  kArbitrated = 16,
  kHidden = 32,
  kTeamView = 64,
  kOnlineOnly = 128,
};

enum class AggregationType : uint16_t {
  kLast = 0x8001,
  kMax = 0x800B,
  kSum = 0x8003,
  kMin = 0x8009,
};

enum class ViewFieldEntryFlags : uint32_t {
  kHidden = 1,
};

enum class ViewFieldType : uint8_t { kContextField, kPropertyField };

constexpr inline std::string GetAggregationTypeName(
    const uint32_t aggregation_type) {
  switch (static_cast<AggregationType>(aggregation_type)) {
    case AggregationType::kLast:
      return "Last";
    case AggregationType::kMax:
      return "Max";
    case AggregationType::kSum:
      return "Sum";
    case AggregationType::kMin:
      return "Min";
    default:
      return "";
  }
}

constexpr inline std::string GetViewTypeName(const uint32_t view_type) {
  switch (static_cast<ViewType>(view_type)) {
    case ViewType::kLeaderboard:
      return "Leaderboard";
    case ViewType::kContextByProperty:
      return "Context by Property";
    case ViewType::kContextByContext:
      return "Context by Context";
    default:
      return "";
  }
}
constexpr inline std::string AttributeIdToName(const uint16_t id) {
  switch (id) {
    case std::numeric_limits<uint16_t>::max():
      return "Rank";
    case 65534:
      return "Rating";
    case 65533:
      return "Gamertag";
    case 65530:
      return "Attachment Size";
    default:
      return "";
  }
}

constexpr inline std::string GetViewTypeName(const ViewType view_type) {
  return GetViewTypeName(static_cast<uint32_t>(view_type));
}

constexpr inline ViewType GetViewType(const uint32_t flags) {
  return static_cast<ViewType>(flags & kViewTypeMask);
}

constexpr inline bool IsArbitrated(const uint32_t flags) {
  return flags & static_cast<uint32_t>(StatsViewFlags::kArbitrated);
}

constexpr inline bool IsHidden(const uint32_t flags) {
  return flags & static_cast<uint32_t>(StatsViewFlags::kHidden);
}

constexpr inline bool IsTeamView(const uint32_t flags) {
  return flags & static_cast<uint32_t>(StatsViewFlags::kTeamView);
}

constexpr inline bool IsOnlineOnly(const uint32_t flags) {
  return flags & static_cast<uint32_t>(StatsViewFlags::kOnlineOnly);
}

constexpr inline uint32_t GetSkillLeaderboardId(uint32_t game_type,
                                                uint32_t game_mode) {
  return (0xFFF00000 | (game_type == 1 ? 0xF0000 : 0xE0000)) |
         (game_mode & 0xFFFF);
}

constexpr inline bool IsLeaderboardIdSkill(uint32_t id) {
  return (id & 0x2000000) != 0;
}

#pragma pack(push, 1)
struct PropertyBagEntry {
  xe::be<uint32_t> contexts_count;
  xe::be<uint32_t> properties_count;
};

struct PropertyBag {
  std::set<xe::be<uint32_t>> contexts;
  std::set<xe::be<uint32_t>> properties;
};

struct TitleHeaderData {
  xe::be<uint32_t> title_id;
  xe::be<TitleType> title_type;
  xe::be<uint16_t> major;
  xe::be<uint16_t> minor;
  xe::be<uint16_t> build;
  xe::be<uint16_t> revision;
  xe::be<uint32_t> flags;
  xe::be<uint32_t> padding_1;
  xe::be<uint32_t> padding_2;
  xe::be<uint32_t> padding_3;
};
static_assert_size(TitleHeaderData, 32);

struct StatsViewTableEntry {
  xe::be<uint32_t> id;
  xe::be<uint32_t> flags;  // StatsViewFlags
  xe::be<uint16_t> shared_index;
  xe::be<uint16_t> string_id;
  xe::be<uint32_t> unused;
};
static_assert_size(StatsViewTableEntry, 0x10);

struct ViewFieldEntry {
  xe::be<uint32_t> size;
  xe::be<uint32_t> property_id;
  xe::be<uint32_t> flags;  // ViewFieldEntryFlags
  xe::be<uint16_t> attribute_id;
  xe::be<uint16_t> string_id;
  xe::be<uint16_t> aggregation_type;  // AggregationType
  xe::be<uint8_t> ordinal;
  xe::be<uint8_t> field_type;
  xe::be<uint32_t> format_type;
  xe::be<uint32_t> unused_1;
  xe::be<uint32_t> unused_2;
};
static_assert_size(ViewFieldEntry, 0x20);

struct SharedViewMetaTableEntry {
  xe::be<uint16_t> column_count;
  xe::be<uint16_t> row_count;
  xe::be<uint32_t> unused_1;
  xe::be<uint32_t> unused_2;
};
static_assert_size(SharedViewMetaTableEntry, 0xC);

struct SharedView {
  std::vector<ViewFieldEntry> column_entries;
  std::vector<ViewFieldEntry> row_entries;
  PropertyBag property_bag;
};

struct ViewTable {
  StatsViewTableEntry view_entry;
  SharedView shared_view;
};

struct PresenceTableEntry {
  PropertyBag property_bag;
  std::vector<PropertyBag> presence_modes;
};

struct AchievementTableEntry {
  xe::be<uint16_t> id;
  xe::be<uint16_t> label_id;
  xe::be<uint16_t> description_id;
  xe::be<uint16_t> unachieved_id;
  xe::be<uint32_t> image_id;
  xe::be<uint16_t> gamerscore;
  xe::be<uint16_t> unkE;
  xe::be<uint32_t> flags;
  xe::be<uint32_t> unk14;
  xe::be<uint32_t> unk18;
  xe::be<uint32_t> unk1C;
  xe::be<uint32_t> unk20;
};
static_assert_size(AchievementTableEntry, 0x24);
#pragma pack(pop)

class SpaInfo : public XdbfFile {
 public:
  SpaInfo(const std::span<uint8_t> buffer);
  ~SpaInfo() = default;

  void Load();

  const uint8_t* ReadXLast(uint32_t& compressed_size,
                           uint32_t& decompressed_size);

  // Checks if provided language exist, if not returns default title language.
  XLanguage GetExistingLanguage(XLanguage language_to_check) const;

  // The game icon image, if found.
  std::span<const uint8_t> title_icon() const;

  std::span<const uint8_t> GetIcon(uint64_t id) const;

  // The game's default language.
  XLanguage default_language() const;

  bool is_system_app() const;
  bool is_demo() const;
  bool include_in_profile() const;

  uint32_t title_id() const;
  // The game's title in its default language.
  std::string title_name() const;

  std::string title_name(XLanguage language) const;

  uint32_t achievement_count() const {
    return static_cast<uint32_t>(achievements_.size());
  }

  const AchievementTableEntry* GetAchievement(uint32_t id);

  std::vector<const AchievementTableEntry*> GetAchievements() const {
    return achievements_;
  }

  std::vector<const XdbfContextTableEntry*> GetContexts() const {
    return contexts_;
  }

  std::vector<const XdbfPropertyTableEntry*> GetProperties() const {
    return properties_;
  }

  const std::vector<ViewTable>* GetStatsViews() const { return &stats_views_; }

  const PresenceTableEntry* GetPresence() const { return &presence_; }

  const PropertyBag* GetMatchCollection() const { return &matchmaking_; }

  const XdbfContextTableEntry* GetContext(uint32_t id);
  const XdbfPropertyTableEntry* GetProperty(uint32_t id);
  const std::optional<ViewTable> GetStatsView(uint32_t id);
  const std::optional<PropertyBag> GetPresenceMode(
      uint32_t context_value) const;

  uint32_t total_gamerscore() const {
    return std::accumulate(achievements_.cbegin(), achievements_.cend(), 0,
                           [](uint32_t sum, const auto& entry) {
                             return sum + entry->gamerscore;
                           });
  }

  friend bool operator<(const SpaInfo& first, const SpaInfo& second);
  friend bool operator<=(const SpaInfo& first, const SpaInfo& second);
  friend bool operator==(const SpaInfo& first, const SpaInfo& second);

  std::string GetStringTableEntry(XLanguage language, uint16_t string_id) const;

 private:
  // Base info. There should be comparator between different SpaInfos and entry
  // with newer data should replace old one. Such situation can happen when game
  // adds achievements and so on with DLC.
  TitleHeaderData title_header_;

  // SPA is Read-Only so it's reasonable to make it readonly.
  std::vector<const AchievementTableEntry*> achievements_;
  std::vector<const XdbfContextTableEntry*> contexts_;
  std::vector<const XdbfPropertyTableEntry*> properties_;
  std::vector<ViewTable> stats_views_;
  PresenceTableEntry presence_;
  PropertyBag matchmaking_;

  using XdbfLanguageStrings = std::map<uint16_t, std::string>;

  std::map<XLanguage, XdbfLanguageStrings> language_strings_;

  void LoadTitleInformation();
  void LoadAchievements();

  void LoadLanguageData();

  void LoadContexts();
  void LoadProperties();

  void LoadStatsViews();
  void LoadPresenceModes();
  void LoadMatchmaking();

  template <typename T>
  static T GetSpaEntry(std::vector<T>& container, uint32_t id);
};

}  // namespace xam
}  // namespace kernel
}  // namespace xe

#endif  // XENIA_KERNEL_XAM_XDBF_SPA_INFO_H_
