#include "audio/MapAudioController.h"
#include <SDL2/SDL.h>
#include <algorithm>
#include <unordered_map>

namespace frostbite2D {

namespace {

std::string NormalizeAudioPath(const std::string& path) {
  std::string normalized = path;
  std::replace(normalized.begin(), normalized.end(), '\\', '/');
  std::transform(normalized.begin(), normalized.end(), normalized.begin(),
                 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
  return normalized;
}

} // namespace

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

void MapAudioController::ApplyMapAudio(const MapAudioState& state) {
  ApplyBgmTrack(state.bgmTrack);
  ApplyAmbientLoops(state.ambientLoops);
}

void MapAudioController::ClearMapAudio() {
  if (currentBgmMusic_) {
    currentBgmMusic_->stop();
    currentBgmMusic_.Reset();
  }
  currentBgmTrack_.reset();

  for (auto& [_, loop] : activeAmbientLoops_) {
    StopAmbientLoop(loop);
  }
  activeAmbientLoops_.clear();
}

void MapAudioController::ApplyBgmTrack(
    const std::optional<MapAudioTrack>& bgmTrack) {
  if (!bgmTrack || bgmTrack->path.empty()) {
    if (currentBgmMusic_) {
      currentBgmMusic_->stop();
      currentBgmMusic_.Reset();
    }
    currentBgmTrack_.reset();
    return;
  }

  const std::string desiredKey = NormalizeTrackKey(*bgmTrack);
  const bool sameTrack =
      currentBgmTrack_ && NormalizeTrackKey(*currentBgmTrack_) == desiredKey;
  if (sameTrack && Music::isPathPlaying(bgmTrack->path)) {
    return;
  }

  RefPtr<Music> music = Music::loadFromPath(bgmTrack->path);
  if (!music) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "MapAudioController: failed to load bgm %s",
                bgmTrack->path.c_str());
    return;
  }

  if (!music->play(-1)) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "MapAudioController: failed to play bgm %s",
                bgmTrack->path.c_str());
    return;
  }

  currentBgmMusic_ = music;
  currentBgmTrack_ = *bgmTrack;
}

void MapAudioController::ApplyAmbientLoops(
    const std::vector<MapAudioTrack>& ambientLoops) {
  std::unordered_map<std::string, MapAudioTrack> desiredLoops;
  desiredLoops.reserve(ambientLoops.size());
  for (const auto& loop : ambientLoops) {
    if (loop.path.empty()) {
      continue;
    }
    desiredLoops.emplace(NormalizeTrackKey(loop), loop);
  }

  for (auto it = activeAmbientLoops_.begin(); it != activeAmbientLoops_.end();) {
    if (desiredLoops.find(it->first) != desiredLoops.end()) {
      ++it;
      continue;
    }

    StopAmbientLoop(it->second);
    it = activeAmbientLoops_.erase(it);
  }

  for (const auto& [key, track] : desiredLoops) {
    auto activeIt = activeAmbientLoops_.find(key);
    if (activeIt != activeAmbientLoops_.end()) {
      ActiveAmbientLoop& loop = activeIt->second;
      if (NormalizeAudioPath(loop.track.path) == NormalizeAudioPath(track.path) &&
          Sound::isPlaying(loop.channel)) {
        continue;
      }

      StopAmbientLoop(loop);
      loop.track = track;
      if (!loop.sound ||
          NormalizeAudioPath(loop.sound->getPath()) !=
              NormalizeAudioPath(track.path)) {
        loop.sound = Sound::loadFromPath(track.path);
      }
      if (!loop.sound) {
        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                    "MapAudioController: failed to load ambient %s",
                    track.path.c_str());
        continue;
      }

      int channel = loop.sound->playLoop();
      if (channel == -1) {
        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                    "MapAudioController: failed to play ambient %s",
                    track.path.c_str());
        continue;
      }

      loop.channel = channel;
      continue;
    }

    ActiveAmbientLoop loop;
    loop.track = track;
    loop.sound = Sound::loadFromPath(track.path);
    if (!loop.sound) {
      SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                  "MapAudioController: failed to load ambient %s",
                  track.path.c_str());
      continue;
    }

    loop.channel = loop.sound->playLoop();
    if (loop.channel == -1) {
      SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                  "MapAudioController: failed to play ambient %s",
                  track.path.c_str());
      continue;
    }

    activeAmbientLoops_.emplace(key, std::move(loop));
  }
}

void MapAudioController::StopAmbientLoop(ActiveAmbientLoop& loop) {
  if (loop.channel >= 0) {
    Sound::stop(loop.channel);
    loop.channel = -1;
  }
}

std::string MapAudioController::NormalizeTrackKey(const MapAudioTrack& track) {
  if (!track.id.empty()) {
    return NormalizeAudioPath(track.id);
  }
  return NormalizeAudioPath(track.path);
}

} // namespace frostbite2D
