#ifndef JA_USESDLMIXER
#include "jail_audio.h"
#include <SDL3/SDL_iostream.h> // Para SDL_IOFromMem
#include <SDL3/SDL_log.h>      // Para SDL_Log, SDL_SetLogPriority, SDL_LogC...
#include <SDL3/SDL_timer.h>    // Para SDL_GetTicks, SDL_AddTimer, SDL_Remov...
#include <stdint.h>            // Para uint32_t, uint8_t
#include <stdio.h>             // Para NULL, fseek, printf, fclose, fopen
#include <stdlib.h>            // Para free, malloc
#include "stb_vorbis.c"        // Para stb_vorbis_decode_memory

#define JA_MAX_SIMULTANEOUS_CHANNELS 20

struct JA_Sound_t
{
    SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
    Uint32 length{0};
    Uint8 *buffer{NULL};
};

struct JA_Channel_t
{
    JA_Sound_t *sound{nullptr};
    int pos{0};
    int times{0};
    SDL_AudioStream *stream{nullptr};
    JA_Channel_state state{JA_CHANNEL_FREE};
};

struct JA_Music_t
{
    SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
    Uint32 length{0};
    Uint8 *buffer{nullptr};

    int pos{0};
    int times{0};
    SDL_AudioStream *stream{nullptr};
    JA_Music_state state{JA_MUSIC_INVALID};
};

JA_Music_t *current_music{nullptr};
JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS];

SDL_AudioSpec JA_audioSpec{SDL_AUDIO_S16, 2, 48000};
float JA_musicVolume{1.0f};
float JA_soundVolume{0.5f};
bool JA_musicEnabled{true};
bool JA_soundEnabled{true};
SDL_AudioDeviceID sdlAudioDevice{0};
SDL_TimerID JA_timerID{0};

bool fading = false;
int fade_start_time;
int fade_duration;
int fade_initial_volume;

/*
void audioCallback(void * userdata, uint8_t * stream, int len) {
    SDL_memset(stream, 0, len);
    if (current_music != NULL && current_music->state == JA_MUSIC_PLAYING) {
        const int size = SDL_min(len, current_music->samples*2-current_music->pos);
        SDL_MixAudioFormat(stream, (Uint8*)(current_music->output+current_music->pos), AUDIO_S16, size, JA_musicVolume);
        current_music->pos += size/2;
        if (size < len) {
            if (current_music->times != 0) {
                SDL_MixAudioFormat(stream+size, (Uint8*)current_music->output, AUDIO_S16, len-size, JA_musicVolume);
                current_music->pos = (len-size)/2;
                if (current_music->times > 0) current_music->times--;
            } else {
                current_music->pos = 0;
                current_music->state = JA_MUSIC_STOPPED;
            }
        }
    }
    // Mixar els channels mi amol
    for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
        if (channels[i].state == JA_CHANNEL_PLAYING) {
            const int size = SDL_min(len, channels[i].sound->length - channels[i].pos);
            SDL_MixAudioFormat(stream, channels[i].sound->buffer + channels[i].pos, AUDIO_S16, size, JA_soundVolume);
            channels[i].pos += size;
            if (size < len) {
                if (channels[i].times != 0) {
                    SDL_MixAudioFormat(stream + size, channels[i].sound->buffer, AUDIO_S16, len-size, JA_soundVolume);
                    channels[i].pos = len-size;
                    if (channels[i].times > 0) channels[i].times--;
                } else {
                    JA_StopChannel(i);
                }
            }
        }
    }
}
*/

Uint32 JA_UpdateCallback(void *userdata, SDL_TimerID timerID, Uint32 interval)
{
    if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING)
    {
        if (fading)
        {
            int time = SDL_GetTicks();
            if (time > (fade_start_time + fade_duration))
            {
                fading = false;
                JA_StopMusic();
                return 30;
            }
            else
            {
                const int time_passed = time - fade_start_time;
                const float percent = (float)time_passed / (float)fade_duration;
                SDL_SetAudioStreamGain(current_music->stream, 1.0 - percent);
            }
        }

        if (current_music->times != 0)
        {
            if (SDL_GetAudioStreamAvailable(current_music->stream) < static_cast<int>(current_music->length / 2))
            {
                SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length);
            }
            if (current_music->times > 0)
                current_music->times--;
        }
        else
        {
            if (SDL_GetAudioStreamAvailable(current_music->stream) == 0)
                JA_StopMusic();
        }
    }

    if (JA_soundEnabled)
    {
        for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i)
            if (channels[i].state == JA_CHANNEL_PLAYING)
            {
                if (channels[i].times != 0)
                {
                    if (SDL_GetAudioStreamAvailable(channels[i].stream) < static_cast<int>(channels[i].sound->length / 2))
                        SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length);
                    if (channels[i].times > 0)
                        channels[i].times--;
                }
            }
            else
            {
                if (channels[i].stream && SDL_GetAudioStreamAvailable(channels[i].stream) == 0)
                {
                    JA_StopChannel(i);
                }
            }
    }

    return 30;
}

