/*
#   avcodecdecode.c: decodes wma, mp4, and other file formats for xlplayer
#   Copyright (C) 2007-2022 Stephen Fairchild (s-fairchild@users.sf.net)
#
#   This program 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.
#
#   This program 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 this program in the file entitled COPYING.
#   If not, see <http://www.gnu.org/licenses/>.
*/

#include "../config.h"

#ifdef HAVE_LIBAV

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include "main.h"
#include "xlplayer.h"
#include "avcodecdecode.h"

#define TRUE 1
#define FALSE 0
#define ACCEPTED 1
#define REJECTED 0

#ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE
#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000
#endif


extern int dynamic_metadata_form[];

static const struct timespec time_delay = { .tv_nsec = 10 };


static void write_frame(struct xlplayer *xlplayer, AVFrame *frame)
    {
    struct avcodecdecode_vars *self = xlplayer->dec_data;
    int frames;
    int channels = self->c->ch_layout.nb_channels;
    SRC_DATA *src_data = &xlplayer->src_data;

    if (channels == 0) {
        fprintf(stderr, "avcodecdecode_play: number of channels is 0\n");
        xlplayer->playmode = PM_EJECTING;
    }

    if (!self->swr)
        {
        if (!(self->swr = swr_alloc()))
            {
            fprintf(stderr, "avcodecdecode_play: call to swr_alloc failed\n");
            xlplayer->playmode = PM_EJECTING;
            return;
            }

        av_opt_set_chlayout(self->swr, "in_chlayout", &self->c->ch_layout, 0);
        av_opt_set_chlayout(self->swr, "out_chlayout", &self->out_ch_layout, 0);
        av_opt_set_sample_fmt(self->swr, "in_sample_fmt", self->c->sample_fmt, 0);
        av_opt_set_sample_fmt(self->swr, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
        av_opt_set_int(self->swr, "in_sample_rate", self->c->sample_rate, 0);
        /* We are using libswresample only for the channel layout conversion
         * therefore the output sample rate must match the input. */
        av_opt_set_int(self->swr, "out_sample_rate", self->c->sample_rate, 0);

        if (swr_init(self->swr))
            {
            fprintf(stderr, "avcodecdecode_init: swr_init failed\n");
            xlplayer->playmode = PM_EJECTING;
            return;
            }
        }

    if (self->floatsamples)
        av_freep(&self->floatsamples);

    if (av_samples_alloc(&self->floatsamples, NULL, 2, frame->nb_samples, AV_SAMPLE_FMT_FLT, 0) < 0)
        {
        fprintf(stderr, "avcodecdecode_play: av_samples_alloc failed\n");
        xlplayer->playmode = PM_EJECTING;
        return;
        }

    swr_convert(self->swr, &self->floatsamples, frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples);

    if (self->resample)
        {
        src_data->input_frames = frame->nb_samples;
        src_data->data_in = (float *)self->floatsamples;
        if (src_process(xlplayer->src_state, src_data))
            {
            fprintf(stderr, "avcodecdecode_play: error occured during resampling\n");
            xlplayer->playmode = PM_EJECTING;
            return;
            }
        xlplayer_demux_channel_data(xlplayer, src_data->data_out, frames = src_data->output_frames_gen, self->channels, 1.f);
        }
    else
        xlplayer_demux_channel_data(xlplayer, (float *)self->floatsamples, frames = frame->nb_samples, self->channels, 1.f);

    if (self->drop > 0)
        self->drop -= frames / (float)xlplayer->samplerate;
    else
        xlplayer_write_channel_data(xlplayer);
    }


static void avcodecdecode_eject(struct xlplayer *xlplayer)
    {
    struct avcodecdecode_vars *self = xlplayer->dec_data;

    fprintf(stderr, "started eject\n");
    if (self->pkt.data)
        av_packet_unref(&self->pkt);
    if (self->resample)
        {
        xlplayer->src_state = src_delete(xlplayer->src_state);
        free(xlplayer->src_data.data_out);
        }
    if (self->floatsamples)
        free(self->floatsamples);
    if (self->swr)
        swr_free(&self->swr);
    while (pthread_mutex_trylock(&g.avc_mutex))
        nanosleep(&time_delay, NULL);
    avcodec_free_context(&self->c);
    pthread_mutex_unlock(&g.avc_mutex);
    avformat_close_input(&self->ic);
    av_channel_layout_uninit(&self->out_ch_layout);
    free(self);
    fprintf(stderr, "finished eject\n");
    }

static void avcodecdecode_init(struct xlplayer *xlplayer)
    {
    struct avcodecdecode_vars *self = xlplayer->dec_data;
    int src_error;

    if (xlplayer->seek_s)
        {
        av_seek_frame(self->ic, -1, (int64_t)xlplayer->seek_s * AV_TIME_BASE, 0);
        switch (self->c->codec_id)
            {
            case AV_CODEC_ID_MUSEPACK7:   /* add formats here that glitch when seeked */
            case AV_CODEC_ID_MUSEPACK8:
                self->drop = 1.6;
                fprintf(stderr, "dropping %0.2f seconds of audio\n", self->drop);
            default:
                break;
            }
        }

    switch(self->c->ch_layout.nb_channels) {
        case 2:
            av_channel_layout_from_string(&self->out_ch_layout, "stereo");
            self->channels = 2;
            break;
        case 1:
            av_channel_layout_from_string(&self->out_ch_layout, "mono");
            self->channels = 1;
            break;
        default:
            av_channel_layout_from_string(&self->out_ch_layout, "downmix");
            self->channels = 2;
    }

    if ((self->resample = (self->c->sample_rate != (int)xlplayer->samplerate)))
        {
        fprintf(stderr, "configuring resampler\n");
        xlplayer->src_data.src_ratio = (double)xlplayer->samplerate / (double)self->c->sample_rate;
        xlplayer->src_data.end_of_input = 0;

        const size_t dsiz = AVCODEC_MAX_AUDIO_FRAME_SIZE * self->channels * xlplayer->src_data.src_ratio + 512;

        xlplayer->src_data.output_frames = dsiz / (sizeof (float) * self->channels);
        if (!(xlplayer->src_data.data_out = malloc(dsiz)))
            {
            fprintf(stderr, "avcodecdecode_init: malloc failure\n");
            self->resample = FALSE;
            avcodecdecode_eject(xlplayer);
            xlplayer->playmode = PM_STOPPED;
            xlplayer->command = CMD_COMPLETE;
            return;
            }
        if ((xlplayer->src_state = src_new(xlplayer->rsqual, self->channels, &src_error)), src_error)
            {
            fprintf(stderr, "avcodecdecode_init: src_new reports %s\n", src_strerror(src_error));
            free(xlplayer->src_data.data_out);
            self->resample = FALSE;
            avcodecdecode_eject(xlplayer);
            xlplayer->playmode = PM_STOPPED;
            xlplayer->command = CMD_COMPLETE;
            return;
            }
        }

    fprintf(stderr, "avcodecdecode_init: completed\n");
    }


static void avcodecdecode_play(struct xlplayer *xlplayer)
{
    struct avcodecdecode_vars *self = xlplayer->dec_data;
    int channels = self->c->ch_layout.nb_channels;
    SRC_DATA *src_data = &xlplayer->src_data;

    for(;;) {
        switch(avcodec_receive_frame(self->c, &self->af)) {
            case 0:
                /* successfully got an audio frame */
                write_frame(xlplayer, &self->af);
                return;
            case AVERROR(EAGAIN):
                for(;;)
                    {
                    /* obtain a new packet */
                    if (av_read_frame(self->ic, &self->pkt) < 0 || self->pkt.size == 0)
                        goto cleanup;
                    /* drop this packet if it's not for our audio stream */
                    if (self->pkt.stream_index != (int)self->stream)
                        {
                        if (self->pkt.data)
                            av_packet_unref(&self->pkt);
                        }
                    else
                        break;
                    }
                /* feed in the audio packet */
                if (avcodec_send_packet(self->c, &self->pkt))
                    {
                    fprintf(stderr, "avcodecdecode_play: avcodec_send_packet failed\n");
                    goto cleanup;
                    }
                av_packet_unref(&self->pkt);
                /* update metadata if needed (checking once per packet) */
                int delay = xlplayer_calc_rbdelay(xlplayer);
                struct chapter *chapter = mp3_tag_chapter_scan(&self->taginfo, xlplayer->play_progress_ms + delay);
                if (chapter && chapter != self->current_chapter)
                    {
                    self->current_chapter = chapter;
                    xlplayer_set_dynamic_metadata(xlplayer, dynamic_metadata_form[chapter->title.encoding], chapter->artist.text, chapter->title.text, chapter->album.text, delay);
                    }
                break;

            case AVERROR(EINVAL):
                fprintf(stderr, "avcodecdecode_play: wtf happened?\n");
                goto cleanup;

            case AVERROR_EOF:
                /* terminal packet received */
                fprintf(stderr, "avcodecdecode_play: EOF\n");
                goto cleanup;

            default:
                fprintf(stderr, "avcodecdecode_play: unexpected error\n");
                goto cleanup;
            }
        }
    return;

cleanup:
    av_frame_unref(&self->af);

    if (self->pkt.data)
        av_packet_unref(&self->pkt);

    if (self->resample)       /* flush the resampler */
        {
        src_data->end_of_input = TRUE;
        src_data->input_frames = 0;
        if (src_process(xlplayer->src_state, src_data))
            {
            fprintf(stderr, "avcodecdecode_play: error occured during resampling\n");
            xlplayer->playmode = PM_EJECTING;
            return;
            }
        xlplayer_demux_channel_data(xlplayer, src_data->data_out, src_data->output_frames_gen, channels, 1.f);
        xlplayer_write_channel_data(xlplayer);
        }
    xlplayer->playmode = PM_FLUSH;
}


int avcodecdecode_reg(struct xlplayer *xlplayer)
    {
    struct avcodecdecode_vars *self;
    FILE *fp;
    struct chapter *chapter;
    int ret;

    if (!(xlplayer->dec_data = self = calloc(1, sizeof (struct avcodecdecode_vars))))
        {
        fprintf(stderr, "avcodecdecode_reg: malloc failure\n");
        return REJECTED;
        }

    if ((fp = fopen(xlplayer->pathname, "r")))
        {
        mp3_tag_read(&self->taginfo, fp);
        if ((chapter = mp3_tag_chapter_scan(&self->taginfo, xlplayer->play_progress_ms + 70)))
            {
            self->current_chapter = chapter;
            xlplayer_set_dynamic_metadata(xlplayer, dynamic_metadata_form[chapter->title.encoding], chapter->artist.text, chapter->title.text, chapter->album.text, 70);
            }
        fclose(fp);
        }

    if (avformat_open_input(&self->ic, xlplayer->pathname, NULL, NULL) < 0)
        {
        fprintf(stderr, "avcodecdecode_reg: failed to open input file %s\n", xlplayer->pathname);
        free(self);
        return REJECTED;
        }

    while (pthread_mutex_trylock(&g.avc_mutex))
        nanosleep(&time_delay, NULL);
    ret = avformat_find_stream_info(self->ic, NULL);
    pthread_mutex_unlock(&g.avc_mutex);

    if (ret < 0)
        {
        fprintf(stderr, "avcodecdecode_reg: call to avformat_find_stream_info failed\n");
        avformat_close_input(&self->ic);
        free(self);
        return REJECTED;
        }

    while (pthread_mutex_trylock(&g.avc_mutex))
        nanosleep(&time_delay, NULL);

    self->stream = av_find_best_stream(self->ic, AVMEDIA_TYPE_AUDIO, -1, -1, &self->codec, 0);

    pthread_mutex_unlock(&g.avc_mutex);

    if (self->stream < 0)
        {
        fprintf(stderr, "Cannot find an audio stream in the input file\n");
        avformat_close_input(&self->ic);
        free(self);
        return REJECTED;
        }

    /* Allocate a codec context for the decoder */
    self->c = avcodec_alloc_context3(self->codec);
    if (!(self->c = avcodec_alloc_context3(self->codec))) {
        fprintf(stderr, "failed to allocate the codec context\n");
        avformat_close_input(&self->ic);
        free(self);
        return REJECTED;
    }
    /* Copy codec parameters from input stream to output codec context */
    if (avcodec_parameters_to_context(self->c, self->ic->streams[self->stream]->codecpar) < 0) {
        fprintf(stderr, "Failed to copy codec parameters to decoder context\n");
        avformat_close_input(&self->ic);
        free(self);
        return REJECTED;

    }

    while (pthread_mutex_trylock(&g.avc_mutex))
        nanosleep(&time_delay, NULL);
    if (avcodec_open2(self->c, self->codec, NULL) < 0)
        {
        pthread_mutex_unlock(&g.avc_mutex);
        fprintf(stderr, "avcodecdecode_reg: could not open codec\n");
        avformat_close_input(&self->ic);
        free(self);
        return REJECTED;
        }
    pthread_mutex_unlock(&g.avc_mutex);

    xlplayer->dec_init = avcodecdecode_init;
    xlplayer->dec_play = avcodecdecode_play;
    xlplayer->dec_eject = avcodecdecode_eject;

    return ACCEPTED;
    }

#endif /* HAVE_LIBAV */
