/*
    Copyright 2016-2023 melonDS team

    This file is part of melonDS.

    melonDS 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.

    melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
*/

#include "AudioInOut.h"

#include <SDL2/SDL.h>

#include "FrontendUtil.h"
#include "Config.h"
#include "NDS.h"
#include "SPU.h"
#include "Platform.h"
#include "Input.h"
#include "main.h"

namespace AudioInOut
{

SDL_AudioDeviceID audioDevice;
int audioFreq;
bool audioMuted;
SDL_cond* audioSync;
SDL_mutex* audioSyncLock;

SDL_AudioDeviceID micDevice;
s16 micExtBuffer[2048];
u32 micExtBufferWritePos;

u32 micWavLength;
s16* micWavBuffer;

void AudioCallback(void* data, Uint8* stream, int len)
{
    len /= (sizeof(s16) * 2);

    // resample incoming audio to match the output sample rate

    int len_in = Frontend::AudioOut_GetNumSamples(len);
    s16 buf_in[1024*2];
    int num_in;

    SDL_LockMutex(audioSyncLock);
    num_in = NDS::SPU->ReadOutput(buf_in, len_in);
    SDL_CondSignal(audioSync);
    SDL_UnlockMutex(audioSyncLock);

    if ((num_in < 1) || audioMuted)
    {
        memset(stream, 0, len*sizeof(s16)*2);
        return;
    }

    int margin = 6;
    if (num_in < len_in-margin)
    {
        int last = num_in-1;

        for (int i = num_in; i < len_in-margin; i++)
            ((u32*)buf_in)[i] = ((u32*)buf_in)[last];

        num_in = len_in-margin;
    }

    Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume);
}

void MicCallback(void* data, Uint8* stream, int len)
{
    s16* input = (s16*)stream;
    len /= sizeof(s16);

    int maxlen = sizeof(micExtBuffer) / sizeof(s16);

    if ((micExtBufferWritePos + len) > maxlen)
    {
        u32 len1 = maxlen - micExtBufferWritePos;
        memcpy(&micExtBuffer[micExtBufferWritePos], &input[0], len1*sizeof(s16));
        memcpy(&micExtBuffer[0], &input[len1], (len - len1)*sizeof(s16));
        micExtBufferWritePos = len - len1;
    }
    else
    {
        memcpy(&micExtBuffer[micExtBufferWritePos], input, len*sizeof(s16));
        micExtBufferWritePos += len;
    }
}

void AudioMute(QMainWindow* mainWindow)
{
    int inst = Platform::InstanceID();
    audioMuted = false;

    switch (Config::MPAudioMode)
    {
    case 1: // only instance 1
        if (inst > 0) audioMuted = true;
        break;

    case 2: // only currently focused instance
        if (mainWindow != nullptr)
            audioMuted = !mainWindow->isActiveWindow();
        break;
    }
}


void MicOpen()
{
    if (Config::MicInputType != micInputType_External)
    {
        micDevice = 0;
        return;
    }

    int numMics = SDL_GetNumAudioDevices(1);
    if (numMics == 0)
        return;

    SDL_AudioSpec whatIwant, whatIget;
    memset(&whatIwant, 0, sizeof(SDL_AudioSpec));
    whatIwant.freq = 44100;
    whatIwant.format = AUDIO_S16LSB;
    whatIwant.channels = 1;
    whatIwant.samples = 1024;
    whatIwant.callback = MicCallback;
    const char* mic = NULL;
    if (Config::MicDevice != "")
    {
        mic = Config::MicDevice.c_str();
    }
    micDevice = SDL_OpenAudioDevice(mic, 1, &whatIwant, &whatIget, 0);
    if (!micDevice)
    {
        Platform::Log(Platform::LogLevel::Error, "Mic init failed: %s\n", SDL_GetError());
    }
    else
    {
        SDL_PauseAudioDevice(micDevice, 0);
    }
}

void MicClose()
{
    if (micDevice)
        SDL_CloseAudioDevice(micDevice);

    micDevice = 0;
}

