#include <frostbite2D/resource/pvf_archive.h>
#include <algorithm>
#include <cctype>
#include <SDL.h>

namespace frostbite2D {

PvfArchive& PvfArchive::get() {
  static PvfArchive instance;
  return instance;
}

bool PvfArchive::open(const std::string& filePath) {
  close();

  if (!reader_.open(filePath)) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: 无法打开文件: %s", filePath.c_str());
    return false;
  }

  return true;
}

void PvfArchive::close() {
  reader_.close();
  dataStartPos_ = 0;
  fileInfo_.clear();
  binStringTable_.clear();
  loadStrings_.clear();
}

bool PvfArchive::isOpen() const {
  return reader_.isOpen();
}

void PvfArchive::init() {
  if (!isOpen()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: 请先打开文件再初始化");
    return;
  }

  initHeader();
  initBinStringTable();
  initLoadStrings();
}

void PvfArchive::initHeader() {
  if (!isOpen()) {
    return;
  }

  reader_.seek(0);

  int32 uuidLength = reader_.readInt32();
  std::string uuid = reader_.readString(uuidLength);
  (void)uuid;

  int32 version = reader_.readInt32();
  (void)version;

  int32 alignedIndexHeaderSize = reader_.readInt32();
  int32 indexHeaderCrc = reader_.readInt32();
  int32 indexSize = reader_.readInt32();

  size_t firstPos = reader_.tell();
  reader_.crcDecode(alignedIndexHeaderSize, indexHeaderCrc);

  dataStartPos_ = alignedIndexHeaderSize + 56;
  size_t currPos = 0;

  for (int32 i = 0; i < indexSize; ++i) {
    reader_.seek(firstPos + currPos);

    int32 fileNumber = reader_.readInt32();
    (void)fileNumber;

    int32 filePathLength = reader_.readInt32();
    std::string fileName = normalizePath(reader_.readString(filePathLength));

    int32 fileLength = reader_.readInt32();
    int32 crc32 = reader_.readInt32();
    int32 relativeOffset = reader_.readInt32();

    if (fileLength > 0) {
      int32 realFileLength = (fileLength + 3) & 0xFFFFFFFC;
      PvfFileInfo info;
      info.offset = relativeOffset;
      info.crc32 = static_cast<uint32>(crc32);
      info.length = realFileLength;
      info.decoded = false;
      fileInfo_[fileName] = info;
    }

    currPos += 20;
    currPos += filePathLength;
  }
}

void PvfArchive::initBinStringTable() {
  std::string tablePath = "stringtable.bin";
  auto infoOpt = getFileInfo(tablePath);

  if (!infoOpt.has_value()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: stringtable.bin 文件不存在");
    return;
  }

  PvfFileInfo info = infoOpt.value();
  reader_.seek(dataStartPos_ + info.offset);
  reader_.crcDecode(info.length, info.crc32);
  reader_.seek(dataStartPos_ + info.offset);

  size_t fileHeaderPos = reader_.tell();
  int32 count = reader_.readInt32();

  for (int32 i = 0; i < count; ++i) {
    reader_.seek(fileHeaderPos + i * 4 + 4);
    int32 startPos = reader_.readInt32();
    int32 endPos = reader_.readInt32();
    int32 len = endPos - startPos;

    reader_.seek(fileHeaderPos + startPos + 4);
    std::string str = reader_.readString(len);
    binStringTable_[i] = str;
  }
}

void PvfArchive::initLoadStrings() {
  std::string lstPath = "n_string.lst";
  auto infoOpt = getFileInfo(lstPath);

  if (!infoOpt.has_value()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: n_string.lst 文件不存在");
    return;
  }

  PvfFileInfo info = infoOpt.value();
  reader_.seek(dataStartPos_ + info.offset);
  reader_.crcDecode(info.length, info.crc32);
  reader_.seek(dataStartPos_ + info.offset);

  size_t fileHeaderPos = reader_.tell();
  int16 flag = reader_.readInt16();
  (void)flag;

  size_t i = 2;
  while (i < info.length) {
    if ((info.length - i) >= 10) {
      reader_.seek(fileHeaderPos + i + 6);
      int32 findKey = reader_.readInt32();

      auto binStrOpt = getBinString(findKey);
      if (!binStrOpt.has_value()) {
        i += 10;
        continue;
      }

      std::string key = binStrOpt.value();
      size_t slashPos = key.find('/');
      std::string type = (slashPos != std::string::npos) ? normalizePath(key.substr(0, slashPos)) : "";

      if (!key.empty()) {
        auto fileInfoOpt = getFileInfo(key);
        if (fileInfoOpt.has_value()) {
          auto contentOpt = getFileContent(key);
          if (contentOpt.has_value()) {
            std::string content = contentOpt.value();
            std::vector<std::string> lines = splitString(content, "\n");

            for (const auto& line : lines) {
              size_t gtPos = line.find('>');
              if (gtPos != std::string::npos && gtPos + 1 < line.length()) {
                std::string strKey = line.substr(0, gtPos);
                std::string strValue = line.substr(gtPos + 1);
                loadStrings_[type][strKey] = strValue;
              }
            }
          }
        }
      }
    } else {
      break;
    }
    i += 10;
  }
}

