/*  This file is part of the KDE project.

Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).

This library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 or 3 of the License.

This library 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/

/*****************************************
 *
 *  This is an aRts plugin for GStreamer
 *
 ****************************************/

#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <gst/audio/gstaudiosink.h>
#include "artssink.h"

QT_BEGIN_NAMESPACE

namespace Phonon
{
namespace Gstreamer
{

static GstStaticPadTemplate sinktemplate =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (
                     "audio/x-raw-int, "
                     "width = (int) { 8, 16 }, "
                     "depth = (int) { 8, 16 }, "
                     "endianness = (int) BYTE_ORDER, "
                     "channels = (int) { 1, 2 }, "
                     "rate = (int) [ 8000, 96000 ]"
                    )
);

typedef int (*Ptr_arts_init)();
typedef arts_stream_t (*Ptr_arts_play_stream)(int, int, int, const char*);
typedef int (*Ptr_arts_close_stream)(arts_stream_t);
typedef int (*Ptr_arts_stream_get)(arts_stream_t, arts_parameter_t_enum);
typedef int (*Ptr_arts_stream_set)(arts_stream_t, arts_parameter_t_enum, int value);
typedef int (*Ptr_arts_write)(arts_stream_t, const void *, int);
typedef int (*Ptr_arts_suspended)();
typedef void (*Ptr_arts_free)();

static Ptr_arts_init p_arts_init = 0;
static Ptr_arts_play_stream p_arts_play_stream = 0;
static Ptr_arts_close_stream p_arts_close_stream = 0;
static Ptr_arts_stream_get p_arts_stream_get= 0;
static Ptr_arts_stream_set p_arts_stream_set= 0;
static Ptr_arts_write p_arts_write = 0;
static Ptr_arts_suspended p_arts_suspended = 0;
static Ptr_arts_free p_arts_free = 0;

static void arts_sink_dispose (GObject * object);
static void arts_sink_reset (GstAudioSink * asink);
static void arts_sink_finalize (GObject * object);
static GstCaps *arts_sink_get_caps (GstBaseSink * bsink);
static gboolean arts_sink_open (GstAudioSink * asink);
static gboolean arts_sink_close (GstAudioSink * asink);
static gboolean arts_sink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec);
static gboolean arts_sink_unprepare (GstAudioSink * asink);
static guint arts_sink_write (GstAudioSink * asink, gpointer data, guint length);
static guint arts_sink_delay (GstAudioSink * asink);

static gboolean connected = false;
static gboolean init = false;
static int sinkCount;

GST_BOILERPLATE (ArtsSink, arts_sink, GstAudioSink, GST_TYPE_AUDIO_SINK)

// ArtsSink args
enum
{
    ARG_0,
    ARG_ARTSSINK
};

/* open the device with given specs */
gboolean arts_sink_open(GstAudioSink *sink)
{
    Q_UNUSED(sink);

    // We already have an open connection to this device
    if (!init) {
        GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE, (NULL), ("Could not connect to aRts", NULL));
        return false;
    } else if (connected) {
        GST_ELEMENT_ERROR (sink, RESOURCE, BUSY, (NULL), ("Device is busy", NULL));
        return false;
    }

    // Check if all symbols were resolved
    if (!(p_arts_init && p_arts_play_stream && p_arts_close_stream
         && p_arts_stream_get && p_arts_stream_set && p_arts_write && p_arts_free))
        return FALSE;

    // Check if arts_init succeeded
    if (!init)
        return false;

    return true;
}

/* prepare resources and state to operate with the given specs */
static gboolean arts_sink_prepare(GstAudioSink *sink, GstRingBufferSpec *spec)
{
    ArtsSink *asink = (ArtsSink*)sink;

    if (!init)
        return false;

    asink->samplerate = spec->rate;
    asink->samplebits = spec->depth;
    asink->channels = spec->channels;
    asink->bytes_per_sample = spec->bytes_per_sample;

    static int id = 0;
    asink->stream = p_arts_play_stream(spec->rate, spec->depth, spec->channels,
                                        QString("gstreamer-%0").arg(id++).toLatin1().constData());
    if (asink->stream)
        connected = true;

    return connected;
}

/* undo anything that was done in prepare() */
static gboolean arts_sink_unprepare(GstAudioSink *sink)
{
    Q_UNUSED(sink);
    ArtsSink *asink = (ArtsSink*)sink;
    if (init && connected) {
        p_arts_close_stream(asink->stream);
        connected = false;
    }
    return true;
}

