/*  This file is part of the QbtGStreamer project, a Qt GStreamer Wrapper
    Copyright (C) 2006 Tim Beaulen <tbscope@gmail.com>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License version 2 as published by the Free Software Foundation.

    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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "qbtgstreamerelement.h"

#include <QCustomEvent>
#include <QApplication>
#include <QtDebug>
#include <QTimer>

#include "qbtgstreamercallbackmanager.h"

QbtGStreamerElement* QbtGStreamerElement::s_instance;

QbtGStreamerElement::QbtGStreamerElement() : QbtGStreamerObject(), d(new QbtGStreamerElementPrivate)
{
    s_instance = this;
    //qDebug() << s_instance << "created";
}

QbtGStreamerElement::QbtGStreamerElement(const QbtGStreamerElement& obj) : QbtGStreamerObject(), d(new QbtGStreamerElementPrivate)
{
    *this = obj;
}

QbtGStreamerElement& QbtGStreamerElement::operator= (const QbtGStreamerElement& obj)
{
    *this = obj;
    return *this;
}

QbtGStreamerElement::~QbtGStreamerElement()
{
}

bool QbtGStreamerElement::link(QbtGStreamerElement *destination)
{
    return gst_element_link(d->element, destination->gstElement());
}

void QbtGStreamerElement::unlink(QbtGStreamerElement *destination)
{
    gst_element_unlink(d->element, destination->gstElement());
}

GstElement *QbtGStreamerElement::gstElement()
{
    return d->element;
}

void QbtGStreamerElement::setGstElement(GstElement *element)
{
    d->element = element;
    //qDebug() << s_instance << "uses element" << m_element;
    m_object = GST_OBJECT(element);

    g_signal_connect(GST_OBJECT(d->element), "pad-added", G_CALLBACK(d->new_pad), s_instance);
    g_signal_connect(GST_OBJECT(d->element), "no-more-pads", G_CALLBACK(d->no_pads), s_instance);
    g_signal_connect(GST_OBJECT(d->element), "pad-removed", G_CALLBACK(d->remove_pad), s_instance);
}

void QbtGStreamerElement::setProperty(const QString& property, const QString& value)
{
    g_object_set(G_OBJECT(d->element), property.toLocal8Bit().data(), value.toLocal8Bit().data(), QBTGSTREAMER_NULL);
}

QbtGStreamerStateChangeReturn QbtGStreamerElement::setState(QbtGStreamerState state)
{
    GstStateChangeReturn r;

    switch (state) {
    case QbtGStreamerStateVoidPending:
        r = gst_element_set_state(d->element, GST_STATE_VOID_PENDING);
        break;
    case QbtGStreamerStateNull:
        r = gst_element_set_state(d->element, GST_STATE_NULL);
        break;
    case QbtGStreamerStateReady:
        r = gst_element_set_state(d->element, GST_STATE_READY);
        break;
    case QbtGStreamerStatePaused:
        r = gst_element_set_state(d->element, GST_STATE_PAUSED);
        break;
    case QbtGStreamerStatePlaying:
        r = gst_element_set_state(d->element, GST_STATE_PLAYING);
        break;
    }

    switch (r) {
    case GST_STATE_CHANGE_FAILURE:
        return QbtGStreamerStateChangeFailure;
    case GST_STATE_CHANGE_SUCCESS:  
        return QbtGStreamerStateChangeSuccess;
    case GST_STATE_CHANGE_ASYNC:  
        return QbtGStreamerStateChangeAsync;
    case GST_STATE_CHANGE_NO_PREROLL:
        return QbtGStreamerStateChangeNoPreroll;
    }

    return QbtGStreamerStateChangeFailure;
}

QbtGStreamerStateChangeReturn QbtGStreamerElement::state(QbtGStreamerState &state, QbtGStreamerState &pending)
{
    GstState s;
    GstState p;
    GstStateChangeReturn r;

    r = gst_element_get_state(d->element, &s, &p, 0);

    switch (s) {
    case GST_STATE_VOID_PENDING:
        state = QbtGStreamerStateVoidPending;
        break;
    case GST_STATE_NULL:
        state = QbtGStreamerStateNull;
        break;
    case GST_STATE_READY:
        state = QbtGStreamerStateReady;
        break;
    case GST_STATE_PAUSED:
        state = QbtGStreamerStatePaused;
        break;
    case GST_STATE_PLAYING:
        state = QbtGStreamerStatePlaying;
        break;
    }

    switch (p) {
    case GST_STATE_VOID_PENDING:
        pending = QbtGStreamerStateVoidPending;
        break;
    case GST_STATE_NULL:
        pending = QbtGStreamerStateNull;
        break;
    case GST_STATE_READY:
        pending = QbtGStreamerStateReady;
        break;
    case GST_STATE_PAUSED:
        pending = QbtGStreamerStatePaused;
        break;
    case GST_STATE_PLAYING:
        pending = QbtGStreamerStatePlaying;
        break;
    }

    switch (r) {
    case GST_STATE_CHANGE_FAILURE:
        return QbtGStreamerStateChangeFailure;
    case GST_STATE_CHANGE_SUCCESS:  
        return QbtGStreamerStateChangeSuccess;
    case GST_STATE_CHANGE_ASYNC:  
        return QbtGStreamerStateChangeAsync;
    case GST_STATE_CHANGE_NO_PREROLL:
        return QbtGStreamerStateChangeNoPreroll;
    }

    return QbtGStreamerStateChangeFailure;
}

bool QbtGStreamerElement::addPad(QbtGStreamerPad *pad)
{
    return gst_element_add_pad(d->element, pad->gstPad());
}

QbtGStreamerPad *QbtGStreamerElement::pad(const QString& name)
{
    QbtGStreamerPad *newPad = new QbtGStreamerPad;
    GstPad *gstPad = gst_element_get_pad(d->element, name.toLocal8Bit().data());

    if(!gstPad)
        return 0;

    newPad->setGstPad(gstPad);
    return newPad;
}

void QbtGStreamerElement::createAllPads()
{
    gst_element_create_all_pads(d->element);
}

QbtGStreamerPad *QbtGStreamerElement::compatiblePad(QbtGStreamerPad *pad, QbtGStreamerCapabilities *caps)
{
    GstPad *p = gst_element_get_compatible_pad(d->element, pad->gstPad(), caps->gstCaps());
    
    if (!p)
        return 0;

    QbtGStreamerPad *pd = new QbtGStreamerPad;
    pd->setGstPad(p);

    return pd;
}

QbtGStreamerPad *QbtGStreamerElement::requestPad(const QString &name)
{
    GstPad *p = gst_element_get_request_pad(d->element, name.toLocal8Bit().data());

    if (!p)
        return 0;

    QbtGStreamerPad *pd = new QbtGStreamerPad;
    pd->setGstPad(p);

    return pd;
}

QbtGStreamerPad *QbtGStreamerElement::staticPad(const QString &name)
{
    GstPad *p = gst_element_get_static_pad(d->element, name.toLocal8Bit().data());

    if (!p)
        return 0;

    QbtGStreamerPad *pd = new QbtGStreamerPad;
    pd->setGstPad(p);

    return pd;
}

void QbtGStreamerElement::releaseRequestPad(QbtGStreamerPad *pad)
{
    gst_element_release_request_pad(d->element, pad->gstPad());
}

bool QbtGStreamerElement::removePad(QbtGStreamerPad *pad)
{
    QbtGStreamerState s;
    QbtGStreamerState p;

    state(s, p);

    if (s == QbtGStreamerStatePaused || s == QbtGStreamerStatePlaying) {
        qWarning() << "Can not remove a pad while being paused or playing!";
    }

    return gst_element_remove_pad(d->element, GST_PAD(pad->gstPad()));
}

QList<QbtGStreamerPad *> QbtGStreamerElement::pads()
{
   GstIterator *it = gst_element_iterate_pads(d->element);
   QbtGStreamerPad *pad;
   QList<QbtGStreamerPad *> pl;

   bool done = FALSE;
   gpointer item;

   while (!done) {
     switch (gst_iterator_next(it, &item)) {
       case GST_ITERATOR_OK:
         pad = new QbtGStreamerPad;
         pad->setGstPad(GST_PAD(item));
         pl << pad;
         gst_object_unref(item);
         break;
       case GST_ITERATOR_RESYNC:
         //...rollback changes to items...
         gst_iterator_resync(it);
         break;
       case GST_ITERATOR_ERROR:
         //...wrong parameter were given...
         done = TRUE;
         break;
       case GST_ITERATOR_DONE:
         done = TRUE;
         break;
     }
   }
   gst_iterator_free(it);

   return pl;
}

QList<QbtGStreamerPad *> QbtGStreamerElement::sinkPads()
{
   GstIterator *it = gst_element_iterate_sink_pads(d->element);
   QbtGStreamerPad *pad;
   QList<QbtGStreamerPad *> pl;

   bool done = FALSE;
   gpointer item;

   while (!done) {
     switch (gst_iterator_next(it, &item)) {
       case GST_ITERATOR_OK:
         pad = new QbtGStreamerPad;
         pad->setGstPad(GST_PAD(&item));
         pl << pad;
         gst_object_unref(item);
         break;
       case GST_ITERATOR_RESYNC:
         //...rollback changes to items...
         gst_iterator_resync(it);
         break;
       case GST_ITERATOR_ERROR:
         //...wrong parameter were given...
         done = TRUE;
         break;
       case GST_ITERATOR_DONE:
         done = TRUE;
         break;
     }
   }
   gst_iterator_free(it);

   return pl;
}

QList<QbtGStreamerPad *> QbtGStreamerElement::sourcePads()
{
   GstIterator *it = gst_element_iterate_src_pads(d->element);
   QbtGStreamerPad *pad;
   QList<QbtGStreamerPad *> pl;

   bool done = FALSE;
   gpointer item;

   while (!done) {
     switch (gst_iterator_next(it, &item)) {
       case GST_ITERATOR_OK:
         pad = new QbtGStreamerPad;
         pad->setGstPad(GST_PAD(&item));
         pl << pad;
         gst_object_unref(item);
         break;
       case GST_ITERATOR_RESYNC:
         //...rollback changes to items...
         gst_iterator_resync(it);
         break;
       case GST_ITERATOR_ERROR:
         //...wrong parameter were given...
         done = TRUE;
         break;
       case GST_ITERATOR_DONE:
         done = TRUE;
         break;
     }
   }
   gst_iterator_free(it);

   return pl;
}

bool QbtGStreamerElement::linkPads(const QString &sourcePadName, QbtGStreamerElement *destination, const QString &destinationPadName)
{
    return gst_element_link_pads(d->element, sourcePadName.toLocal8Bit().data(), destination->gstElement(), destinationPadName.toLocal8Bit().data());
}

void QbtGStreamerElement::unlinkPads(const QString &sourcePadName, QbtGStreamerElement *destination, const QString &destinationPadName)
{
    gst_element_unlink_pads(d->element, sourcePadName.toLocal8Bit().data(), destination->gstElement(), destinationPadName.toLocal8Bit().data());
}

bool QbtGStreamerElement::linkPadsFiltered(const QString &sourcePadName, QbtGStreamerElement *destination, const QString &destinationPadName, QbtGStreamerCapabilities *filter)
{
    return gst_element_link_pads_filtered(d->element, sourcePadName.toLocal8Bit().data(), destination->gstElement(), destinationPadName.toLocal8Bit().data(), filter->gstCaps());
}

bool QbtGStreamerElement::linkFiltered(QbtGStreamerElement *destination, QbtGStreamerCapabilities *filter)
{
    return gst_element_link_filtered(d->element, destination->gstElement(), filter->gstCaps());
}

QbtGStreamerClockTime QbtGStreamerElement::baseTime()
{
    guint64 t;

    t = gst_element_get_base_time(d->element);
    //qDebug() << "### t =" << t;

    return t;
}

void QbtGStreamerElement::setBaseTime(QbtGStreamerClockTime time)
{
    gst_element_set_base_time(d->element, time);
}

QbtGStreamerBus *QbtGStreamerElement::bus()
{
    GstBus *gstBus = gst_element_get_bus(d->element);

    if(!gstBus)
        return 0;

    QbtGStreamerBus *bus = new QbtGStreamerBus;
    bus->setGstBus(gstBus);
 
    QbtGStreamerCallbackManager::Instance()->busses.append(bus);

    return bus;
}

void QbtGStreamerElement::setBus(QbtGStreamerBus *bus)
{
    gst_element_set_bus(d->element, bus->gstBus());
}

/*QbtGStreamerElementFactory *QbtGStreamerElement::factory()
{
    GstElementFactory *f = gst_element_get_factory(d->element);

    if (!f)
        return 0;

    QbtGStreamerElementFactory *btf = new QbtGStreamerElementFactory;
    btf->setGstElementFactory(f);

    return btf;
}*/

