#if BACKEND == SDL3

#include "backends/backend.h"
#include "external/stb_vorbis.h"
#include "other/log.h"

#include <SDL3/SDL.h>

#include <stdio.h>
#include <vector>

// structs i variables
// =============================
namespace backend
{
    namespace audio
    {
        static SDL_AudioSpec    audioSpec       { SDL_AUDIO_S16, 2, 48000 };
        SDL_AudioDeviceID       sdlAudioDevice  { 0 };
        SDL_TimerID             timerID         { 0 };

        namespace music
        {
            struct music_t
            {
                SDL_AudioSpec       spec { SDL_AUDIO_S16, 2, 48000 };
                SDL_AudioStream*    stream { nullptr };

                const uint8_t*      ogg_data { nullptr };
                uint32_t            ogg_length { 0 };

                stb_vorbis*         vorbis { nullptr };
                stb_vorbis_info     info {};

                int                 times { 0 };
                music::state        state { music::state::invalid };
            };

            static int                      current  { -1 };
            static std::vector<music_t>     musics;
            static float                    volume  { 1.0f };
            static bool                     enabled { true };

            namespace fade
            {
                static bool fading = false;
                static int  start_time;
                static int  duration;
                static int  initial_volume;
            }
        }

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

            static std::vector<sound_t> sounds;
            static float                volume  { 0.5f };
            static bool                 enabled { true };

            namespace channel
            {
                struct channel_t
                {
                    int                 sound       { -1 };
                    int                 pos         { 0 };
                    int                 times       { 0 };
                    SDL_AudioStream     *stream     { nullptr };
                    channel::state      state       { channel::state::free };
                };

                static std::vector<channel_t> channels;

            }
        }
    }
}

// Funcions
// ==================
namespace backend
{
    namespace audio
    {
        static void updateMusic()
        {
            if (!music::enabled) return;
            if (music::current < 0) return;

            auto& m = music::musics[music::current];
            if (m.state != music::state::playing) return;

            // Fade-out
            if (music::fade::fading) {
                int time = SDL_GetTicks();
                if (time > (music::fade::start_time + music::fade::duration)) {
                    music::fade::fading = false;
                    music::stop();
                    return;
                } else {
                    float percent = float(time - music::fade::start_time) / float(music::fade::duration);
                    SDL_SetAudioStreamGain(m.stream, 1.0f - percent);
                }
            }

            // ¿Hay suficiente audio en el stream?
            if (SDL_GetAudioStreamAvailable(m.stream) > 48000) return; // 1 segundo

            // Decodificar un bloque
            const int SAMPLES = 4096;
            static int16_t temp[SAMPLES * 2];

            int n = stb_vorbis_get_samples_short_interleaved(
                m.vorbis,
                m.info.channels,
                temp,
                SAMPLES
            );

            if (n == 0) {
                if (m.times != 0) {
                    stb_vorbis_seek_start(m.vorbis);
                    if (m.times > 0) m.times--;
                    return;
                } else {
                    music::stop();
                    return;
                }
            }

            SDL_PutAudioStreamData(m.stream, temp, n * m.info.channels * sizeof(int16_t));
        }

        static void updateSound()
        {
            if (sound::enabled)
            {
                for (int i=0; i < static_cast<int>(sound::channel::channels.size()); ++i) {
                    auto &c = sound::channel::channels[i];
                    if (c.state == sound::channel::state::playing)
                    {
                        if (c.times != 0)
                        {
                            auto &s = sound::sounds[c.sound];
                            if (SDL_GetAudioStreamAvailable(c.stream) < static_cast<int>(s.length/2))
                                SDL_PutAudioStreamData(c.stream, s.buffer, s.length);
                            if (c.times>0) c.times--;
                        }
                    }
                    else
                    {
                        if (SDL_GetAudioStreamAvailable(c.stream) == 0) sound::channel::stop(i);
                    }
                }
            }
        }

        Uint32 updateCallback(void *userdata, SDL_TimerID timerID, Uint32 interval)
        {
            updateMusic();
            updateSound();
            return 30;
        }
    
        void init()
        {
            #ifdef DEBUG
            SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
            #endif

            audioSpec = {SDL_AUDIO_S16, 1, 48000 };
            if (!sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
            sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audioSpec);
            if (!sdlAudioDevice) {
                log_msg(LOG_FAIL, "Failed to initialize SDL audio: %s\n", SDL_GetError());
            } else {
                log_msg(LOG_OK, "Audio subsytem initialized\n");
            }
            //SDL_PauseAudioDevice(sdlAudioDevice);
            timerID = SDL_AddTimer(30, updateCallback, nullptr);
        }