/* close the device */
static gboolean arts_sink_close(GstAudioSink *sink)
{
    Q_UNUSED(sink);
    return true;
}

/* write samples to the device */
static guint arts_sink_write(GstAudioSink *sink, gpointer data, guint length)
{
    ArtsSink *asink = (ArtsSink*)sink;

    if (!init)
        return 0;

    int errorcode = p_arts_write(asink->stream, (char*)data, length);

    if (errorcode < 0)
        GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL), ("Could not write to device.", NULL));

    return errorcode > 0 ? errorcode : 0;
}

/* get number of samples queued in the device */
static guint arts_sink_delay(GstAudioSink *sink)
{
    ArtsSink *asink = (ArtsSink*)sink;
    if (!init)
        return 0;

    // We get results in millisecons so we have to caculate the approximate size in samples
    guint delay = p_arts_stream_get(asink->stream, ARTS_P_SERVER_LATENCY) * (asink->samplerate / 1000);
    return delay;
}

/* reset the audio device, unblock from a write */
static void arts_sink_reset(GstAudioSink *sink)
{
    // ### We are currently unable to gracefully recover
    // after artsd has been restarted or killed.
    Q_UNUSED(sink);
}

// Register element details
static void arts_sink_base_init (gpointer g_class) {
    GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
    static gchar longname[] = "Experimental aRts sink",
                    klass[] = "Sink/Audio",
              description[] = "aRts Audio Output Device",
                   author[] = "Nokia Corporation and/or its subsidiary(-ies) <qt-info@nokia.com>";
    GstElementDetails details = GST_ELEMENT_DETAILS (longname,
                                          klass,
                                          description,
                                          author);
    gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&sinktemplate));
    gst_element_class_set_details (gstelement_class, &details);
}

static void arts_sink_class_init (ArtsSinkClass * klass)
{
    parent_class = (GstAudioSinkClass*)g_type_class_peek_parent(klass);

    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
    gobject_class->finalize = GST_DEBUG_FUNCPTR (arts_sink_finalize);
    gobject_class->dispose = GST_DEBUG_FUNCPTR (arts_sink_dispose);

    GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass;
    gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (arts_sink_get_caps);

    GstAudioSinkClass *gstaudiosink_class = (GstAudioSinkClass*)klass;
    gstaudiosink_class->open =      GST_DEBUG_FUNCPTR(arts_sink_open);
    gstaudiosink_class->prepare =   GST_DEBUG_FUNCPTR(arts_sink_prepare);
    gstaudiosink_class->unprepare = GST_DEBUG_FUNCPTR(arts_sink_unprepare);
    gstaudiosink_class->close =     GST_DEBUG_FUNCPTR(arts_sink_close);
    gstaudiosink_class->write =     GST_DEBUG_FUNCPTR(arts_sink_write);
    gstaudiosink_class->delay =     GST_DEBUG_FUNCPTR(arts_sink_delay);
    gstaudiosink_class->reset =     GST_DEBUG_FUNCPTR(arts_sink_reset);
}

static void arts_sink_init (ArtsSink * src, ArtsSinkClass * g_class)
{
    Q_UNUSED(g_class);
    GST_DEBUG_OBJECT (src, "initializing artssink");
    src->stream = 0;
#ifndef QT_NO_LIBRARY
    p_arts_init =  (Ptr_arts_init)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_init");
    p_arts_play_stream =  (Ptr_arts_play_stream)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_play_stream");
    p_arts_close_stream =  (Ptr_arts_close_stream)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_close_stream");
    p_arts_stream_get =  (Ptr_arts_stream_get)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_stream_get");
    p_arts_stream_set =  (Ptr_arts_stream_set)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_stream_set");
    p_arts_write =  (Ptr_arts_write)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_write");
    p_arts_suspended =  (Ptr_arts_suspended)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_suspended");
    p_arts_free =  (Ptr_arts_free)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_free");

    if (!sinkCount) {
        int errorcode = p_arts_init();
        if (!errorcode) {
            init = TRUE;
        }
    }
    sinkCount ++;
#endif //QT_NO_LIBRARY
}

static void arts_sink_dispose (GObject * object)
{
    Q_UNUSED(object);
    if (--sinkCount == 0) {
        p_arts_free();
    }
}

static void arts_sink_finalize (GObject * object)
{
    G_OBJECT_CLASS (parent_class)->finalize (object);
}

static GstCaps *arts_sink_get_caps (GstBaseSink * bsink)
{
    Q_UNUSED(bsink);
    return NULL;
}

}
} //namespace Phonon::Gstreamer

QT_END_NAMESPACE
