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

namespace frostbite2D {

const uint8 SoundPackArchive::NPK_KEY[256] = {
    112, 117, 99, 104, 105, 107, 111, 110, 64, 110, 101, 111, 112, 108, 101, 32,
    100, 117, 110, 103, 101, 111, 110, 32, 97, 110, 100, 32, 102, 105, 103, 104,
    116, 101, 114, 32, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78,
    70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70,
    68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68,
    78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78,
    70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70,
    68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 68,
    78, 70, 68, 78, 70, 68, 78, 70, 68, 78, 70, 0
};

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

void SoundPackArchive::setSoundPackDirectory(const std::string& dir) {
  soundPackDirectory_ = dir;
}

const std::string& SoundPackArchive::getSoundPackDirectory() const {
  return soundPackDirectory_;
}

void SoundPackArchive::init() {
  if (initialized_) {
    close();
  }

  scanNpkFiles();
  initialized_ = true;
}

void SoundPackArchive::close() {
  audioIndex_.clear();
  audioCache_.clear();
  lruList_.clear();
  currentCacheSize_ = 0;
  initialized_ = false;
}

bool SoundPackArchive::isOpen() const {
  return initialized_;
}

std::string SoundPackArchive::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;
}

void SoundPackArchive::scanNpkFiles() {
  Asset& asset = Asset::get();
  std::string npkDir = asset.resolvePath(soundPackDirectory_);
  if (!asset.isDirectory(npkDir)) {
    SDL_Log("Sound NPK directory not found: %s", npkDir.c_str());
    return;
  }

  std::vector<std::string> files = asset.listFilesWithExtension(npkDir, ".npk");
  SDL_Log("Scanning %d sound NPK files...", static_cast<int>(files.size()));
  for (const auto &file : files) {
    parseNpkFile(file);
  }
}

bool SoundPackArchive::parseNpkFile(const std::string& npkPath) {
  Asset& asset = Asset::get();
  BinaryReader reader(npkPath);

  if (!reader.isOpen()) {
    SDL_Log("Failed to open sound NPK file: %s", npkPath.c_str());
    return false;
  }

  std::string npkFileName = asset.getFileName(npkPath);
  std::string header = reader.readNullTerminatedString();

  if (header.find("NeoplePack_Bill") == std::string::npos) {
    return false;
  }

  int32 audioCount = reader.readInt32();

  for (int32 i = 0; i < audioCount; ++i) {
    int32 offset = reader.readInt32();
    int32 length = reader.readInt32();
    std::string audioPath = readNpkInfoString(reader);

    AudioRef audio;
    audio.path = normalizePath(audioPath);
    audio.npkFile = npkFileName;
    audio.offset = static_cast<uint32>(offset);
    audio.size = static_cast<uint32>(length);
    audio.loaded = false;

    audioIndex_[audio.path] = audio;
  }

  return true;
}

bool SoundPackArchive::hasAudio(const std::string& path) const {
  return audioIndex_.find(normalizePath(path)) != audioIndex_.end();
}

std::optional<AudioRef> SoundPackArchive::getAudio(const std::string& path) {
  auto it = audioIndex_.find(normalizePath(path));
  if (it == audioIndex_.end()) {
    return std::nullopt;
  }
  return it->second;
}

std::vector<std::string> SoundPackArchive::listAudios() const {
  std::vector<std::string> result;
  result.reserve(audioIndex_.size());
  for (const auto& pair : audioIndex_) {
    result.push_back(pair.first);
  }
  return result;
}

std::optional<std::vector<uint8>> SoundPackArchive::getAudioData(const AudioRef& audio) {
  std::string audioPath = normalizePath(audio.path);

  auto cacheIt = audioCache_.find(audioPath);
  if (cacheIt == audioCache_.end()) {
    auto it = audioIndex_.find(audioPath);
    if (it == audioIndex_.end()) {
      return std::nullopt;
    }

    if (!loadAudioData(it->second)) {
      return std::nullopt;
    }
    cacheIt = audioCache_.find(audioPath);
    if (cacheIt == audioCache_.end()) {
      return std::nullopt;
    }
  }

  updateCacheUsage(audioPath);
  return cacheIt->second.data;
}

bool SoundPackArchive::loadAudioData(AudioRef& audio) {
  Asset& asset = Asset::get();
  std::string npkPath = asset.combinePath(soundPackDirectory_, audio.npkFile);
  npkPath = asset.resolvePath(npkPath);

  BinaryReader reader(npkPath);
  if (!reader.isOpen()) {
    SDL_Log("Failed to open NPK for audio: %s", npkPath.c_str());
    return false;
  }

  reader.seek(audio.offset);
  std::vector<uint8> audioData = reader.readBytes(audio.size);

  if (audioData.size() != audio.size) {
    SDL_Log("Failed to read complete audio data");
    return false;
  }

  evictCacheIfNeeded(audioData.size());

  CachedAudioData cachedData;
  cachedData.data = std::move(audioData);
  cachedData.memoryUsage = cachedData.data.size();
  cachedData.lastUseTime = SDL_GetTicks();

  audioCache_[audio.path] = std::move(cachedData);
  currentCacheSize_ += audioCache_[audio.path].memoryUsage;
  lruList_.push_front(audio.path);

  audio.loaded = true;
  audioIndex_[audio.path] = audio;

  return true;
}

void SoundPackArchive::setCacheSize(size_t maxBytes) {
  maxCacheSize_ = maxBytes;
  evictCacheIfNeeded(0);
}

void SoundPackArchive::clearCache() {
  audioCache_.clear();
  lruList_.clear();
  currentCacheSize_ = 0;
}

size_t SoundPackArchive::getCacheUsage() const {
  return currentCacheSize_;
}

void SoundPackArchive::evictCacheIfNeeded(size_t requiredSize) {
  while (currentCacheSize_ + requiredSize > maxCacheSize_ && !lruList_.empty()) {
    std::string oldest = lruList_.back();
    lruList_.pop_back();

    auto it = audioCache_.find(oldest);
    if (it != audioCache_.end()) {
      currentCacheSize_ -= it->second.memoryUsage;
      audioCache_.erase(it);
    }
  }
}

void SoundPackArchive::updateCacheUsage(const std::string& audioPath) {
  auto it = std::find(lruList_.begin(), lruList_.end(), audioPath);
  if (it != lruList_.end()) {
    lruList_.erase(it);
  }
  lruList_.push_front(audioPath);

  auto cacheIt = audioCache_.find(audioPath);
  if (cacheIt != audioCache_.end()) {
    cacheIt->second.lastUseTime = SDL_GetTicks();
  }
}

std::string SoundPackArchive::readNpkInfoString(BinaryReader& reader) {
  if (reader.eof()) {
    return "";
  }

  std::vector<uint8> encrypted = reader.readBytes(256);
  if (encrypted.size() < 256) {
    return "";
  }

  std::vector<char> decrypted(256);
  for (int i = 0; i < 256; ++i) {
    decrypted[i] = static_cast<char>(encrypted[i] ^ NPK_KEY[i]);
  }

  return std::string(decrypted.data());
}

void SoundPackArchive::setDefaultAudio(const std::string& audioPath) {
  defaultAudioPath_ = normalizePath(audioPath);
}

const std::string& SoundPackArchive::getDefaultAudioPath() const {
  return defaultAudioPath_;
}

}
