/*
 * snd_sdl.c - SDL audio driver for Hexen II: Hammer of Thyrion (uHexen2)
 * based on implementations found in the quakeforge and ioquake3 projects.
 *
 * Copyright (C) 1999-2005 Id Software, Inc.
 * Copyright (C) 2005-2012 O.Sezer <sezero@users.sourceforge.net>
 * Copyright (C) 2010-2014 QuakeSpasm developers
Copyright (C) 2020-2021 Vittorio Romeo
 *
 * This program 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 2 of the License, or (at
 * your option) any later version.
 *
 * This program 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "quakedef.hpp"
#include "console.hpp"
#include "q_sound.hpp"
#include "common.hpp"

#include <SDL2/SDL.h>

static int buffersize;

static void SDLCALL paint_audio(void* unused, Uint8* stream, int len)
{
    (void)unused;

    int pos;

    int tobufend;
    int len1;

    int len2;

    if(!shm)
    { /* shouldn't happen, but just in case */
        memset(stream, 0, len);
        return;
    }

    pos = (shm->samplepos * (shm->samplebits / 8));
    if(pos >= buffersize)
    {
        shm->samplepos = pos = 0;
    }

    tobufend = buffersize - pos; /* bytes to buffer's end. */
    len1 = len;
    len2 = 0;

    if(len1 > tobufend)
    {
        len1 = tobufend;
        len2 = len - len1;
    }

    memcpy(stream, shm->buffer + pos, len1);

    if(len2 <= 0)
    {
        shm->samplepos += (len1 / (shm->samplebits / 8));
    }
    else
    { /* wraparound? */
        memcpy(stream + len1, shm->buffer, len2);
        shm->samplepos = (len2 / (shm->samplebits / 8));
    }

    if(shm->samplepos >= buffersize)
    {
        shm->samplepos = 0;
    }
}

bool SNDDMA_Init(dma_t* dma)
{
    SDL_AudioSpec desired;
    int tmp;

    int val;
    char drivername[128];

// TODO VR: (P1) hangs on linux
#ifdef WIN32
    if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
#endif
    {
        Con_Printf("Couldn't init SDL audio: %s\n", SDL_GetError());
        return false;
    }

    /* Set up the desired format */
    desired.freq = snd_mixspeed.value;
    desired.format = (loadas8bit.value) ? AUDIO_U8 : AUDIO_S16SYS;
    desired.channels = 2; /* = desired_channels; */
    if(desired.freq <= 11025)
    {
        desired.samples = 256;
    }
    else if(desired.freq <= 22050)
    {
        desired.samples = 512;
    }
    else if(desired.freq <= 44100)
    {
        desired.samples = 1024;
    }
    else if(desired.freq <= 56000)
    {
        desired.samples = 2048; /* for 48 kHz */
    }
    else
    {
        desired.samples = 4096; /* for 96 kHz */
    }
    desired.callback = paint_audio;
    desired.userdata = nullptr;

    /* Open the audio device */
    if(SDL_OpenAudio(&desired, nullptr) == -1)
    {
        Con_Printf("Couldn't open SDL audio: %s\n", SDL_GetError());
        SDL_QuitSubSystem(SDL_INIT_AUDIO);
        return false;
    }

    memset((void*)dma, 0, sizeof(dma_t));
    shm = dma;

    /* Fill the audio DMA information block */
    /* Since we passed nullptr as the 'obtained' spec to SDL_OpenAudio(),
     * SDL will convert to hardware format for us if needed, hence we
     * directly use the desired values here. */
    shm->samplebits =
        (desired.format & 0xFF); /* first byte of format is bits */
    shm->signed8 = (desired.format == AUDIO_S8);
    shm->speed = desired.freq;
    shm->channels = desired.channels;
    tmp = (desired.samples * desired.channels) * 10;
    if(tmp & (tmp - 1))
    { /* make it a power of two */
        val = 1;
        while(val < tmp)
        {
            val <<= 1;
        }

        tmp = val;
    }
    shm->samples = tmp;
    shm->samplepos = 0;
    shm->submission_chunk = 1;

    Con_Printf("SDL audio spec  : %d Hz, %d samples, %d channels\n",
        desired.freq, desired.samples, desired.channels);

    {
        const char* driver = SDL_GetCurrentAudioDriver();
        const char* device = SDL_GetAudioDeviceName(0, SDL_FALSE);
        q_snprintf(drivername, sizeof(drivername), "%s - %s",
            driver != nullptr ? driver : "(UNKNOWN)",
            device != nullptr ? device : "(UNKNOWN)");
    }

    buffersize = shm->samples * (shm->samplebits / 8);
    Con_Printf(
        "SDL audio driver: %s, %d bytes buffer\n", drivername, buffersize);

    shm->buffer = (unsigned char*)calloc(1, buffersize);
    if(!shm->buffer)
    {
        SDL_CloseAudio();
        SDL_QuitSubSystem(SDL_INIT_AUDIO);
        shm = nullptr;
        Con_Printf("Failed allocating memory for SDL audio\n");
        return false;
    }

    SDL_PauseAudio(0);

    return true;
}

int SNDDMA_GetDMAPos()
{
    return shm->samplepos;
}

void SNDDMA_Shutdown()
{
    if(shm)
    {
        Con_Printf("Shutting down SDL sound\n");
        SDL_CloseAudio();
        SDL_QuitSubSystem(SDL_INIT_AUDIO);
        if(shm->buffer)
        {
            free(shm->buffer);
        }
        shm->buffer = nullptr;
        shm = nullptr;
    }
}

void SNDDMA_LockBuffer()
{
    SDL_LockAudio();
}

void SNDDMA_Submit()
{
    SDL_UnlockAudio();
}

void SNDDMA_BlockSound()
{
    SDL_PauseAudio(1);
}

void SNDDMA_UnblockSound()
{
    SDL_PauseAudio(0);
}
