/*
 * This file is part of mpv.
 *
 * mpv 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.
 *
 * mpv 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 mpv.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <assert.h>

#include <libavutil/mem.h>

#include "demux/codec_tags.h"

#include "common/codecs.h"
#include "common/msg.h"
#include "misc/bstr.h"

#include "stream/stream.h"
#include "demux/demux.h"

#include "demux/stheader.h"

#include "dec_audio.h"
#include "ad.h"
#include "audio/format.h"
#include "audio/audio.h"
#include "audio/audio_buffer.h"

#include "audio/filter/af.h"

extern const struct ad_functions ad_lavc;
extern const struct ad_functions ad_spdif;

static const struct ad_functions * const ad_drivers[] = {
    &ad_lavc,
    &ad_spdif,
    NULL
};

static void uninit_decoder(struct dec_audio *d_audio)
{
    audio_reset_decoding(d_audio);
    if (d_audio->ad_driver) {
        MP_VERBOSE(d_audio, "Uninit audio decoder.\n");
        d_audio->ad_driver->uninit(d_audio);
    }
    d_audio->ad_driver = NULL;
    talloc_free(d_audio->priv);
    d_audio->priv = NULL;
    d_audio->afilter->initialized = -1;
    d_audio->decode_format = (struct mp_audio){0};
}

static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
{
    if (!d_audio->ad_driver->init(d_audio, decoder)) {
        MP_VERBOSE(d_audio, "Audio decoder init failed.\n");
        d_audio->ad_driver = NULL;
        uninit_decoder(d_audio);
        return 0;
    }

    return 1;
}

struct mp_decoder_list *audio_decoder_list(void)
{
    struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
    for (int i = 0; ad_drivers[i] != NULL; i++)
        ad_drivers[i]->add_decoders(list);
    return list;
}

static struct mp_decoder_list *audio_select_decoders(struct dec_audio *d_audio)
{
    struct MPOpts *opts = d_audio->opts;
    const char *codec = d_audio->header->codec;

    struct mp_decoder_list *list = audio_decoder_list();
    struct mp_decoder_list *new =
        mp_select_decoders(list, codec, opts->audio_decoders);
    if (d_audio->spdif_passthrough) {
        struct mp_decoder_list *spdif =
            mp_select_decoder_list(list, codec, "spdif", opts->audio_spdif);
        mp_append_decoders(spdif, new);
        talloc_free(new);
        new = spdif;
    }
    talloc_free(list);
    return new;
}

static const struct ad_functions *find_driver(const char *name)
{
    for (int i = 0; ad_drivers[i] != NULL; i++) {
        if (strcmp(ad_drivers[i]->name, name) == 0)
            return ad_drivers[i];
    }
    return NULL;
}

int audio_init_best_codec(struct dec_audio *d_audio)
{
    uninit_decoder(d_audio);
    assert(!d_audio->ad_driver);

    struct mp_decoder_entry *decoder = NULL;
    struct mp_decoder_list *list = audio_select_decoders(d_audio);

    mp_print_decoders(d_audio->log, MSGL_V, "Codec list:", list);

    for (int n = 0; n < list->num_entries; n++) {
        struct mp_decoder_entry *sel = &list->entries[n];
        const struct ad_functions *driver = find_driver(sel->family);
        if (!driver)
            continue;
        MP_VERBOSE(d_audio, "Opening audio decoder %s:%s\n",
                   sel->family, sel->decoder);
        d_audio->ad_driver = driver;
        if (init_audio_codec(d_audio, sel->decoder)) {
            decoder = sel;
            break;
        }
        MP_WARN(d_audio, "Audio decoder init failed for "
                "%s:%s\n", sel->family, sel->decoder);
    }

    if (d_audio->ad_driver) {
        d_audio->decoder_desc =
            talloc_asprintf(d_audio, "%s [%s:%s]", decoder->desc, decoder->family,
                            decoder->decoder);
        MP_VERBOSE(d_audio, "Selected audio codec: %s\n", d_audio->decoder_desc);
    } else {
        MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
               d_audio->header->codec ? d_audio->header->codec : "<unknown>");
    }

    talloc_free(list);
    return !!d_audio->ad_driver;
}

void audio_uninit(struct dec_audio *d_audio)
{
    if (!d_audio)
        return;
    MP_VERBOSE(d_audio, "Uninit audio filters...\n");
    uninit_decoder(d_audio);
    af_destroy(d_audio->afilter);
    talloc_free(d_audio->waiting);
    talloc_free(d_audio);
}

static int decode_new_frame(struct dec_audio *da)
{
    while (!da->waiting) {
        int ret = da->ad_driver->decode_packet(da, &da->waiting);
        if (ret < 0)
            return ret;

        if (da->waiting) {
            if (da->waiting->pts != MP_NOPTS_VALUE) {
                if (da->pts != MP_NOPTS_VALUE) {
                    da->pts += da->pts_offset / (double)da->waiting->rate;
                    da->pts_offset = 0;
                }
                // Keep the interpolated timestamp if it doesn't deviate more
                // than 1 ms from the real one. (MKV rounded timestamps.)
                if (da->pts == MP_NOPTS_VALUE || da->pts_offset != 0 ||
                    fabs(da->pts - da->waiting->pts) > 0.001)
                {
                    da->pts = da->waiting->pts;
                    da->pts_offset = 0;
                }
            }
            da->pts_offset += da->waiting->samples;
            da->decode_format = *da->waiting;
            mp_audio_set_null_data(&da->decode_format);
        }

        if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps)
            da->pts = 0;
    }
    return mp_audio_config_valid(da->waiting) ? AD_OK : AD_ERR;
}

/* Decode packets until we know the audio format. Then reinit the buffer.
 * Returns AD_OK on success, negative AD_* code otherwise.
 * Also returns AD_OK if already initialized (and does nothing).
 */
