#include "ffmpeg_audio.h"

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

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>

#ifdef WIN32
#define fprintf fprintf_s
#endif

static void write_u16_le(FILE *f, uint16_t v)
{
    fputc((int)(v & 0xff), f);
    fputc((int)((v >> 8) & 0xff), f);
}

static void write_u32_le(FILE *f, uint32_t v)
{
    fputc((int)(v & 0xff), f);
    fputc((int)((v >> 8) & 0xff), f);
    fputc((int)((v >> 16) & 0xff), f);
    fputc((int)((v >> 24) & 0xff), f);
}

static int write_wav_header(FILE *f,
                            int sample_rate,
                            int channels,
                            int bits_per_sample,
                            uint32_t data_size)
{
    const uint32_t byte_rate =
        (uint32_t)(sample_rate * channels * bits_per_sample / 8);
    const uint16_t block_align =
        (uint16_t)(channels * bits_per_sample / 8);

    fwrite("RIFF", 1, 4, f);
    write_u32_le(f, 36u + data_size);
    fwrite("WAVE", 1, 4, f);

    fwrite("fmt ", 1, 4, f);
    write_u32_le(f, 16);              /* fmt chunk size */
    write_u16_le(f, 1);               /* PCM */
    write_u16_le(f, (uint16_t)channels);
    write_u32_le(f, (uint32_t)sample_rate);
    write_u32_le(f, byte_rate);
    write_u16_le(f, block_align);
    write_u16_le(f, (uint16_t)bits_per_sample);

    fwrite("data", 1, 4, f);
    write_u32_le(f, data_size);

    return ferror(f) ? 0 : 1;
}

static int rewrite_wav_header(FILE *f,
                              int sample_rate,
                              int channels,
                              int bits_per_sample,
                              uint32_t data_size)
{
    if (fseek(f, 0, SEEK_SET) != 0) {
        return 0;
    }

    return write_wav_header(f,
                            sample_rate,
                            channels,
                            bits_per_sample,
                            data_size);
}

static void print_if_present(const char *label, const char *value)
{
    if (value && value[0]) {
        printf("%s: %s\n", label, value);
    }
}

int main(int argc, char **argv)
{
    const char *infile;
    const char *outfile;

    fmpg_instance *fmpg = NULL;
    FILE *out = NULL;

    int sample_rate;
    int channels;
    int bits_per_sample;
    uint64_t total_written = 0;

    if (argc != 3) {
        fprintf(stderr, "usage: %s input-audio output.wav\n", argv[0]);
        return 1;
    }

    infile = argv[1];
    outfile = argv[2];

    fprintf(stderr, "ffmpeg compiled version: %s\n", fmpg_ffmpeg_version());
    fprintf(stderr, "ffmpeg runtime version avformat: %d - %s\n", avformat_version(), fmpg_int_version2string(avformat_version()));

    //fprintf(stderr, "ffmpeg runtme config string: %s\n", avformat_configuration());

    fmpg = fmpg_init();
    if (!fmpg) {
        fprintf(stderr, "fmpg_init failed\n");
        return 1;
    }

    if (!fmpg_open_file(fmpg, infile)) {
        fprintf(stderr, "could not open input file: %s\n", infile);
        fmpg_free(fmpg);
        return 1;
    }

    sample_rate = fmpg_audio_sample_rate(fmpg);
    channels = fmpg_audio_channels(fmpg);
    bits_per_sample = fmpg_audio_bits_per_sample(fmpg);

    if (sample_rate <= 0 || channels <= 0 || bits_per_sample != 32) {
        fprintf(stderr, "unexpected audio format reported by decoder\n");
        fmpg_free(fmpg);
        return 1;
    }

    out = fopen(outfile, "wb");
    if (!out) {
        fprintf(stderr, "could not open output file: %s\n", outfile);
        fmpg_free(fmpg);
        return 1;
    }

    /*
     * The final WAV data size is only known after decoding. Write a temporary
     * header now and patch it after the decode loop.
     */
    if (!write_wav_header(out,
                          sample_rate,
                          channels,
                          bits_per_sample,
                          0)) {
        fprintf(stderr, "could not write WAV header\n");
        fclose(out);
        fmpg_free(fmpg);
        return 1;
    }

    while (fmpg_decode_next(fmpg)) {
        const uint8_t *buf = fmpg_buffer(fmpg);
        const int size = fmpg_buffer_size(fmpg);

        if (buf && size > 0) {
            if (fwrite(buf, 1, (size_t)size, out) != (size_t)size) {
                fprintf(stderr, "write error\n");
                fclose(out);
                fmpg_free(fmpg);
                return 1;
            }
            total_written += (uint64_t)size;
        }
    }

    if (total_written > 0xffffffffu) {
        fprintf(stderr,
                "warning: WAV data larger than 4GB; classic WAV header "
                "will overflow\n");
    }

    if (!rewrite_wav_header(out,
                            sample_rate,
                            channels,
                            bits_per_sample,
                            (uint32_t)total_written)) {
        fprintf(stderr, "could not rewrite WAV header\n");
        fclose(out);
        fmpg_free(fmpg);
        return 1;
    }

    fclose(out);

    printf("wrote:       %s\n", outfile);
    printf("sample rate: %d\n", sample_rate);
    printf("channels:    %d\n", channels);
    printf("sample bits: %d\n", bits_per_sample);
    printf("data bytes:  %llu\n", (unsigned long long)total_written);
    printf("samples out: %lld\n", (long long)fmpg_sample_position(fmpg));

    if (fmpg_duration_ms(fmpg) >= 0) {
        printf("duration ms: %lld\n", (long long)fmpg_duration_ms(fmpg));
    }
    if (fmpg_duration_samples(fmpg) >= 0) {
        printf("duration smp:%lld\n", (long long)fmpg_duration_samples(fmpg));
    }

    print_if_present("title", fmpg_file_title(fmpg));
    print_if_present("artist", fmpg_file_author(fmpg));
    print_if_present("album", fmpg_file_album(fmpg));

    fmpg_free(fmpg);
    return 0;
}