        void quit()
        {
            if (timerID) SDL_RemoveTimer(timerID);

            for (int i=0; i<static_cast<int>(music::musics.size());++i) music::destroy(i);
            music::musics.clear();

            for (int i=0; i<static_cast<int>(sound::channel::channels.size());++i) sound::channel::stop(i);
            sound::channel::channels.clear();

            for (int i=0; i<static_cast<int>(sound::sounds.size());++i) sound::destroy(i);
            sound::sounds.clear();

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

        float setVolume(float vol)
        {
            sound::setVolume(music::setVolume(vol) / 2.0f);
            return music::volume;
        }


        namespace music
        {
            int load(const uint8_t* buffer, uint32_t length)
            {
                int idx = 0;
                while (idx < (int)musics.size() && musics[idx].state != state::invalid) idx++;
                if (idx == (int)musics.size()) musics.emplace_back();

                auto& m = musics[idx];

                m.ogg_data = buffer;
                m.ogg_length = length;

                int error;
                m.vorbis = stb_vorbis_open_memory(buffer, length, &error, nullptr);
                if (!m.vorbis) {
                    log_msg(LOG_FAIL, "stb_vorbis_open_memory failed: %d\n", error);
                    return -1;
                }

                m.info = stb_vorbis_get_info(m.vorbis);

                m.spec.channels = m.info.channels;
                m.spec.freq = m.info.sample_rate;
                m.spec.format = SDL_AUDIO_S16;

                m.state = state::stopped;

                return idx;
            }

            int load(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 -1;
                fclose(f);

                int music = load(buffer, fsize);

                free(buffer);

                return music;
            }

            void play(int mus, int loop)
            {
                stop();

                current = mus;
                auto& m = musics[mus];

                m.times = loop;
                m.state = state::playing;

                stb_vorbis_seek_start(m.vorbis);

                m.stream = SDL_CreateAudioStream(&m.spec, &audioSpec);
                SDL_SetAudioStreamGain(m.stream, volume);
                SDL_BindAudioStream(sdlAudioDevice, m.stream);
            }

            void pause()
            {
                if (!music::enabled || (current<0)) return;
                if (current>static_cast<int>(musics.size())) {
                    log_msg(LOG_FAIL, "music::pause: Illegal music handle: %i\n", current);
                    return;
                }
                auto &m = musics[current];
                if (m.state == state::invalid) {
                    log_msg(LOG_FAIL, "music::pause: Invalidated music handle: %i\n", current);
                    return;
                }

                m.state = state::paused;
                //SDL_PauseAudioStreamDevice(current->stream);
                SDL_UnbindAudioStream(m.stream);
            }

            void resume()
            {
                if (!music::enabled || (current<0)) return;
                if (current>static_cast<int>(musics.size())) {
                    log_msg(LOG_FAIL, "music::resume: Illegal music handle: %i\n", current);
                    return;
                }
                auto &m = musics[current];
                if (m.state == state::invalid) {
                    log_msg(LOG_FAIL, "music::resume: Invalidated music handle: %i\n", current);
                    return;
                }

                m.state = state::playing;
                //SDL_ResumeAudioStreamDevice(current->stream);
                SDL_BindAudioStream(sdlAudioDevice, m.stream);
            }

            void stop()
            {
                if (current < 0) return;

                auto& m = musics[current];

                m.state = state::stopped;

                if (m.stream) SDL_DestroyAudioStream(m.stream);
                m.stream = nullptr;
            }

            void fadeOut(int milliseconds)
            {
                if (!music::enabled || (current<0)) return;
                if (current>static_cast<int>(musics.size())) {
                    log_msg(LOG_FAIL, "music::fadeOut: Illegal music handle: %i\n", current);
                    return;
                }
                auto &m = musics[current];
                if (m.state == state::invalid) {
                    log_msg(LOG_FAIL, "music::fadeOut: Invalidated music handle: %i\n", current);
                    return;
                }

                fade::fading = true;
                fade::start_time = SDL_GetTicks();
                fade::duration = milliseconds;
                fade::initial_volume = volume;
            }

            music::state getState()
            {
                if (!music::enabled) return music::state::disabled;
                if (current<0 || current>static_cast<int>(musics.size())) return state::invalid;
                return musics[current].state;
            }

            void destroy(int mus)
            {
                if (mus < 0 || mus >= (int)musics.size()) return;

                auto& m = musics[mus];

                if (m.stream) SDL_DestroyAudioStream(m.stream);
                if (m.vorbis) stb_vorbis_close(m.vorbis);

                m.stream = nullptr;
                m.vorbis = nullptr;

                m.state = state::invalid;
            }

            float setVolume(float vol)
            {
                volume = SDL_clamp( vol, 0.0f, 1.0f );

                if (current<0 || current>static_cast<int>(musics.size())) return vol;
                auto &m = musics[current];
                if (m.state == state::invalid) return vol;

                SDL_SetAudioStreamGain(m.stream, volume);
                return volume;
            }

            void setPosition(float value)
            {
                auto& m = musics[current];

                int sample = int(value * m.info.sample_rate);

                if (stb_vorbis_seek(m.vorbis, sample)) {
                    SDL_ClearAudioStream(m.stream); // importante
                }
            }

            float getPosition()
            {
                if (!music::enabled) return 0;
                if (current<0 || current>static_cast<int>(musics.size())) {
                    log_msg(LOG_FAIL, "music::getPosition: Illegal music handle: %i\n", current);
                    return 0;
                }
                auto &m = musics[current];
                if (m.state == state::invalid) {
                    log_msg(LOG_FAIL, "music::getPosition: Invalidated music handle: %i\n", current);
                    return 0;
                }
                int sample = stb_vorbis_get_sample_offset(m.vorbis);
                return float(sample) / float(m.info.sample_rate);
            }

            void enable(bool value)
            {
                if (!value && music::enabled && current>=0 && current<static_cast<int>(musics.size()) && musics[current].state==state::playing) stop();
                music::enabled = value;
            }

            bool isEnabled()
            {
                return enabled;
            }

        }

        namespace sound
        {
            int create(uint8_t* buffer, uint32_t length)
            {
                int snd = 0;
                while (snd < static_cast<int>(sounds.size()) && sounds[snd].buffer) { snd++; }
                if (snd == static_cast<int>(sounds.size())) sounds.emplace_back();

                auto &s = sounds[snd];
                s.buffer = buffer;
                s.length = length;
                return snd;
            }

            int load(uint8_t* buffer, uint32_t size)
            {
                int snd = 0;
                while (snd < static_cast<int>(sounds.size()) && sounds[snd].buffer) { snd++; }
                if (snd == static_cast<int>(sounds.size())) sounds.emplace_back();

                auto &s = sounds[snd];
                SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size),1, &s.spec, &s.buffer, &s.length);

                return snd;
            }