int initial_audio_decode(struct dec_audio *da)
{
    return decode_new_frame(da);
}

static bool copy_output(struct af_stream *afs, struct mp_audio_buffer *outbuf,
                        int minsamples, bool eof)
{
    while (mp_audio_buffer_samples(outbuf) < minsamples) {
        if (af_output_frame(afs, eof) < 0)
            return true; // error, stop doing stuff
        struct mp_audio *mpa = af_read_output_frame(afs);
        if (!mpa)
            return false; // out of data
        mp_audio_buffer_append(outbuf, mpa);
        talloc_free(mpa);
    }
    return true;
}

/* Try to get at least minsamples decoded+filtered samples in outbuf
 * (total length including possible existing data).
 * Return 0 on success, or negative AD_* error code.
 * In the former case outbuf has at least minsamples buffered on return.
 * In case of EOF/error it might or might not be. */
int audio_decode(struct dec_audio *da, struct mp_audio_buffer *outbuf,
                 int minsamples)
{
    struct af_stream *afs = da->afilter;
    if (afs->initialized < 1)
        return AD_ERR;

    MP_STATS(da, "start audio");

    int res;
    while (1) {
        res = 0;

        if (copy_output(afs, outbuf, minsamples, false))
            break;

        res = decode_new_frame(da);
        if (res < 0) {
            // drain filters first (especially for true EOF case)
            copy_output(afs, outbuf, minsamples, true);
            break;
        }

        // On format change, make sure to drain the filter chain.
        if (!mp_audio_config_equals(&afs->input, da->waiting)) {
            copy_output(afs, outbuf, minsamples, true);
            res = AD_NEW_FMT;
            break;
        }

        struct mp_audio *mpa = da->waiting;
        da->waiting = NULL;
        if (af_filter_frame(afs, mpa) < 0)
            return AD_ERR;
    }

    MP_STATS(da, "end audio");

    return res;
}

void audio_reset_decoding(struct dec_audio *d_audio)
{
    if (d_audio->ad_driver)
        d_audio->ad_driver->control(d_audio, ADCTRL_RESET, NULL);
    af_seek_reset(d_audio->afilter);
    d_audio->pts = MP_NOPTS_VALUE;
    d_audio->pts_offset = 0;
    if (d_audio->waiting) {
        talloc_free(d_audio->waiting);
        d_audio->waiting = NULL;
    }
}