void JA_Init(const int freq, const SDL_AudioFormat format, const int channels)
{
#ifdef DEBUG
    SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
#endif

    SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "Iniciant JailAudio...");
    JA_audioSpec = {format, channels, freq};
    if (!sdlAudioDevice)
        SDL_CloseAudioDevice(sdlAudioDevice);
    sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec);
    if (sdlAudioDevice == 0)
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize SDL audio!\n");
    else
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL audio initialized successfully.\n");

    // SDL_PauseAudioDevice(sdlAudioDevice);
    JA_timerID = SDL_AddTimer(30, JA_UpdateCallback, nullptr);
}

void JA_Quit()
{
    if (JA_timerID)
        SDL_RemoveTimer(JA_timerID);

    if (!sdlAudioDevice)
        SDL_CloseAudioDevice(sdlAudioDevice);
    sdlAudioDevice = 0;
}

JA_Music_t *JA_LoadMusic(Uint8 *buffer, Uint32 length)
{
    JA_Music_t *music = new JA_Music_t();

    int chan, samplerate;
    short *output;
    music->length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2;

    music->spec.channels = chan;
    music->spec.freq = samplerate;
    music->spec.format = SDL_AUDIO_S16;
    music->buffer = (Uint8 *)SDL_malloc(music->length);
    SDL_memcpy(music->buffer, output, music->length);
    free(output);
    music->pos = 0;
    music->state = JA_MUSIC_STOPPED;

    return music;
}

JA_Music_t *JA_LoadMusic(const char *filename)
{
    // [RZC 28/08/22] Carreguem primer el arxiu en memòria i després el descomprimim. Es algo més rapid.
    FILE *f = fopen(filename, "rb");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);
    Uint8 *buffer = (Uint8 *)malloc(fsize + 1);
    if (fread(buffer, fsize, 1, f) != 1)
        return NULL;
    fclose(f);

    JA_Music_t *music = JA_LoadMusic(buffer, fsize);

    free(buffer);

    return music;
}

void JA_PlayMusic(JA_Music_t *music, const int loop)
{
    if (!JA_musicEnabled)
        return;

    JA_StopMusic();

    current_music = music;
    current_music->pos = 0;
    current_music->state = JA_MUSIC_PLAYING;
    current_music->times = loop;

    current_music->stream = SDL_CreateAudioStream(&current_music->spec, &JA_audioSpec);
    if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length))
        printf("[ERROR] SDL_PutAudioStreamData failed!\n");
    SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
    if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream))
        printf("[ERROR] SDL_BindAudioStream failed!\n");
}

void JA_PauseMusic()
{
    if (!JA_musicEnabled)
        return;
    if (!current_music || current_music->state == JA_MUSIC_INVALID)
        return;

    current_music->state = JA_MUSIC_PAUSED;
    // SDL_PauseAudioStreamDevice(current_music->stream);
    SDL_UnbindAudioStream(current_music->stream);
}

void JA_ResumeMusic()
{
    if (!JA_musicEnabled)
        return;
    if (!current_music || current_music->state == JA_MUSIC_INVALID)
        return;

    current_music->state = JA_MUSIC_PLAYING;
    // SDL_ResumeAudioStreamDevice(current_music->stream);
    SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
}

void JA_StopMusic()
{
    if (!JA_musicEnabled)
        return;
    if (!current_music || current_music->state == JA_MUSIC_INVALID)
        return;

    current_music->pos = 0;
    current_music->state = JA_MUSIC_STOPPED;
    // SDL_PauseAudioStreamDevice(current_music->stream);
    SDL_DestroyAudioStream(current_music->stream);
    current_music->stream = nullptr;
}

void JA_FadeOutMusic(const int milliseconds)
{
    if (!JA_musicEnabled)
        return;
    if (current_music == NULL || current_music->state == JA_MUSIC_INVALID)
        return;

    fading = true;
    fade_start_time = SDL_GetTicks();
    fade_duration = milliseconds;
    fade_initial_volume = JA_musicVolume;
}

JA_Music_state JA_GetMusicState()
{
    if (!JA_musicEnabled)
        return JA_MUSIC_DISABLED;
    if (!current_music)
        return JA_MUSIC_INVALID;

    return current_music->state;
}

void JA_DeleteMusic(JA_Music_t *music)
{
    if (current_music == music)
        current_music = nullptr;
    SDL_free(music->buffer);
    if (music->stream)
        SDL_DestroyAudioStream(music->stream);
    delete music;
}

float JA_SetMusicVolume(float volume)
{
    JA_musicVolume = SDL_clamp(volume, 0.0f, 1.0f);
    if (current_music)
        SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
    return JA_musicVolume;
}

void JA_SetMusicPosition(float value)
{
    if (!current_music)
        return;
    current_music->pos = value * current_music->spec.freq;
}

float JA_GetMusicPosition()
{
    if (!current_music)
        return 0;
    return float(current_music->pos) / float(current_music->spec.freq);
}

