/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames 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.
 * VgaGames 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 VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "iolib.h"
#include "audio/audio.h"

void init_audio_sdl2(void);

static VG_BOOL sdl2_audio_open(int, VG_BOOL, int);
static VG_BOOL sdl2_audio_is_open(void);
static void sdl2_audio_close(void);
static VG_BOOL sdl2_audio_load(const char *, char **, size_t *);
static void sdl2_audio_lock(VG_BOOL);

static struct iolib * get_iolib(void);
static void SDLCALL audio_cbf(void *, Uint8 *, int);


/* set functions */
void
init_audio_sdl2(void)
{
  vg4data.iolib.f.audio_open = sdl2_audio_open;
  vg4data.iolib.f.audio_is_open = sdl2_audio_is_open;
  vg4data.iolib.f.audio_close = sdl2_audio_close;
  vg4data.iolib.f.audio_load = sdl2_audio_load;
  vg4data.iolib.f.audio_lock = sdl2_audio_lock;
} /* Ende init_audio_sdl2 */


/* check if is valid and return iolib pointer */
static struct iolib *
get_iolib(void)
{
  struct iolib *iolib = (struct iolib *)vg4data.iolib.stptr;

  if (iolib == NULL) { return NULL; }
  if (iolib->audio.aid == 0) { return NULL; }

  return iolib;
} /* Ende get_iolib */


/* callback function for audio-data */
static void SDLCALL
audio_cbf(void *userdata, Uint8 *stream, int len)
{
  struct vgi_audio *sptr = vg4data.lists.audio.s;
  sptr->cbf(userdata, (unsigned char *)stream, len);
} /* Ende audio_cbf */


/* sdl2_audio_open:
 * open audio
 * @param freq      one of VG_AUDIO_FREQS
 * @param stereo    VG_FALSE = mono or VG_TRUE = stereo
 * @param bitsize   one of VG_AUDIO_BITSIZES
 * @return  VG_TRUE = OK or VG_FALSE = error
 */
static VG_BOOL
sdl2_audio_open(int freq, VG_BOOL stereo, int bitsize)
{
  struct iolib *iolib;
  SDL_AudioSpec adev;
  int i1, dev_channels, dev_format, dev_samples, dev_freq;
  struct vgi_audio *sptr;

  iolib = (struct iolib *)vg4data.iolib.stptr;
  if (iolib == NULL) { outerr("Not initalized"); return VG_FALSE; }
  if (iolib->audio.aid > 0) { outerr("Audio already opened"); return VG_FALSE; }

  sptr = vg4data.lists.audio.s;

  dev_samples = 4096;
  switch(freq) {
    case VG_AUDIO_FREQ_LOW:
      dev_freq = 11025;
      dev_samples /= 4;
      break;
    case VG_AUDIO_FREQ_MEDIUM:
      dev_freq = 22050;
      dev_samples /= 2;
      break;
    case VG_AUDIO_FREQ_HIGH:
      dev_freq = 44100;
      break;
    default:
      freq = VG_AUDIO_FREQ_MEDIUM;
      dev_freq = 22050;
      dev_samples /= 2;
  }
  if (stereo) { dev_channels = 2; } else { dev_channels = 1; }
  switch(bitsize) {
    case VG_AUDIO_BITSIZE_16: dev_format = AUDIO_S16; break;
    case VG_AUDIO_BITSIZE_32: dev_format = AUDIO_S32; break;
    case VG_AUDIO_BITSIZE_32FLOAT: dev_format = AUDIO_F32; break;
    default: dev_format = AUDIO_S16;
  }

  pinfo("Using audio driver: %s\n", iolib->syms.SDL_GetCurrentAudioDriver());

  /* open audio-device */

  sptr->mainvol = 100;
  for (i1 = 0; i1 < VG_AUDIO_VOLUME_MAXENUM - 1; i1++) { sptr->volg[i1] = 100; }
  sptr->ismute = VG_FALSE;
  sptr->issuspend = VG_FALSE;

  memset(&adev, 0, sizeof(adev));
  adev.format = dev_format;
  adev.channels = dev_channels;
  adev.freq = dev_freq;
  adev.samples = dev_samples;
  adev.callback = audio_cbf;
  adev.userdata = NULL;

  iolib->audio.aid = iolib->syms.SDL_OpenAudioDevice(NULL, 0, &adev, &iolib->audio.adev, 0);
  if (iolib->audio.aid == 0) {
    outerr("opening audio: %s", iolib->syms.SDL_GetError());
    return VG_FALSE;
  }

  dev_format = (int)SDL_AUDIO_BITSIZE(iolib->audio.adev.format); 
  if (dev_format == 16) {
    sptr->bitsize = VG_AUDIO_BITSIZE_16;
  } else if (dev_format == 32) {
    if (SDL_AUDIO_ISFLOAT(iolib->audio.adev.format)) {
      sptr->bitsize = VG_AUDIO_BITSIZE_32FLOAT;
    } else {
      sptr->bitsize = VG_AUDIO_BITSIZE_32;
    }
  } else {
    iolib->syms.SDL_CloseAudioDevice(iolib->audio.aid);
    outerr("Unsupported format obtained: %d bits", dev_format);
    iolib->audio.aid = 0;
    return VG_FALSE;
  }
  sptr->freq = iolib->audio.adev.freq;
  sptr->stereo = (iolib->audio.adev.channels == 1 ? VG_FALSE : VG_TRUE);

  pinfo("Audio-device opened with:\n");
  pinfo(" - %d frequency (samples per second)\n", sptr->freq);
  pinfo(" - %d channel(s)\n", (int)iolib->audio.adev.channels);
  pinfo(" - %d bits per sample\n", dev_format);
  pinfo(" - %d samples\n", (int)iolib->audio.adev.samples);
  pinfo(" - %s data\n", (SDL_AUDIO_ISFLOAT(iolib->audio.adev.format) ? "floating point" : "integer"));

  iolib->syms.SDL_PauseAudioDevice(iolib->audio.aid, 0);

  return VG_TRUE;
} /* Ende sdl2_audio_open */


