/*
 * Copyright (C) 2006-2025 Metallizer, 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/ALWrapper.h"
#include "solarus/core/Debug.h"
#include <sstream>

namespace Solarus {

/**
 * EFX and effect procedure pointers
 */
static LPALGENEFFECTS alGenEffects;
static LPALDELETEEFFECTS alDeleteEffects;
static LPALEFFECTI alEffecti;
static LPALEFFECTF alEffectf;
static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;

/**
 * EFX filter procedure pointers
 */
static LPALGENFILTERS alGenFilters;
static LPALFILTERI alFilteri;
static LPALFILTERF alFilterf;

/**
 * Init the EFX effect system by retrieving procedure pointers
 */
void ALWrapper::init_efx_system() {
  alGenEffects = (LPALGENEFFECTS)alGetProcAddress("alGenEffects");
  alDeleteEffects = (LPALDELETEEFFECTS)alGetProcAddress("alDeleteEffects");
  alEffecti = (LPALEFFECTI)alGetProcAddress("alEffecti");
  alEffectf = (LPALEFFECTF)alGetProcAddress("alEffectf");
  alGenAuxiliaryEffectSlots = (LPALGENAUXILIARYEFFECTSLOTS)alGetProcAddress("alGenAuxiliaryEffectSlots");
  alAuxiliaryEffectSloti = (LPALAUXILIARYEFFECTSLOTI)alGetProcAddress("alAuxiliaryEffectSloti");

  alGenFilters = (LPALGENFILTERS)alGetProcAddress("alGenFilters");
  alFilteri    = (LPALFILTERI)alGetProcAddress("alFilteri");
  alFilterf    = (LPALFILTERF)alGetProcAddress("alFilterf");
}

/**
 * Clean all previous errors in the context
 */
void ALWrapper::clean_errors() {
  alGetError();
}

/**
 * Check for errors and print it in the debug standard output
 * \param message The error message to display
 * \return The AL error
 */
ALenum ALWrapper::check_for_errors(const std::string& message) {
  ALenum error = alGetError();
  if (error != AL_NO_ERROR) {
    std::ostringstream oss;
    oss << message << ": " << std::hex << error;
    Debug::error(oss.str());
  }
  return error;
}

ALenum ALWrapper::generate_buffers(ALsizei n, ALuint *buffers, const std::string& file_name) {
  clean_errors();
  alGenBuffers(n, buffers);
  return check_for_errors("Generating " + std::to_string(n) + " buffers for " + file_name);
}

ALenum ALWrapper::delete_buffers(ALsizei n, const ALuint *buffers, const std::string& file_name) {
  clean_errors();
  alDeleteBuffers(n, buffers);
  return check_for_errors("Deleting " + std::to_string(n) + " buffers for " + file_name);
}

ALenum ALWrapper::set_buffer_data(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate, const std::string& file_name) {
  clean_errors();
  alBufferData(buffer, format, data, size, samplerate);
  return check_for_errors("Setting data for buffer for " + file_name);
}

ALenum ALWrapper::queue_source_buffers(ALuint source, ALsizei nb, const ALuint *buffers, const std::string& file_name) {
  clean_errors();
  alSourceQueueBuffers(source, nb, buffers);
  return check_for_errors("Queuing " + std::to_string(nb) + " source buffers for " + file_name);
}

ALenum ALWrapper::unqueue_source_buffers(ALuint source, ALsizei nb, ALuint *buffers, const std::string& file_name) {
  clean_errors();
  alSourceUnqueueBuffers(source, nb, buffers);
  return check_for_errors("Unqueuing " + std::to_string(nb) + "source buffers for " + file_name);
}

ALenum ALWrapper::generate_sources(ALsizei n, ALuint *sources, const std::string& file_name) {
  clean_errors();
  alGenSources(n, sources);
  return check_for_errors("Generating " + std::to_string(n) + " sources for " + file_name);
}

ALenum ALWrapper::delete_sources(ALsizei n, const ALuint *sources, const std::string& file_name) {
  clean_errors();
  alDeleteSources(n, sources);
  return check_for_errors("Deleting " + std::to_string(n) + " sources for " + file_name);
}

ALenum ALWrapper::get_source_int(ALuint source, ALenum param, ALint *value, const std::string& file_name) {
  clean_errors();
  alGetSourcei(source, param, value);
  return check_for_errors("Getting source param " + std::to_string(param) + " for " + file_name);
}