void MicLoadWav(const std::string& name)
{
    SDL_AudioSpec format;
    memset(&format, 0, sizeof(SDL_AudioSpec));

    if (micWavBuffer) delete[] micWavBuffer;
    micWavBuffer = nullptr;
    micWavLength = 0;

    u8* buf;
    u32 len;
    if (!SDL_LoadWAV(name.c_str(), &format, &buf, &len))
        return;

    const u64 dstfreq = 44100;

    int srcinc = format.channels;
    len /= ((SDL_AUDIO_BITSIZE(format.format) / 8) * srcinc);

    micWavLength = (len * dstfreq) / format.freq;
    if (micWavLength < 735) micWavLength = 735;
    micWavBuffer = new s16[micWavLength];

    float res_incr = len / (float)micWavLength;
    float res_timer = 0;
    int res_pos = 0;

    for (int i = 0; i < micWavLength; i++)
    {
        u16 val = 0;

        switch (SDL_AUDIO_BITSIZE(format.format))
        {
        case 8:
            val = buf[res_pos] << 8;
            break;

        case 16:
            if (SDL_AUDIO_ISBIGENDIAN(format.format))
                val = (buf[res_pos*2] << 8) | buf[res_pos*2 + 1];
            else
                val = (buf[res_pos*2 + 1] << 8) | buf[res_pos*2];
            break;

        case 32:
            if (SDL_AUDIO_ISFLOAT(format.format))
            {
                u32 rawval;
                if (SDL_AUDIO_ISBIGENDIAN(format.format))
                    rawval = (buf[res_pos*4] << 24) | (buf[res_pos*4 + 1] << 16) | (buf[res_pos*4 + 2] << 8) | buf[res_pos*4 + 3];
                else
                    rawval = (buf[res_pos*4 + 3] << 24) | (buf[res_pos*4 + 2] << 16) | (buf[res_pos*4 + 1] << 8) | buf[res_pos*4];

                float fval = *(float*)&rawval;
                s32 ival = (s32)(fval * 0x8000);
                ival = std::clamp(ival, -0x8000, 0x7FFF);
                val = (s16)ival;
            }
            else if (SDL_AUDIO_ISBIGENDIAN(format.format))
                val = (buf[res_pos*4] << 8) | buf[res_pos*4 + 1];
            else
                val = (buf[res_pos*4 + 3] << 8) | buf[res_pos*4 + 2];
            break;
        }

        if (SDL_AUDIO_ISUNSIGNED(format.format))
            val ^= 0x8000;

        micWavBuffer[i] = val;

        res_timer += res_incr;
        while (res_timer >= 1.0)
        {
            res_timer -= 1.0;
            res_pos += srcinc;
        }
    }

    SDL_FreeWAV(buf);
}

void MicProcess()
{
    int type = Config::MicInputType;
    bool cmd = Input::HotkeyDown(HK_Mic);

    if (type != micInputType_External && !cmd)
    {
        type = micInputType_Silence;
    }

    switch (type)
    {
    case micInputType_Silence: // no mic
        Frontend::Mic_FeedSilence();
        break;

    case micInputType_External: // host mic
    case micInputType_Wav: // WAV
        Frontend::Mic_FeedExternalBuffer();
        break;

    case micInputType_Noise: // blowing noise
        Frontend::Mic_FeedNoise();
        break;
    }
}

void SetupMicInputData()
{
    if (micWavBuffer != nullptr)
    {
        delete[] micWavBuffer;
        micWavBuffer = nullptr;
        micWavLength = 0;
    }

    switch (Config::MicInputType)
    {
    case micInputType_Silence:
    case micInputType_Noise:
        Frontend::Mic_SetExternalBuffer(NULL, 0);
        break;
    case micInputType_External:
        Frontend::Mic_SetExternalBuffer(micExtBuffer, sizeof(micExtBuffer)/sizeof(s16));
        break;
    case micInputType_Wav:
        MicLoadWav(Config::MicWavPath);
        Frontend::Mic_SetExternalBuffer(micWavBuffer, micWavLength);
        break;
    }
}

void Init()
{
    audioMuted = false;
    audioSync = SDL_CreateCond();
    audioSyncLock = SDL_CreateMutex();

    audioFreq = 48000; // TODO: make configurable?
    SDL_AudioSpec whatIwant, whatIget;
    memset(&whatIwant, 0, sizeof(SDL_AudioSpec));
    whatIwant.freq = audioFreq;
    whatIwant.format = AUDIO_S16LSB;
    whatIwant.channels = 2;
    whatIwant.samples = 1024;
    whatIwant.callback = AudioCallback;
    audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
    if (!audioDevice)
    {
        Platform::Log(Platform::LogLevel::Error, "Audio init failed: %s\n", SDL_GetError());
    }
    else
    {
        audioFreq = whatIget.freq;
        Platform::Log(Platform::LogLevel::Info, "Audio output frequency: %d Hz\n", audioFreq);
        SDL_PauseAudioDevice(audioDevice, 1);
    }

    micDevice = 0;

    memset(micExtBuffer, 0, sizeof(micExtBuffer));
    micExtBufferWritePos = 0;
    micWavBuffer = nullptr;

    Frontend::Init_Audio(audioFreq);

    SetupMicInputData();
}

void DeInit()
{
    if (audioDevice) SDL_CloseAudioDevice(audioDevice);
    audioDevice = 0;
    MicClose();

    if (audioSync) SDL_DestroyCond(audioSync);
    audioSync = nullptr;

    if (audioSyncLock) SDL_DestroyMutex(audioSyncLock);
    audioSyncLock = nullptr;

    if (micWavBuffer) delete[] micWavBuffer;
    micWavBuffer = nullptr;
}

void AudioSync()
{
    if (audioDevice)
    {
        SDL_LockMutex(audioSyncLock);
        while (NDS::SPU->GetOutputSize() > 1024)
        {
            int ret = SDL_CondWaitTimeout(audioSync, audioSyncLock, 500);
            if (ret == SDL_MUTEX_TIMEDOUT) break;
        }
        SDL_UnlockMutex(audioSyncLock);
    }
}

void UpdateSettings()
{
    MicClose();

    NDS::SPU->SetInterpolation(Config::AudioInterp);
    SetupMicInputData();

    MicOpen();
}

void Enable()
{
    if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0);
    MicOpen();
}

void Disable()
{
    if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1);
    MicClose();
}

}
