/*
 * Copyright (C) 2006-2025 Christopho, Solarus - http://www.solarus-games.org
 *
 * Solarus is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Solarus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */
#include "solarus/audio/Music.h"
#include "solarus/audio/MusicSystem.h"
#include "solarus/audio/decoders/ItDecoder.h"
#include "solarus/audio/decoders/OggDecoder.h"
#include "solarus/audio/decoders/SpcDecoder.h"
#include "solarus/core/QuestFiles.h"
#include <lua.hpp>
#include <algorithm>

namespace Solarus {

bool MusicSystem::initialized = false;
float MusicSystem::global_volume = 1.0f;

/**
 * \brief Initializes the music system.
 */
void MusicSystem::initialize() {
  global_volume = 1.0;
  initialized = true;

  // Ensure AudioContext is initialized (needed for editor compatibility)
  if (!AudioContext::is_initialized()) {
    AudioContext::initialize();
  }
}

/**
 * \brief Exits the music system.
 */
void MusicSystem::quit() {
  if (is_initialized()) {
    global_volume = 1.0;
  }
}

/**
 * \brief Returns whether the music system is initialized.
 * \return \c true if the music system is initialized.
 */
bool MusicSystem::is_initialized() {
  return initialized;
}

/**
 * \brief Returns the current volume of musics.
 * \return the volume (0 to 100)
 */
int MusicSystem::get_global_volume() {
  return static_cast<int>(global_volume * 100.0 + 0.5);
}

/**
 * \brief Sets the volume of musics.
 * \param volume the new volume (0 to 100)
 */
void MusicSystem::set_global_volume(int volume) {
  global_volume = std::min(100, std::max(0, volume)) / 100.0;
  auto context = AudioContext::get_context(AudioContext::MAIN);
  if (context != nullptr) {
    context->notify_music_global_volume_changed();
  }
}

/**
 * \brief Legacy function called by the editor to play a music.
 * \param music_id Id of the music to play (file name without extension).
 * \param loop Whether the music should loop when reaching its end (if there is an end).
 * \deprecated
 */
void MusicSystem::play(
  const std::string& music_id,
  bool loop
) {
  if (is_initialized()) {
    auto audio_context = AudioContext::get_context(AudioContext::MAIN);
    if (audio_context == nullptr) {
      return;
    }
    auto current_music = audio_context->get_common_music();
    bool has_music_changed = current_music == nullptr || current_music->get_id() != music_id;
    if (music_id == Music::none) {
      audio_context->stop_common_music();
    }
    else if (music_id != Music::unchanged && has_music_changed) {
      auto music = Music::create(music_id, loop);
      if (music != nullptr) {
        audio_context->play_common_music(music);
      }
    }
  }
}

/**
 * \brief Legacy function called by the editor to stop a music
 * \deprecated
 */
void MusicSystem::stop_playing() {
  auto audio_context = AudioContext::get_context(AudioContext::MAIN);
  if (audio_context == nullptr) {
    return;
  }
  audio_context->stop_common_music();
}

/**
 * \brief Tries to find a music file from a music id.
 * \param music_id Id of the music to find (file name without
 * directory or extension). Cannot be Music::none or Music::unchanged.
 * \param file_name Resulting file name with its extension
 * (empty string if not found).
 * \param format Resulting music format.
 */
void MusicSystem::find_music_file(const std::string& music_id, std::string& file_name, Music::Format& format) {

  file_name = "";
  format = Music::FORMAT_OGG;

  std::string file_name_start = std::string("musics/" + music_id);
  if (QuestFiles::data_file_exists(file_name_start + ".ogg")) {
    format = Music::FORMAT_OGG;
    file_name = file_name_start + ".ogg";
  }
  else if (QuestFiles::data_file_exists(file_name_start + ".it")) {
    format = Music::FORMAT_IT;
    file_name = file_name_start + ".it";
  }
  else if (QuestFiles::data_file_exists(file_name_start + ".spc")) {
    format = Music::FORMAT_SPC;
    file_name = file_name_start + ".spc";
  }
}

/**
 * \brief Returns whether a music exists.
 * \param music_id Id of the music to test. Music::none and Music::unchanged
 * are also considered valid.
 * \return true If this music exists.
 */
bool MusicSystem::exists(const std::string& music_id) {

  if (music_id == Music::none || music_id == Music::unchanged) {
    return true;
  }

  std::string file_name;
  Music::Format format;
  find_music_file(music_id, file_name, format);

  return !file_name.empty();
}

}
