/*
 * Beat Rat -- export encoders
 *
 * WAV reuses our own writer (wav.c) -- no dependency at all. FLAC and
 * MP3 are compiled in only when the Makefile found the corresponding
 * dev library (libflac / libmp3lame); otherwise their code doesn't
 * exist in the binary at all, so there's no way for a missing codec to
 * crash anything at runtime -- the UI just doesn't offer the button.
 */

#include "export.h"
#include "wav.h"
#include "log.h"

#ifdef HAVE_FLAC
#include <FLAC/stream_encoder.h>
#endif
#ifdef HAVE_LAME
#include <lame/lame.h>
#endif

#include <stdio.h>
#include <stdlib.h>

static int export_wav_impl(const char *path, const int16_t *samples, size_t num_samples, uint32_t rate) {
    WavWriter w;
    if (wav_writer_open(&w, path, rate, 1, 16) != 0)
        return -1;
    int rc = wav_writer_write(&w, samples, num_samples);
    wav_writer_close(&w);
    return rc;
}

#ifdef HAVE_FLAC
static int export_flac_impl(const char *path, const int16_t *samples, size_t num_samples, uint32_t rate) {
    FLAC__StreamEncoder *enc = FLAC__stream_encoder_new();
    if (!enc) {
        LOG_ERROR("export: FLAC__stream_encoder_new failed");
        return -1;
    }

    FLAC__stream_encoder_set_channels(enc, 1);
    FLAC__stream_encoder_set_bits_per_sample(enc, 16);
    FLAC__stream_encoder_set_sample_rate(enc, rate);

    FLAC__StreamEncoderInitStatus status = FLAC__stream_encoder_init_file(enc, path, NULL, NULL);
    if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
        LOG_ERROR("export: FLAC init failed (%s)", FLAC__StreamEncoderInitStatusString[status]);
        FLAC__stream_encoder_delete(enc);
        return -1;
    }

    /* FLAC's interleaved-process API wants 32-bit sample buffers even
     * for 16-bit audio -- widen rather than reinterpret. */
    FLAC__int32 *buf32 = malloc(num_samples * sizeof(FLAC__int32));
    if (!buf32) {
        LOG_ERROR("export: out of memory converting samples for FLAC");
        FLAC__stream_encoder_finish(enc);
        FLAC__stream_encoder_delete(enc);
        return -1;
    }
    for (size_t i = 0; i < num_samples; i++)
        buf32[i] = samples[i];

    FLAC__bool ok = FLAC__stream_encoder_process_interleaved(enc, buf32, (unsigned)num_samples);
    free(buf32);

    FLAC__stream_encoder_finish(enc);
    FLAC__stream_encoder_delete(enc);

    if (!ok) {
        LOG_ERROR("export: FLAC encoding failed");
        return -1;
    }
    return 0;
}
#endif

#ifdef HAVE_LAME
static int export_mp3_impl(const char *path, const int16_t *samples, size_t num_samples, uint32_t rate) {
    lame_t gf = lame_init();
    if (!gf) {
        LOG_ERROR("export: lame_init failed");
        return -1;
    }

    lame_set_num_channels(gf, 1);
    lame_set_in_samplerate(gf, (int)rate);
    lame_set_brate(gf, 192); /* reasonable default quality */
    lame_set_mode(gf, MONO);
    if (lame_init_params(gf) < 0) {
        LOG_ERROR("export: lame_init_params failed");
        lame_close(gf);
        return -1;
    }

    FILE *fp = fopen(path, "wb");
    if (!fp) {
        LOG_ERROR("export: could not open '%s' for writing", path);
        lame_close(gf);
        return -1;
    }

    size_t mp3_buf_size = num_samples * 5 / 4 + 7200; /* LAME's documented worst-case size */
    unsigned char *mp3_buf = malloc(mp3_buf_size);
    if (!mp3_buf) {
        LOG_ERROR("export: out of memory allocating MP3 buffer");
        fclose(fp);
        lame_close(gf);
        return -1;
    }

    /* Mono: LAME's non-interleaved API still takes two channel
     * pointers -- passing the same buffer for both is the documented
     * approach when num_channels is set to 1. */
    int bytes = lame_encode_buffer(gf, samples, samples, (int)num_samples, mp3_buf, (int)mp3_buf_size);
    if (bytes < 0) {
        LOG_ERROR("export: lame_encode_buffer failed (%d)", bytes);
        free(mp3_buf);
        fclose(fp);
        lame_close(gf);
        return -1;
    }
    fwrite(mp3_buf, 1, (size_t)bytes, fp);

    int flush_bytes = lame_encode_flush(gf, mp3_buf, (int)mp3_buf_size);
    if (flush_bytes > 0)
        fwrite(mp3_buf, 1, (size_t)flush_bytes, fp);

    free(mp3_buf);
    fclose(fp);
    lame_close(gf);
    return 0;
}
#endif

int export_write(const char *path, ExportFormat format,
                  const int16_t *samples, size_t num_samples, uint32_t rate) {
    switch (format) {
        case EXPORT_FORMAT_WAV:
            return export_wav_impl(path, samples, num_samples, rate);
#ifdef HAVE_FLAC
        case EXPORT_FORMAT_FLAC:
            return export_flac_impl(path, samples, num_samples, rate);
#endif
#ifdef HAVE_LAME
        case EXPORT_FORMAT_MP3:
            return export_mp3_impl(path, samples, num_samples, rate);
#endif
        default:
            LOG_ERROR("export: unknown format %d", (int)format);
            return -1;
    }
}

const char *export_extension(ExportFormat format) {
    switch (format) {
        case EXPORT_FORMAT_WAV: return "wav";
#ifdef HAVE_FLAC
        case EXPORT_FORMAT_FLAC: return "flac";
#endif
#ifdef HAVE_LAME
        case EXPORT_FORMAT_MP3: return "mp3";
#endif
        default: return "bin";
    }
}
