// AudioDecoderGst.cpp: Audio decoding using Gstreamer.
// 
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
//   Free Software Foundation, Inc
// 
// 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 3 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; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

#include "AudioDecoderGst.h"
#include "MediaParser.h" // for AudioInfo
#include "MediaParserGst.h"
#include "GstUtil.h"
#include "FLVParser.h" // for ExtraAudioInfoFlv
#include "SoundInfo.h"

namespace gnash {
namespace media {
namespace gst {


AudioDecoderGst::AudioDecoderGst(SoundInfo& info)
{
    // init GStreamer. TODO: what about doing this in MediaHandlerGst ctor?
    gst_init (nullptr, nullptr);

    GstCaps* srccaps = gst_caps_new_simple ("audio/mpeg",
		"mpegversion", G_TYPE_INT, 1,
		"layer", G_TYPE_INT, 3,
		"rate", G_TYPE_INT, info.getSampleRate(),
		"channels", G_TYPE_INT, info.isStereo() ? 2 : 1, NULL);

    setup(srccaps);

    // FIXME: should we handle other types?
}

AudioDecoderGst::AudioDecoderGst(const AudioInfo& info)
{
    // init GStreamer. TODO: what about doing this in MediaHandlerGst ctor?
    gst_init (nullptr, nullptr);

    GstCaps* srccaps=nullptr;

    if (info.type == CODEC_TYPE_FLASH && info.codec == AUDIO_CODEC_MP3)
    {
        srccaps = gst_caps_new_simple ("audio/mpeg",
		"mpegversion", G_TYPE_INT, 1,
		"layer", G_TYPE_INT, 3,
		"rate", G_TYPE_INT, info.sampleRate,
		"channels", G_TYPE_INT, info.stereo ? 2 : 1, NULL);
        setup(srccaps);
        return;
    }
    
    if (info.type == CODEC_TYPE_FLASH && info.codec == AUDIO_CODEC_NELLYMOSER)
    {
        srccaps = gst_caps_new_simple ("audio/x-nellymoser",
		"rate", G_TYPE_INT, info.sampleRate,
		"channels", G_TYPE_INT, info.stereo ? 2 : 1, NULL);
        setup(srccaps);
        return;
    }

    if (info.type == CODEC_TYPE_FLASH && info.codec == AUDIO_CODEC_ADPCM)
    {
        srccaps = gst_caps_new_simple ("audio/x-adpcm",
                "rate", G_TYPE_INT, info.sampleRate,
                "channels", G_TYPE_INT, info.stereo ? 2 : 1, 
                "layout", G_TYPE_STRING, "swf", NULL);
        setup(srccaps);
        return;
    }

    if (info.type == CODEC_TYPE_FLASH && info.codec == AUDIO_CODEC_AAC)
    {
        srccaps = gst_caps_new_simple ("audio/mpeg",
            "mpegversion", G_TYPE_INT, 4,
            "rate", G_TYPE_INT, 44100,
            "channels", G_TYPE_INT, 2, 
            NULL);

        ExtraAudioInfoFlv* extra = dynamic_cast<ExtraAudioInfoFlv*>(info.extra.get());
        if (extra) {
            GstBuffer* buf = gst_buffer_new_and_alloc(extra->size);
            memcpy(GST_BUFFER_DATA(buf), extra->data.get(), extra->size);
            gst_caps_set_simple (srccaps, "codec_data", GST_TYPE_BUFFER, buf, NULL);

        } else {
            log_error(_("Creating AAC decoder without extra data. This will probably fail!"));
        }

        setup(srccaps);
        return;
    }


    if (info.type == CODEC_TYPE_FLASH) {
		boost::format err = boost::format(
                _("AudioDecoderGst: cannot handle codec %d (%s)")) %
                info.codec %
                (audioCodecType)info.codec;
        throw MediaException(err.str());
    }

    ExtraInfoGst* extraaudioinfo = dynamic_cast<ExtraInfoGst*>(info.extra.get());

    if (!extraaudioinfo) {
		boost::format err = boost::format(
                _("AudioDecoderGst: cannot handle codec %d "
                  "(no ExtraInfoGst attached)")) %
                info.codec;
        throw MediaException(err.str());
    }

    gst_caps_ref(extraaudioinfo->caps);
    setup(extraaudioinfo->caps);
}

AudioDecoderGst::~AudioDecoderGst()
{
    assert(g_queue_is_empty (_decoder.queue));
    swfdec_gst_decoder_push_eos(&_decoder);
    swfdec_gst_decoder_finish(&_decoder);
}

/// Find the best available audio resampler on the system
static std::string 
findResampler()
{
    std::string resampler = "ffaudioresample";

    GstElementFactory* factory = gst_element_factory_find(resampler.c_str());
     
    if (!factory) {
        resampler = "speexresample";
        factory = gst_element_factory_find(resampler.c_str());
        if (!factory) {
            log_error(_("The best available resampler is 'audioresample'."
                      " Please install gstreamer-ffmpeg 0.10.4 or newer, or you"
                      " may experience long delays in audio playback!"));
            resampler = "audioresample";
        }
    }

    if (factory) {
        gst_object_unref(factory);
    }

    return resampler;
}



void AudioDecoderGst::setup(GstCaps* srccaps)
{
    if (!srccaps) {
        throw MediaException(_("AudioDecoderGst: internal error (caps creation failed)"));      
    }

    bool success = GstUtil::check_missing_plugins(srccaps);
    if (!success) {
        GstStructure* sct = gst_caps_get_structure(srccaps, 0);
        std::string type(gst_structure_get_name(sct));
        std::string msg = (boost::format(_("Couldn't find a plugin for "
                    "audio type %s!")) % type).str();

        gst_caps_unref(srccaps);

        throw MediaException(msg);
    }


    GstCaps* sinkcaps = gst_caps_from_string ("audio/x-raw-int, "
            "endianness=byte_order, signed=(boolean)true, width=16, "
            "depth=16, rate=44100, channels=2");
    if (!sinkcaps) {
        throw MediaException(_("AudioDecoderGst: internal error "
                    "(caps creation failed)"));      
    }

    std::string resampler = findResampler();

    success = swfdec_gst_decoder_init (&_decoder, srccaps, sinkcaps, "audioconvert", resampler.c_str(), NULL);
    if (!success) {
        GstStructure* sct = gst_caps_get_structure(srccaps, 0);
        std::string type(gst_structure_get_name(sct));
        std::string msg = (boost::format(
            _("AudioDecoderGst: initialisation failed for audio type %s!"))
            % type).str();
        throw MediaException(msg);
    }

    gst_caps_unref (srccaps);
    gst_caps_unref (sinkcaps);
}

static void
buf_add(gpointer buf, gpointer data)
{
    std::uint32_t* total = (std::uint32_t*) data;

    GstBuffer* buffer = (GstBuffer*) buf;
    *total += GST_BUFFER_SIZE(buffer);
}


/* private */
std::uint8_t*
AudioDecoderGst::pullBuffers(std::uint32_t&  outputSize)
{
    outputSize = 0;
    
    g_queue_foreach(_decoder.queue, buf_add, &outputSize);
  
    if (!outputSize) {
        log_debug(_("Pushed data, but there's nothing to pull (yet)"));
        return nullptr;
    }
    
    std::uint8_t* rbuf = new std::uint8_t[outputSize];
    
    std::uint8_t* ptr = rbuf;
    
    while (true) {
    
        GstBuffer* buffer = swfdec_gst_decoder_pull (&_decoder);
        if (!buffer) {
            break;
        }
    
        memcpy(ptr, GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer));
        ptr += GST_BUFFER_SIZE(buffer);
      
        gst_buffer_unref (buffer);
    }