bool QbtGStreamerElement::indexable()
{
    return gst_element_is_indexable(d->element);
}

// TODO: format
// return as milliseconds
qint64 QbtGStreamerElement::duration()
{
    gint64 t;

    GstFormat f;// = GST_FORMAT_UNDEFINED; //GST_FORMAT_TIME;
    
    if(gst_element_query_duration(d->element, &f, &t)) {
        qDebug() << "Format =" << f << "and duration (nanoseconds) =" << t;
        return t/1000000;
    }

    qDebug() << "Could not query the duration!";
    return 3000;
}

bool QbtGStreamerElement::seekSimple(qint64 position)
{
    gint64 t = position * 1000000; //from ms to ns

    GstFormat f = GST_FORMAT_TIME; //in ns

    GstSeekFlags s = GstSeekFlags(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE);

    return gst_element_seek_simple(d->element, f, s, t);
}

/*GstState QbtGStreamerElement::state()
{

}*/

/*void QbtGStreamerElementPrivate::emitPadAdded(GstElement *element, GstPad *pad, gpointer data)
{
    //emit padAdded(element, pad, data);
    //emit testSig();
}*/

void QbtGStreamerElementPrivate::new_pad(GstElement *element, GstPad *pad, gpointer data)
{
    QbtGStreamerPad *btPad;
    QbtGStreamerDataPointer *btData;

    foreach(QbtGStreamerElement* e, QbtGStreamerCallbackManager::Instance()->elements) {
        if(element == e->gstElement()) {
            btPad = new QbtGStreamerPad;
            btPad->setGstPad(pad);
            btData = new QbtGStreamerDataPointer;
            btData->setGPointer(data);
            emit e->padAdded(e, btPad, btData);
        }
    }
}

void QbtGStreamerElementPrivate::remove_pad(GstElement *element, GstPad *pad, gpointer data)
{
    QbtGStreamerPad *btPad;
    QbtGStreamerDataPointer *btData;

    foreach(QbtGStreamerElement* e, QbtGStreamerCallbackManager::Instance()->elements) {
        if(element == e->gstElement()) {
            btPad = new QbtGStreamerPad;
            btPad->setGstPad(pad);
            btData = new QbtGStreamerDataPointer;
            btData->setGPointer(data);
            emit e->padRemoved(e, btPad, btData);
        }
    }
}

void QbtGStreamerElementPrivate::no_pads(GstElement *element, gpointer data)
{
    QbtGStreamerDataPointer *btData;

    foreach(QbtGStreamerElement* e, QbtGStreamerCallbackManager::Instance()->elements) {
        if(element == e->gstElement()) {
            btData = new QbtGStreamerDataPointer;
            btData->setGPointer(data);
            emit e->noMorePads(e, btData);
        }
    }
}

#include "qbtgstreamerelement.moc"

