/* 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 "vgi.h"

VG_BOOL flac2wav(const char *, char **, size_t *);

#ifndef VGI_DISABLE_FLAC
#include <FLAC/all.h>

struct fl2w {
  char *memptr;
  size_t memsize, mempos;
  unsigned int total_samples;
  unsigned int sample_rate;
  unsigned int channels;
  unsigned int bits_per_sample;
};

static int mempalloc(struct fl2w *, int);
static int wr8(struct fl2w *, unsigned int);
static int wr16(struct fl2w *, unsigned int);
static int wr32(struct fl2w *, unsigned int);
static int wrchar(struct fl2w *, const char *);
static void errcbk(const FLAC__StreamDecoder *, FLAC__StreamDecoderErrorStatus, void *);
static void mtcbk(const FLAC__StreamDecoder *, const FLAC__StreamMetadata *, void *);
static FLAC__StreamDecoderWriteStatus wrcbk(const FLAC__StreamDecoder *, const FLAC__Frame *, const FLAC__int32 * const [], void *);


/* flac2wav:
 * konvertiert FLAC-Datei in WAVE-Memory-Puffer
 * @param file  FLAC-Datei
 * @param memptr  fuer Rueckgabe WAVE-Memory-Puffer (muss dealloziert werden)
 * @param memlen  fuer Rueckgabe Byteanzahl von memptr
 * @return        VG_TRUE = OK oder VG_FALSE = Fehler
 */
VG_BOOL
flac2wav(const char *file, char **memptr, size_t *memlen)
{
  struct fl2w f2w;
  FLAC__StreamDecoder *sdec;
  FLAC__StreamDecoderInitStatus init_status;
  FLAC__bool ok;

  if (file == NULL || *file == '\0') { outerr("no file given"); return VG_FALSE; }
  if (memptr == NULL || memlen == NULL) { outerr("parameter NULL"); return VG_FALSE; }

  memset(&f2w, 0, sizeof(f2w));

  sdec = FLAC__stream_decoder_new();
  if (sdec == NULL) { outerr("cannot allocate memory"); return VG_FALSE; }

  init_status = FLAC__stream_decoder_init_file(sdec, file, wrcbk, mtcbk, errcbk, &f2w);
  if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
    outerr("error initializing decoder: %s", FLAC__StreamDecoderInitStatusString[init_status]);
    FLAC__stream_decoder_delete(sdec);
    return VG_FALSE;
  }

  ok = FLAC__stream_decoder_process_until_end_of_stream(sdec);
  if (ok) { FLAC__stream_decoder_finish(sdec); }

  FLAC__stream_decoder_delete(sdec);

  if (ok) {
    *memptr = f2w.memptr;
    *memlen = f2w.mempos;
    return VG_TRUE;
  }

  if (f2w.memptr != NULL) { free(f2w.memptr); }
  outerr("error decoding flac-file \"%s\"", file);
  return VG_FALSE;
} /* flac2wav */


/* (re)alloziert WAVE-Memory-Buffer */
static int
mempalloc(struct fl2w *f2w, int bytes)
{
  const size_t block = 4096;
  size_t plus;

  if (f2w == NULL || bytes <= 0) { return 0; }

  plus = (((size_t)bytes - 1) / block) * block + block;

  if (f2w->memptr == NULL) {
    f2w->memptr = malloc(plus);
    f2w->memsize = plus;
  } else if (f2w->memsize < f2w->mempos + bytes) {
    f2w->memptr = realloc(f2w->memptr, f2w->mempos + plus);
    f2w->memsize += plus;
  }
  if (f2w->memptr == NULL) { f2w->memsize = 0; return -1; }

  return 0;
} /* Ende mempalloc */


/* setzt uChar in WAVE-Memory-Buffer */
static int
wr8(struct fl2w *f2w, unsigned int z1)
{
  if (f2w == NULL) { return 0; }
  if (mempalloc(f2w, 1) < 0) { return -1; }
  f2w->memptr[f2w->mempos++] = (unsigned char)z1;
  return 0;
} /* Ende wr8 */


/* setzt Short in WAVE-Memory-Buffer */
static int
wr16(struct fl2w *f2w, unsigned int z1)
{
  union { unsigned short h; unsigned char c[2]; } u1;

  if (f2w == NULL) { return 0; }
  if (mempalloc(f2w, 2) < 0) { return -1; }

  u1.h = (unsigned short)z1;

#ifdef SML3_ENDIAN_IS_LITTLE
    f2w->memptr[f2w->mempos++] = u1.c[0];
    f2w->memptr[f2w->mempos++] = u1.c[1];
#else
    f2w->memptr[f2w->mempos++] = u1.c[1];
    f2w->memptr[f2w->mempos++] = u1.c[0];
#endif
  return 0;
} /* Ende wr16 */