            int load(const char* filename)
            {
                int snd = 0;
                while (snd < static_cast<int>(sounds.size()) && sounds[snd].buffer) { snd++; }
                if (snd == static_cast<int>(sounds.size())) sounds.emplace_back();

                auto &s = sounds[snd];
                SDL_LoadWAV(filename, &s.spec, &s.buffer, &s.length);

                return snd;
            }

            int play(int snd, int loop)
            {
                if (!sound::enabled) return -1;

                if (snd<0 || snd>static_cast<int>(sounds.size())) {
                    log_msg(LOG_FAIL, "sound::play: Illegal sound handle: %i\n", snd);
                    return -1;
                }
                auto &s = sounds[snd];
                if (!s.buffer) {
                    log_msg(LOG_FAIL, "sound::play: Invalid sound: %i\n", snd);
                    return -1;
                }

                int chan = 0;
                while (chan < static_cast<int>(channel::channels.size()) && channel::channels[chan].state != channel::state::free) { chan++; }
                if (chan == static_cast<int>(channel::channels.size())) channel::channels.emplace_back();
                channel::stop(chan);
                auto &c = channel::channels[chan];
                c.sound = snd;
                c.times = loop;
                c.pos = 0;
                c.state = channel::state::playing;
                c.stream = SDL_CreateAudioStream(&s.spec, &audioSpec);
                SDL_PutAudioStreamData(c.stream, s.buffer, s.length);
                SDL_SetAudioStreamGain(c.stream, volume);
                SDL_BindAudioStream(sdlAudioDevice, c.stream);

                return chan;
            }

