#pragma once

#include <frostbite2D/resource/binary_file_stream_reader.h>
#include <frostbite2D/types/type_alias.h>

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

namespace frostbite2D {

/**
 * @brief PVF ???????
 */
struct PvfFileInfo {
  size_t offset = 0;  ///< ????????????
  uint32 crc32 = 0;   ///< CRC32 ???
  size_t length = 0;  ///< ????????
  bool decoded = false;  ///< ?????
};

/**
 * @brief ?????????
 */
struct RawData {
  std::unique_ptr<char[]> data;  ///< ??????????????????
  size_t size;                   ///< ????????
};

/**
 * @brief PVF ??????
 *
 * ??????? PVF ???????????
 * ??????????????????????????
 */
class PvfArchive {
public:
  static PvfArchive& get();

  PvfArchive(const PvfArchive&) = delete;
  PvfArchive& operator=(const PvfArchive&) = delete;
  PvfArchive(PvfArchive&&) = delete;
  PvfArchive& operator=(PvfArchive&&) = delete;

  bool open(const std::string& filePath = "Script.pvf");
  void close();
  bool isOpen() const;

  void init();
  void initHeader();
  void initBinStringTable();
  void initLoadStrings();

  bool hasFile(const std::string& path) const;
  std::optional<PvfFileInfo> getFileInfo(const std::string& path) const;
  std::vector<std::string> listFiles() const;

  std::optional<std::string> getFileContent(const std::string& path);
  std::optional<std::vector<uint8>> getFileBytes(const std::string& path);
  std::optional<RawData> getFileRawData(const std::string& path);

  std::optional<std::string> getBinString(int key) const;
  std::optional<std::string> getLoadString(const std::string& type,
                                           const std::string& key) const;
  bool hasBinString(int key) const;
  bool hasLoadString(const std::string& type, const std::string& key) const;

  std::string normalizePath(const std::string& path) const;
  std::string resolvePath(const std::string& baseDir,
                          const std::string& path) const;

private:
  PvfArchive() = default;
  ~PvfArchive() = default;

  std::vector<std::string> splitString(const std::string& str,
                                       const std::string& delimiter) const;
  bool decodeFile(const std::string& normalizedPath, PvfFileInfo& info);
  void clearInitData();
  std::optional<std::vector<uint8>> readArchiveBytes(size_t absoluteOffset,
                                                     size_t length);
  std::optional<std::vector<uint8>> readDecodedFileBytes(
      const PvfFileInfo& info);
  static void crcDecodeBuffer(std::vector<uint8>& data, uint32 crc32);

  BinaryFileStreamReader reader_;
  size_t dataStartPos_ = 0;
  std::map<std::string, PvfFileInfo> fileInfo_;
  std::map<int, std::string> binStringTable_;
  std::map<std::string, std::map<std::string, std::string>> loadStrings_;
  std::map<std::string, std::vector<uint8>> decodedFileCache_;
};

} // namespace frostbite2D
