#include <frostbite2D/audio/music.h>
#include <frostbite2D/audio/audio_system.h>
#include <frostbite2D/resource/asset.h>
#include <frostbite2D/resource/sound_pack_archive.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>
#include <algorithm>

namespace frostbite2D {

Ptr<Music> Music::loadFromFile(const std::string& path) {
  auto& asset = Asset::get();
  std::string fullPath = asset.resolvePath(path);
  
  Mix_Music* music = Mix_LoadMUS(fullPath.c_str());
  if (!music) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load music from file %s: %s", fullPath.c_str(), Mix_GetError());
    return nullptr;
  }

  return Ptr<Music>(new Music(music, path));
}

Ptr<Music> Music::loadFromMemory(const uint8* data, size_t size) {
  std::vector<uint8> dataCopy(data, data + size);
  
  SDL_RWops* rw = SDL_RWFromConstMem(dataCopy.data(), static_cast<int>(dataCopy.size()));
  if (!rw) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create RWops from memory: %s", SDL_GetError());
    return nullptr;
  }

  Mix_Music* music = Mix_LoadMUS_RW(rw, 1);
  if (!music) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load music from memory: %s", Mix_GetError());
    return nullptr;
  }

  return Ptr<Music>(new Music(music, std::move(dataCopy)));
}

Ptr<Music> Music::loadFromNpk(const std::string& audioPath) {
  SoundPackArchive& archive = SoundPackArchive::get();
  
  if (!archive.isOpen()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SoundPackArchive not initialized");
    return nullptr;
  }

  auto audioOpt = archive.getAudio(audioPath);
  if (!audioOpt) {
    std::string defaultPath = archive.getDefaultAudioPath();
    if (!defaultPath.empty()) {
      audioOpt = archive.getAudio(defaultPath);
    }
    if (!audioOpt) {
      SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Audio not found in NPK: %s", audioPath.c_str());
      return nullptr;
    }
  }

  auto dataOpt = archive.getAudioData(*audioOpt);
  if (!dataOpt || dataOpt->empty()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get audio data for: %s", audioPath.c_str());
    return nullptr;
  }

  return loadFromMemory(dataOpt->data(), dataOpt->size());
}

Music::Music(Mix_Music* music, const std::string& path)
  : music_(music), path_(path) {
}

Music::Music(Mix_Music* music, std::vector<uint8> data, const std::string& path)
  : music_(music), path_(path), data_(std::move(data)) {
}

Music::~Music() {
  if (music_) {
    Mix_FreeMusic(music_);
    music_ = nullptr;
  }
}

bool Music::play(int loops) {
  if (!music_) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Music is null");
    return false;
  }

  if (!AudioSystem::get().isInitialized()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Audio system not initialized");
    return false;
  }

  int vol = static_cast<int>(volume_ * AudioSystem::get().getMusicVolume() * AudioSystem::get().getMasterVolume() * MIX_MAX_VOLUME);
  Mix_VolumeMusic(vol);

  if (Mix_PlayMusic(music_, loops) == 0) {
    return true;
  } else {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to play music: %s", Mix_GetError());
    return false;
  }
}

bool Music::fadeIn(int ms, int loops) {
  if (!music_) {
    return false;
  }

  if (!AudioSystem::get().isInitialized()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Audio system not initialized");
    return false;
  }

  int vol = static_cast<int>(volume_ * AudioSystem::get().getMusicVolume() * AudioSystem::get().getMasterVolume() * MIX_MAX_VOLUME);
  Mix_VolumeMusic(vol);

  return Mix_FadeInMusic(music_, loops, ms) == 0;
}

void Music::pause() {
  if (!music_) {
    return;
  }
  Mix_PauseMusic();
}

void Music::resume() {
  if (!music_) {
    return;
  }
  Mix_ResumeMusic();
}

void Music::stop() {
  if (!music_) {
    return;
  }
  Mix_HaltMusic();
}

bool Music::fadeOut(int ms) {
  if (!music_) {
    return false;
  }
  return Mix_FadeOutMusic(ms) == 0;
}

void Music::setVolume(float volume) {
  volume_ = std::clamp(volume, 0.0f, 1.0f);

  if (AudioSystem::get().isInitialized() && isPlaying()) {
    int vol = static_cast<int>(volume_ * AudioSystem::get().getMusicVolume() * AudioSystem::get().getMasterVolume() * MIX_MAX_VOLUME);
    Mix_VolumeMusic(vol);
  }
}

float Music::getVolume() const {
  return volume_;
}

bool Music::isPlaying() const {
  return Mix_PlayingMusic() == 1;
}

bool Music::isPaused() const {
  return Mix_PausedMusic() == 1;
}

}