/* sdl2_audio_is_open:
 * return whether audio has been opened
 * @return  VG_TRUE = open, VG_FALSE = not open
 */
static VG_BOOL
sdl2_audio_is_open(void)
{
  struct iolib *iolib;

  if ((iolib = get_iolib()) == NULL) { return VG_FALSE; }
  if (iolib->audio.aid <= 0) { return VG_FALSE; }

  return VG_TRUE;
} /* Ende sdl2_audio_is_open */


/* sdl2_audio_close:
 * close audio
 */
static void
sdl2_audio_close(void)
{
  struct iolib *iolib;

  if ((iolib = get_iolib()) == NULL) { return; }

  iolib->syms.SDL_CloseAudioDevice(iolib->audio.aid);
  iolib->audio.aid = 0;
} /* Ende sdl2_audio_close */


/* sdl2_audio_load:
 * load audio file
 * @param file     audio file to load (if not memptr set)
 * @param memptr   address: if *memptr not NULL, load from *memptr, not from file,
 *                 return loaded data
 * @param memlen   address: number of bytes in *memptr,
 *                 return number of bytes for loaded data
 * @return  VG_TRUE = OK or VG_FALSE = not loaded
 */
static VG_BOOL
sdl2_audio_load(const char *file, char **memptr, size_t *memlen)
{
  struct iolib *iolib;
  SDL_AudioSpec aspec;
  Uint8 *wav_start;
  Uint32 wav_size;
  SDL_RWops *rwops;

  if ((iolib = get_iolib()) == NULL) {
    outerr("Error loading \"%s\": audio device not opened", (file == NULL ? "" : file));
    return VG_FALSE;
  }
  if (memptr == NULL || memlen == NULL) { return VG_FALSE; }
  if (*memptr == NULL || *memlen == 0) { *memptr = NULL; *memlen = 0; }

  /* open file or memory */
  if (*memptr != NULL) {
    rwops = iolib->syms.SDL_RWFromMem(*memptr, (int)*memlen);
  } else if (file != NULL && *file != '\0') {
    rwops = iolib->syms.SDL_RWFromFile(file, "rb");
  } else {
    return VG_FALSE;
  }
  if (rwops == NULL) {
    outerr("Error opening \"%s\": %s", (file == NULL ? "" : file), iolib->syms.SDL_GetError());
    return VG_FALSE;
  }

  /* load WAVE */
  if (iolib->syms.SDL_LoadWAV_RW(rwops, 1, &aspec, &wav_start, &wav_size) == NULL) {
    outerr("Error loading \"%s\": %s", (file == NULL ? "" : file), iolib->syms.SDL_GetError());
    if (*memptr != NULL) { free(*memptr); *memptr = NULL; *memlen = 0; }
    return VG_FALSE;
  }

  if (*memptr != NULL) { free(*memptr); *memptr = NULL; *memlen = 0; }

  /* check for correct format */
  if (aspec.freq != iolib->audio.adev.freq
      || aspec.format != iolib->audio.adev.format
      || aspec.channels != iolib->audio.adev.channels
     ) {
    outerr("Error: format of file \"%s\" (freq=%d,format=%d,channels=%d) is different to audio-device (%d,%d,%d)",
           (file == NULL ? "" : file),
           aspec.freq, aspec.format, aspec.channels,
           iolib->audio.adev.freq, iolib->audio.adev.format, iolib->audio.adev.channels);
    iolib->syms.SDL_FreeWAV(wav_start);
    return VG_FALSE;
  }

  /* return data */
  *memlen = (size_t)wav_size;
  *memptr = SML3_malloc(*memlen);
  memcpy(*memptr, wav_start, *memlen);
  iolib->syms.SDL_FreeWAV(wav_start);

  return VG_TRUE;
} /* Ende sdl2_audio_load */


/* sdl2_audio_lock:
 * (un)lock audio-device
 * @param dolock  VG_TRUE = lock or VG_FALSE = unlock
 */
static void
sdl2_audio_lock(VG_BOOL dolock)
{
  struct iolib *iolib;

  if ((iolib = get_iolib()) == NULL) { return; }

  if (dolock) {
    iolib->syms.SDL_LockAudioDevice(iolib->audio.aid);
  } else {
    iolib->syms.SDL_UnlockAudioDevice(iolib->audio.aid);
  }
} /* Ende sdl2_audio_lock */