ALenum ALWrapper::set_source_int(ALuint source, ALenum param, ALint value, const std::string& file_name) {
  clean_errors();
  alSourcei(source, param, value);
  return check_for_errors("Setting source param int " + std::to_string(param) + " for " + file_name);
}

ALenum ALWrapper::set_source_float(ALuint source, ALenum param, ALfloat value, const std::string& file_name) {
  clean_errors();
  alSourcef(source, param, value);
  return check_for_errors("Setting source param float " + std::to_string(param) + " for " + file_name);
}

ALenum ALWrapper::set_source_3ints(ALuint source, ALenum param, ALint i1, ALint i2, ALint i3, const std::string& file_name) {
  clean_errors();
  alSource3i(source, param, i1, i2, i3);
  return check_for_errors("Setting source 3 ints for " + file_name);
}

ALenum ALWrapper::set_source_vector3(ALuint source, ALenum param, ALfloat x, ALfloat y, ALfloat z, const std::string& file_name) {
  clean_errors();
  alSource3f(source, param, x, y, z);
  return check_for_errors("Setting source param vector3 " + std::to_string(param) + " for " + file_name);
}

ALenum ALWrapper::set_listener_float(ALenum param, ALfloat value, const std::string& file_name) {
  clean_errors();
  alListenerf(param, value);
  return check_for_errors("Setting listener param float " + std::to_string(param) + " for " + file_name);
}

ALenum ALWrapper::play_source(ALuint source, const std::string& file_name) {
  clean_errors();
  alSourcePlay(source);
  return check_for_errors("Playing source for " + file_name);
}

ALenum ALWrapper::pause_source(ALuint source, const std::string& file_name) {
  clean_errors();
  alSourcePause(source);
  return check_for_errors("Pausing source for " + file_name);
}

ALenum ALWrapper::stop_source(ALuint source, const std::string& file_name) {
  clean_errors();
  alSourceStop(source);
  return check_for_errors("Stopping source for " + file_name);
}

ALenum ALWrapper::generate_effect(ALsizei n, ALuint *effects, const std::string& context_name) {
  clean_errors();
  alGenEffects(n, effects);
  return check_for_errors("Generating " + std::to_string(n) + " effect for " + context_name);
}

ALenum ALWrapper::generate_effect_slots(ALsizei n, ALuint *slots, const std::string& context_name) {
  clean_errors();
  alGenAuxiliaryEffectSlots(n, slots);
  return check_for_errors("Generating " + std::to_string(n) + " effect slots for " + context_name);
}

ALenum ALWrapper::set_effect_int(ALuint effect, ALenum param, ALint value, const std::string& context_name) {
  clean_errors();
  alEffecti(effect, param, value);
  return check_for_errors("Setting effect param int " + std::to_string(param) + " for " + context_name);
}

ALenum ALWrapper::set_effect_float(ALuint effect, ALenum param, ALfloat value, const std::string& context_name) {
  clean_errors();
  alEffectf(effect, param, value);
  return check_for_errors("Setting effect param float " + std::to_string(param) + " for " + context_name);
}

ALenum ALWrapper::set_effect_slot_int(ALuint slot, ALenum param, ALint value, const std::string& context_name) {
  clean_errors();
  alAuxiliaryEffectSloti(slot, param, value);
  return check_for_errors("Setting effect slot param int " + std::to_string(param) + " for " + context_name);
}

ALenum ALWrapper::generate_filter(ALsizei n, ALuint *filters, const std::string& context_name) {
  clean_errors();
  alGenFilters(n, filters);
  return check_for_errors("Generating " + std::to_string(n) + " filter for " + context_name);
}

ALenum ALWrapper::set_filter_int(ALuint filter, ALenum param, ALint value, const std::string& context_name) {
  clean_errors();
  alFilteri(filter, param, value);
  return check_for_errors("Setting filter param int " + std::to_string(param) + " for " + context_name);
}

ALenum ALWrapper::set_filter_float(ALuint filter, ALenum param, ALfloat value, const std::string& context_name) {
  clean_errors();
  alFilterf(filter, param, value);
  return check_for_errors("Setting filter param float " + std::to_string(param) + " for " + context_name);
}

} // namepsace Solarus