void JA_EnableMusic(const bool value)
{
    if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING))
        JA_StopMusic();

    JA_musicEnabled = value;
}

JA_Sound_t *JA_NewSound(Uint8 *buffer, Uint32 length)
{
    JA_Sound_t *sound = new JA_Sound_t();
    sound->buffer = buffer;
    sound->length = length;
    return sound;
}

JA_Sound_t *JA_LoadSound(uint8_t *buffer, uint32_t size)
{
    JA_Sound_t *sound = new JA_Sound_t();
    SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &sound->buffer, &sound->length);

    return sound;
}

JA_Sound_t *JA_LoadSound(const char *filename)
{
    JA_Sound_t *sound = new JA_Sound_t();
    SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length);

    return sound;
}

int JA_PlaySound(JA_Sound_t *sound, const int loop)
{
    if (!JA_soundEnabled)
        return -1;

    int channel = 0;
    while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE)
    {
        channel++;
    }
    if (channel == JA_MAX_SIMULTANEOUS_CHANNELS)
        channel = 0;
    JA_StopChannel(channel);

    channels[channel].sound = sound;
    channels[channel].times = loop;
    channels[channel].pos = 0;
    channels[channel].state = JA_CHANNEL_PLAYING;
    channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
    SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
    SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume);
    SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);

    return channel;
}

int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop)
{
    if (!JA_soundEnabled)
        return -1;

    if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS)
        return -1;
    JA_StopChannel(channel);

    channels[channel].sound = sound;
    channels[channel].times = loop;
    channels[channel].pos = 0;
    channels[channel].state = JA_CHANNEL_PLAYING;
    channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
    SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
    SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume);
    SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);

    return channel;
}

void JA_DeleteSound(JA_Sound_t *sound)
{
    for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
    {
        if (channels[i].sound == sound)
            JA_StopChannel(i);
    }
    SDL_free(sound->buffer);
    delete sound;
}

void JA_PauseChannel(const int channel)
{
    if (!JA_soundEnabled)
        return;

    if (channel == -1)
    {
        for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
            if (channels[i].state == JA_CHANNEL_PLAYING)
            {
                channels[i].state = JA_CHANNEL_PAUSED;
                // SDL_PauseAudioStreamDevice(channels[i].stream);
                SDL_UnbindAudioStream(channels[i].stream);
            }
    }
    else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
    {
        if (channels[channel].state == JA_CHANNEL_PLAYING)
        {
            channels[channel].state = JA_CHANNEL_PAUSED;
            // SDL_PauseAudioStreamDevice(channels[channel].stream);
            SDL_UnbindAudioStream(channels[channel].stream);
        }
    }
}

void JA_ResumeChannel(const int channel)
{
    if (!JA_soundEnabled)
        return;

    if (channel == -1)
    {
        for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
            if (channels[i].state == JA_CHANNEL_PAUSED)
            {
                channels[i].state = JA_CHANNEL_PLAYING;
                // SDL_ResumeAudioStreamDevice(channels[i].stream);
                SDL_BindAudioStream(sdlAudioDevice, channels[i].stream);
            }
    }
    else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
    {
        if (channels[channel].state == JA_CHANNEL_PAUSED)
        {
            channels[channel].state = JA_CHANNEL_PLAYING;
            // SDL_ResumeAudioStreamDevice(channels[channel].stream);
            SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
        }
    }
}

void JA_StopChannel(const int channel)
{
    if (!JA_soundEnabled)
        return;

    if (channel == -1)
    {
        for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
        {
            if (channels[i].state != JA_CHANNEL_FREE)
                SDL_DestroyAudioStream(channels[i].stream);
            channels[i].stream = nullptr;
            channels[i].state = JA_CHANNEL_FREE;
            channels[i].pos = 0;
            channels[i].sound = NULL;
        }
    }
    else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
    {
        if (channels[channel].state != JA_CHANNEL_FREE)
            SDL_DestroyAudioStream(channels[channel].stream);
        channels[channel].stream = nullptr;
        channels[channel].state = JA_CHANNEL_FREE;
        channels[channel].pos = 0;
        channels[channel].sound = NULL;
    }
}

JA_Channel_state JA_GetChannelState(const int channel)
{
    if (!JA_soundEnabled)
        return JA_SOUND_DISABLED;

    if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS)
        return JA_CHANNEL_INVALID;

    return channels[channel].state;
}

float JA_SetSoundVolume(float volume)
{
    JA_soundVolume = SDL_clamp(volume, 0.0f, 1.0f);

    for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
        if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED))
            SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume);

    return JA_soundVolume;
}

void JA_EnableSound(const bool value)
{
    for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
    {
        if (channels[i].state == JA_CHANNEL_PLAYING)
            JA_StopChannel(i);
    }
    JA_soundEnabled = value;
}

float JA_SetVolume(float volume)
{
    JA_SetSoundVolume(JA_SetMusicVolume(volume) / 2.0f);

    return JA_musicVolume;
}

#endif