/* setzt Integer in WAVE-Memory-Buffer */
static int
wr32(struct fl2w *f2w, unsigned int z1)
{
  union { unsigned int i; unsigned char c[4]; } u1;

  if (f2w == NULL) { return 0; }
  if (mempalloc(f2w, 4) < 0) { return -1; }

  u1.i = z1;

#ifdef SML3_ENDIAN_IS_LITTLE
    f2w->memptr[f2w->mempos++] = u1.c[0];
    f2w->memptr[f2w->mempos++] = u1.c[1];
    f2w->memptr[f2w->mempos++] = u1.c[2];
    f2w->memptr[f2w->mempos++] = u1.c[3];
#else
    f2w->memptr[f2w->mempos++] = u1.c[3];
    f2w->memptr[f2w->mempos++] = u1.c[2];
    f2w->memptr[f2w->mempos++] = u1.c[1];
    f2w->memptr[f2w->mempos++] = u1.c[0];
#endif
  return 0;
} /* Ende wr32 */


/* setzt String in WAVE-Memory-Buffer */
static int
wrchar(struct fl2w *f2w, const char *s1)
{
  size_t len;

  if (f2w == NULL || s1 == NULL) { return 0; }

  len = strlen(s1);
  if (mempalloc(f2w, (int)len) < 0) { return -1; }

  memmove(f2w->memptr + f2w->mempos, s1, len);
  f2w->mempos += len;

  return 0;
} /* Ende wrchar */


/* Fehler-Callback-Funktion fuer Decoder */
static void
errcbk(const FLAC__StreamDecoder *sdec, FLAC__StreamDecoderErrorStatus status, void *vptr)
{
  outerr("error decoding: %s", FLAC__StreamDecoderErrorStatusString[status]);
  (void)sdec; (void)vptr;
} /* Ende errcbk */


/* Metadata-Callback-Funktion fuer Decoder */
static void
mtcbk(const FLAC__StreamDecoder *sdec, const FLAC__StreamMetadata *metadata, void *vptr)
{
  struct fl2w *f2w = (struct fl2w *)vptr;

  if (f2w == NULL) { return; }

  if (metadata != NULL && metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
    f2w->total_samples = metadata->data.stream_info.total_samples;
    f2w->sample_rate = metadata->data.stream_info.sample_rate;
    f2w->channels = metadata->data.stream_info.channels;
    f2w->bits_per_sample = metadata->data.stream_info.bits_per_sample;
  }

  (void)sdec; (void)vptr;
} /* Ende mtcbk */


/* Callback-Funktion fuer Ausgabe decodierter Daten */
static FLAC__StreamDecoderWriteStatus
wrcbk(const FLAC__StreamDecoder *sdec, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *vptr)
{
  struct fl2w *f2w = (struct fl2w *)vptr;
  size_t s1;

  if (f2w == NULL) { errno = EINVAL; return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }

#if 0
  assert(frame->header.sample_rate == f2w->sample_rate);
  assert(frame->header.channels == f2w->channels);
  assert(frame->header.bits_per_sample == f2w->bits_per_sample);
#endif

  /* vor dem ersten Frame WAVE-Header setzen */
  if (frame->header.number.sample_number == 0) {
    FLAC__uint32 total_size;

    if (f2w->total_samples == 0) {
      outerr("total number of samples missing in FLAC-file");
      return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
    }

    total_size = (FLAC__uint32)(f2w->total_samples * f2w->channels * (f2w->bits_per_sample / 8));

    if (wrchar(f2w, "RIFF") < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wr32(f2w, total_size + 36) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wrchar(f2w, "WAVEfmt ") < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wr32(f2w, 16) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wr16(f2w, 1) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wr16(f2w, (FLAC__uint16)f2w->channels) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wr32(f2w, f2w->sample_rate) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wr32(f2w, f2w->sample_rate * f2w->channels * (f2w->bits_per_sample / 8)) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wr16(f2w, (FLAC__uint16)(f2w->channels * (f2w->bits_per_sample / 8))) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wr16(f2w, (FLAC__uint16)f2w->bits_per_sample) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wrchar(f2w, "data") < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
    if (wr32(f2w, total_size) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
  }

  /* Daten setzen */
  for (s1 = 0; s1 < frame->header.blocksize; s1++) {
    if (f2w->bits_per_sample == 8) {  /* Bitsize 8 */
      /* linker oder einziger Kanal */
      if (wr8(f2w, (FLAC__uint8)buffer[0][s1] - 128) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
      if (f2w->channels > 1) {
        /* rechter Kanal */
        if (wr8(f2w, (FLAC__uint8)buffer[1][s1] - 128) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
      }
    } else {  /* Bitsize 16 */
      /* linker oder einziger Kanal */
      if (wr16(f2w, (FLAC__uint16)buffer[0][s1]) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
      if (f2w->channels > 1) {
        /* rechter Kanal */
        if (wr16(f2w, (FLAC__uint16)buffer[1][s1]) < 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; }
      }
    }
  }

  (void)sdec;

  return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
} /* Ende wrcbk */

#else

VG_BOOL
flac2wav(const char *file, char **memptr, size_t *memlen)
{
  (void)memptr;
  (void)memlen;
  outerr("No support for flac compiled in (%s)", file);
  return VG_FALSE;
}

#endif