bool PvfArchive::hasFile(const std::string& path) const {
  return fileInfo_.find(normalizePath(path)) != fileInfo_.end();
}

std::optional<PvfFileInfo> PvfArchive::getFileInfo(const std::string& path) const {
  std::string normalizedPath = normalizePath(path);
  auto it = fileInfo_.find(normalizedPath);
  if (it != fileInfo_.end()) {
    return it->second;
  }
  return std::nullopt;
}

std::vector<std::string> PvfArchive::listFiles() const {
  std::vector<std::string> files;
  files.reserve(fileInfo_.size());
  for (const auto& pair : fileInfo_) {
    files.push_back(pair.first);
  }
  return files;
}

std::optional<std::string> PvfArchive::getFileContent(const std::string& path) {
  std::string normalizedPath = normalizePath(path);
  auto it = fileInfo_.find(normalizedPath);
  if (it == fileInfo_.end()) {
    return std::nullopt;
  }

  PvfFileInfo& info = it->second;
  if (!decodeFile(info)) {
    return std::nullopt;
  }

  reader_.seek(dataStartPos_ + info.offset);
  std::vector<uint8> bytes = reader_.readBytes(info.length);
  return std::string(reinterpret_cast<const char*>(bytes.data()), bytes.size());
}

std::optional<std::vector<uint8>> PvfArchive::getFileBytes(const std::string& path) {
  std::string normalizedPath = normalizePath(path);
  auto it = fileInfo_.find(normalizedPath);
  if (it == fileInfo_.end()) {
    return std::nullopt;
  }

  PvfFileInfo& info = it->second;
  if (!decodeFile(info)) {
    return std::nullopt;
  }

  reader_.seek(dataStartPos_ + info.offset);
  return reader_.readBytes(info.length);
}

std::optional<RawData> PvfArchive::getFileRawData(const std::string& path) {
  std::string normalizedPath = normalizePath(path);
  auto it = fileInfo_.find(normalizedPath);
  if (it == fileInfo_.end()) {
    return std::nullopt;
  }

  PvfFileInfo& info = it->second;
  if (!decodeFile(info)) {
    return std::nullopt;
  }

  reader_.seek(dataStartPos_ + info.offset);

  RawData result;
  result.size = info.length;
  result.data = std::make_unique<char[]>(info.length);
  reader_.read(result.data.get(), info.length);

  return result;
}

std::optional<std::string> PvfArchive::getBinString(int key) const {
  auto it = binStringTable_.find(key);
  if (it != binStringTable_.end()) {
    return it->second;
  }
  return std::nullopt;
}

std::optional<std::string> PvfArchive::getLoadString(const std::string& type, const std::string& key) const {
  std::string normalizedType = normalizePath(type);
  auto typeIt = loadStrings_.find(normalizedType);
  if (typeIt != loadStrings_.end()) {
    auto keyIt = typeIt->second.find(key);
    if (keyIt != typeIt->second.end()) {
      return keyIt->second;
    }
  }
  return std::nullopt;
}

bool PvfArchive::hasBinString(int key) const {
  return binStringTable_.find(key) != binStringTable_.end();
}

bool PvfArchive::hasLoadString(const std::string& type, const std::string& key) const {
  std::string normalizedType = normalizePath(type);
  auto typeIt = loadStrings_.find(normalizedType);
  if (typeIt != loadStrings_.end()) {
    return typeIt->second.find(key) != typeIt->second.end();
  }
  return false;
}

std::string PvfArchive::normalizePath(const std::string& path) const {
  std::string result = path;
  std::transform(result.begin(), result.end(), result.begin(),
                 [](unsigned char c) { return std::tolower(c); });
  return result;
}

std::vector<std::string> PvfArchive::splitString(const std::string& str, const std::string& delimiter) const {
  std::vector<std::string> tokens;
  size_t pos = 0;
  size_t found;

  while ((found = str.find(delimiter, pos)) != std::string::npos) {
    tokens.push_back(str.substr(pos, found - pos));
    pos = found + delimiter.length();
  }

  if (pos < str.length()) {
    tokens.push_back(str.substr(pos));
  }

  return tokens;
}

bool PvfArchive::decodeFile(PvfFileInfo& info) {
  if (info.decoded) {
    return true;
  }

  reader_.seek(dataStartPos_ + info.offset);
  reader_.crcDecode(info.length, info.crc32);
  info.decoded = true;

  return true;
}

} // namespace frostbite2D
