#include "audio.h"
#include "file.h"
#include "stb_vorbis.h"
#include <SDL3/SDL.h>
#include <stdio.h>

namespace audio {

    #define MAX_CHANNELS 5

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

    struct channel
    {
        audio::sound        *sound      { nullptr };
        int                 pos         { 0 };
        int                 times       { 0 };
        SDL_AudioStream     *stream     { nullptr };
        audio::state        state       { audio::state::available };
    };

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

        int                 pos         { 0 };
        int                 times       { 0 };
        SDL_AudioStream     *stream     { nullptr };
        audio::state        state       { audio::state::invalid };
    };

    audio::music        *current_music  { nullptr };
    audio::channel      channels[MAX_CHANNELS];

    SDL_AudioSpec       audio_spec      { SDL_AUDIO_S16, 2, 48000 };
    float               music_volume    { 1.0f };
    float               sound_volume    { 0.5f };
    bool                music_enabled   { true };
    bool                sound_enabled   { true };
    SDL_AudioDeviceID   audio_device    { 0 };
    SDL_TimerID         timer_id        { 0 };

    bool                fading          { false };
    int                 fade_start_time;
    int                 fade_duration;
    int                 fade_initial_volume;

    Uint32 updateCallback(void *userdata, SDL_TimerID timer_id, Uint32 interval)
    {
        if (music_enabled && current_music && current_music->state == audio::state::playing)
        {
            if (fading)
            {
                const int time = SDL_GetTicks();
                if (time > (fade_start_time+fade_duration)) {
                    fading = false;
                    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, music_volume*(1.0 - percent));
                }
            }

            if (current_music->times != 0)
            {
                if (SDL_GetAudioStreamAvailable(current_music->stream) < 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) stopMusic();
            }
        }

        if (sound_enabled)
        {
            for (int i=0; i < MAX_CHANNELS; ++i)
                if (channels[i].state == audio::state::playing)
                {
                    if (channels[i].times != 0)
                    {
                        if (SDL_GetAudioStreamAvailable(channels[i].stream) < 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 (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) stopChannel(i);
                    }
                }

        }

        return 30;
    }

    void init(const int freq, const SDL_AudioFormat format, const int num_channels)
    {
        #ifdef DEBUG
        SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
        #endif
        
        SDL_Log("Iniciant JailAudio...");
        audio_spec = {format, num_channels, freq };
        if (!audio_device) SDL_CloseAudioDevice(audio_device);
        audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec);
        SDL_Log( (audio_device==0) ? "Failed to initialize SDL audio!\n" : "OK!\n");
        for (int i=0;i<MAX_CHANNELS;++i) channels[i].state = audio::state::available;
        timer_id = SDL_AddTimer(30, updateCallback, nullptr);
    }

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

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

    audio::music *loadMusic(Uint8* buffer, Uint32 length)
    {
        audio::music *music = new audio::music();

        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 = audio::state::stopped;

        return music;
    }

    audio::music *loadMusic(const char* filename)
    {
        int size;
        char *buffer = file::getFileBuffer(filename, size);
        if (buffer == nullptr) {
            SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR (audio::loadMusic): No s'ha trobat l'arxiu %s\n", filename);
            exit(1);
        }

        audio::music *music = loadMusic((Uint8*)buffer, size);

        music->filename = (char*)malloc(strlen(filename)+1);
        strcpy(music->filename, filename);

        free(buffer);

        return music;
    }

    void playMusic(audio::music *music, const int loop)
    {
        if (!music_enabled) return;

        stopMusic();

        current_music = music;
        current_music->pos = 0;
        current_music->state = audio::state::playing;
        current_music->times = loop;

        current_music->stream = SDL_CreateAudioStream(&current_music->spec, &audio_spec);
        if (!current_music->stream) {
            SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR (audio::playMusic): SDL_CreateAudioStream: %s\n", SDL_GetError());
            exit(1);
        }
        
        if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length)) {
            SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR (audio::playMusic): SDL_PutAudioStreamData: %s\n", SDL_GetError());
            exit(1);
        }

        SDL_SetAudioStreamGain(current_music->stream, music_volume);

        if (!SDL_BindAudioStream(audio_device, current_music->stream)) {
            SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR (audio::playMusic): SDL_BindAudioStream: %s\n", SDL_GetError());
            exit(1);
        }
    }

    void loadAndPlayMusic(const char* filename, const bool force_reinit)
    {
        if ( (getMusicState() != audio::state::playing) || (strcmp(getMusicFilename(), filename)!=0) || force_reinit )
        {
            playMusic(loadMusic(filename));
        }
    }

    char *getMusicFilename(audio::music *music)
    {
        if (!music) music = current_music;
        return music->filename;
    }

    void pauseMusic()
    {
        if (!music_enabled) return;
        if (!current_music || current_music->state == audio::state::invalid) return;

        current_music->state = audio::state::paused;
        SDL_UnbindAudioStream(current_music->stream);
    }

    void resumeMusic()
    {
        if (!music_enabled) return;
        if (!current_music || current_music->state == audio::state::invalid) return;

        current_music->state = audio::state::playing;
        SDL_BindAudioStream(audio_device, current_music->stream);
    }

    void stopMusic()
    {
        if (!music_enabled) return;
        if (!current_music || current_music->state == audio::state::invalid) return;

        current_music->pos = 0;
        current_music->state = audio::state::stopped;
        SDL_DestroyAudioStream(current_music->stream);
        current_music->stream = nullptr;
        free(current_music->filename);
        current_music->filename = nullptr;
    }

    void fadeOutMusic(const int milliseconds)
    {
        if (!music_enabled) return;
        if (current_music == NULL || current_music->state == audio::state::invalid) return;

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

    audio::state getMusicState()
    {
        if (!music_enabled) return audio::state::disabled;
        if (!current_music) return audio::state::invalid;

        return current_music->state;
    }

    void deleteMusic(audio::music *music)
    {
        if (current_music == music) current_music = nullptr;
        SDL_free(music->buffer);
        if (music->stream) SDL_DestroyAudioStream(music->stream);
        delete music;
    }

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

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

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

    void enableMusic(const bool value)
    {
        if ( !value && current_music && (current_music->state==audio::state::playing) ) stopMusic();

        music_enabled = value;
    }





    audio::sound *newSound(Uint8* buffer, Uint32 length)
    {
        audio::sound *sound = new audio::sound();
        sound->buffer = buffer;
        sound->length = length;
        return sound;
    }

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

        return sound;
    }

    audio::sound *loadSound(const char* filename)
    {
        int size;
        uint8_t *buffer = (uint8_t *)file::getFileBuffer(filename, size);

        if (buffer == nullptr) {
            SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR (audio::loadSound): No s'ha trobat l'arxiu %s\n", filename);
            exit(1);
        }
        audio::sound *sound = loadSound(buffer, size);
        free(buffer);
        return sound;
    }

    int playSound(audio::sound *sound, const int loop)
    {
        if (!sound_enabled) return -1;

        int channel = 0;
        while (channel < MAX_CHANNELS && channels[channel].state != audio::state::available) { channel++; }
        if (channel == MAX_CHANNELS) channel = 0;
        stopChannel(channel);

        channels[channel].sound = sound;
        channels[channel].times = loop;
        channels[channel].pos = 0;
        channels[channel].state = audio::state::playing;
        channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &audio_spec);
        SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
        SDL_SetAudioStreamGain(channels[channel].stream, sound_volume);
        SDL_BindAudioStream(audio_device, channels[channel].stream);

        return channel;
    }

    int playSoundOnChannel(audio::sound *sound, const int channel, const int loop)
    {
        if (!sound_enabled) return -1;

        if (channel < 0 || channel >= MAX_CHANNELS) return -1;
        stopChannel(channel);

        channels[channel].sound = sound;
        channels[channel].times = loop;
        channels[channel].pos = 0;
        channels[channel].state = audio::state::playing;
        channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &audio_spec);
        SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
        SDL_SetAudioStreamGain(channels[channel].stream, sound_volume);
        SDL_BindAudioStream(audio_device, channels[channel].stream);

        return channel;
    }

    void deleteSound(audio::sound *sound)
    {
        for (int i = 0; i < MAX_CHANNELS; i++) {
            if (channels[i].sound == sound) stopChannel(i);
        }
        SDL_free(sound->buffer);
        delete sound;
    }

    void pauseChannel(const int channel)
    {
        if (!sound_enabled) return;

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

    void resumeChannel(const int channel)
    {
        if (!sound_enabled) return;

        if (channel == -1)
        {
            for (int i = 0; i < MAX_CHANNELS; i++)
                if (channels[i].state == audio::state::paused)
                {
                    channels[i].state = audio::state::playing;
                    SDL_BindAudioStream(audio_device, channels[i].stream);
                }
        }
        else if (channel >= 0 && channel < MAX_CHANNELS)
        {
            if (channels[channel].state == audio::state::paused)
            {
                channels[channel].state = audio::state::playing;
                SDL_BindAudioStream(audio_device, channels[channel].stream);
            }
        }
    }

    void stopChannel(const int channel)
    {
        if (!sound_enabled) return;

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

    audio::state getChannelState(const int channel)
    {
        if (!sound_enabled) return audio::state::disabled;

        if (channel < 0 || channel >= MAX_CHANNELS) return audio::state::invalid;

        return channels[channel].state;
    }

    float setSoundVolume(float volume)
    {
        sound_volume = SDL_clamp( volume, 0.0f, 1.0f );

        for (int i = 0; i < MAX_CHANNELS; i++)
            if ( (channels[i].state == audio::state::playing) || (channels[i].state == audio::state::paused) )
                SDL_SetAudioStreamGain(channels[i].stream, sound_volume);

        return sound_volume;
    }

    void enableSound(const bool value)
    {
        for (int i = 0; i < MAX_CHANNELS; i++)
        {
            if (channels[i].state == audio::state::playing) stopChannel(i);
        }
        sound_enabled = value;
    }

    float setVolume(float volume)
    {
        setSoundVolume(setMusicVolume(volume) / 2.0f);

        return music_volume;
    }

    #undef MAX_CHANNELS

}