    return rbuf;    
}

std::uint8_t*
AudioDecoderGst::decode(const std::uint8_t* input, std::uint32_t inputSize,
                        std::uint32_t& outputSize,
                        std::uint32_t& decodedData)
{
    outputSize = decodedData = 0;

    GstBuffer* gstbuf = gst_buffer_new_and_alloc(inputSize);
    memcpy (GST_BUFFER_DATA (gstbuf), input, inputSize);

    bool success = swfdec_gst_decoder_push(&_decoder, gstbuf);
    if (!success) {
        log_error(_("AudioDecoderGst: buffer push failed."));
        return nullptr;
    }

    decodedData = inputSize;

    return pullBuffers(outputSize);
}

std::uint8_t*
AudioDecoderGst::decode(const EncodedAudioFrame& ef, std::uint32_t& outputSize)
{
    outputSize = 0;
    
    GstBuffer* gstbuf;
    
    EncodedExtraGstData* extradata = dynamic_cast<EncodedExtraGstData*>(ef.extradata.get());
    
    if (extradata) {
        gstbuf = extradata->buffer;
    } else {

        gstbuf = gst_buffer_new_and_alloc(ef.dataSize);
        memcpy (GST_BUFFER_DATA (gstbuf), ef.data.get(), ef.dataSize);
    }

    bool success = swfdec_gst_decoder_push(&_decoder, gstbuf);
    if (!success) {
        log_error(_("AudioDecoderGst: buffer push failed."));
        return nullptr;
    }

    return pullBuffers(outputSize);
}


} // gnash.media.gst namespace
} // end of media namespace
} // end of gnash namespace