            int playOnChannel(int snd, int chan, int loop)
            {
                if (!sound::enabled) return -1;

                if (snd<0 || snd>static_cast<int>(sounds.size())) {
                    log_msg(LOG_FAIL, "sound::playOnChannel: Illegal sound handle: %i\n", snd);
                    return -1;
                }
                auto &s = sounds[snd];
                if (!s.buffer) {
                    log_msg(LOG_FAIL, "sound::playOnChannel: Invalid sound: %i\n", snd);
                    return -1;
                }

                if (chan<0 || chan>static_cast<int>(channel::channels.size())) {
                    log_msg(LOG_FAIL, "sound::playOnChannel: Illegal channel handle: %i\n", chan);
                    return -1;
                }

                channel::stop(chan);

                auto &c = channel::channels[chan];
                c.sound = snd;
                c.times = loop;
                c.pos = 0;
                c.state = channel::state::playing;
                c.stream = SDL_CreateAudioStream(&s.spec, &audioSpec);
                SDL_PutAudioStreamData(c.stream, s.buffer, s.length);
                SDL_SetAudioStreamGain(c.stream, volume);
                SDL_BindAudioStream(sdlAudioDevice, c.stream);

                return chan;
            }

            void destroy(int snd)
            {
                for (int i = 0; i < static_cast<int>(channel::channels.size()); i++) {
                    if (channel::channels[i].sound == snd) channel::stop(i);
                }

                if (snd<0 || snd>static_cast<int>(sounds.size())) {
                    log_msg(LOG_FAIL, "sound::destroy: Illegal sound handle: %i\n", snd);
                    return;
                }
                auto &s = sounds[snd];
                SDL_free(s.buffer);
            }

            float setVolume(float vol)
            {
                volume = SDL_clamp( vol, 0.0f, 1.0f );

                for (int i = 0; i < static_cast<int>(channel::channels.size()); i++) {
                    auto &c = channel::channels[i];
                    if ( (c.state == channel::state::playing) || (c.state == channel::state::paused) )
                        SDL_SetAudioStreamGain(c.stream, volume);
                }
                return volume;
            }

            void enable(bool value)
            {
                if (!value && sound::enabled) {
                    for (int i = 0; i < static_cast<int>(channel::channels.size()); i++) {
                        if (channel::channels[i].state == channel::state::playing) channel::stop(i);
                    }
                }
                enabled = value;
            }

            bool isEnabled()
            {
                return enabled;
            }

            namespace channel
            {
                void pause(const int chan)
                {
                    if (!sound::enabled) return;

                    if (chan == -1)
                    {
                        for (int i = 0; i < static_cast<int>(channels.size()); i++)
                            if (channels[i].state == state::playing)
                            {
                                channels[i].state = state::paused;
                                //SDL_PauseAudioStreamDevice(channels[i].stream);
                                SDL_UnbindAudioStream(channels[i].stream);
                            }
                    }
                    else if (chan >= 0 && chan < static_cast<int>(channels.size()))
                    {
                        if (channels[chan].state == state::playing)
                        {
                            channels[chan].state = state::paused;
                            //SDL_PauseAudioStreamDevice(channels[channel].stream);
                            SDL_UnbindAudioStream(channels[chan].stream);
                        }
                    }
                }

                void resume(int chan)
                {
                    if (!sound::enabled) return;

                    if (chan == -1)
                    {
                        for (int i = 0; i < static_cast<int>(channels.size()); i++)
                            if (channels[i].state == state::paused)
                            {
                                channels[i].state = state::playing;
                                //SDL_ResumeAudioStreamDevice(channels[i].stream);
                                SDL_BindAudioStream(sdlAudioDevice, channels[i].stream);
                            }
                    }
                    else if (chan >= 0 && chan < static_cast<int>(channels.size()))
                    {
                        if (channels[chan].state == state::paused)
                        {
                            channels[chan].state = state::playing;
                            //SDL_ResumeAudioStreamDevice(channels[channel].stream);
                            SDL_BindAudioStream(sdlAudioDevice, channels[chan].stream);
                        }
                    }
                }

                void stop(int chan)
                {
                    if (!sound::enabled) return;

                    if (chan == -1)
                    {
                        for (int i = 0; i < static_cast<int>(channels.size()); i++) {
                            if (channels[i].state != state::free) SDL_DestroyAudioStream(channels[i].stream);
                            channels[i].stream = nullptr;
                            channels[i].state = state::free;
                            channels[i].pos = 0;
                            channels[i].sound = -1;
                        }
                    }
                    else if (chan >= 0 && chan < static_cast<int>(channels.size()))
                    {
                        if (channels[chan].state != state::free) SDL_DestroyAudioStream(channels[chan].stream);
                        channels[chan].stream = nullptr;
                        channels[chan].state = state::free;
                        channels[chan].pos = 0;
                        channels[chan].sound = -1;
                    }
                }

                channel::state getState(int chan)
                {
                    if (!sound::enabled) return state::disabled;
                    if (chan < 0 || chan >= static_cast<int>(channels.size())) return state::invalid;
                    return channels[chan].state;
                }
            }
        }
    }
}

#endif