/*  This file is part of the KDE project
    Copyright (C) 2006 Matthias Kretz <kretz@kde.org>
    Copyright (C) 2006-2007 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 "backendprivate.h"

#include <QDBusMessage>
#include <QDBusConnection>
#include <QDomElement>
#include <QTime>

#include <kdebug.h>
#include <klocale.h>
#include <solid/audiointerface.h>

#include <unistd.h>
#include <stdio.h>

namespace Phonon
{
namespace GStreamer
{

findFeature::findFeature(const QString &feature)
{
    makeDefault();

    m_feature = feature;
}

findFeature::~findFeature()
{
}

bool findFeature::featureFilter(QbtGStreamerPluginFeature *feature, QbtGStreamerDataPointer *data)
{
    Q_UNUSED(data)

    QbtGStreamerElementFactory *f = new QbtGStreamerElementFactory;
    if (f->fromPluginFeature(feature)) { 
        if (f->klass().contains(m_feature, Qt::CaseInsensitive)) {
            return true;
        }
    }
    return false;
}

GstBackendPrivate::GstBackendPrivate()
{
    xmlStructure = QDomDocument("xmlstructure");

    mediaObjects.clear();
}

GstBackendPrivate::~GstBackendPrivate()
{
    QString msgPath = QString::fromLatin1("/");
    QString msgInterface = QString::fromLatin1("org.kde.phonongstreamerbackenddebugger");
    QString msgName = QString::fromLatin1("backendDestroyed");
    QDBusMessage dbusMsg = QDBusMessage::createSignal(msgPath, msgInterface, msgName);
    dbusMsg << xmlStructure.toString();
    QDBusConnection::sessionBus().send(dbusMsg);
}

void GstBackendPrivate::initBackend(const QString &name)
{
    QString pipelineName = "Pipeline_" + QTime::currentTime().toString("hhmmss");

    m_pipeline = new QbtGStreamerPipeline(pipelineName);

    if (!m_pipeline) {
        debugmessage(2, 0, backendName, "FATAL! Can not create a GStreamer pipeline!");
        return;
    }

    m_bus = m_pipeline->bus();
    m_bus->messageWatch(true);

    connect(m_bus, SIGNAL(message(QbtGStreamerMessage *, QbtGStreamerDataPointer *)),
            this, SLOT(slotBusMessage(QbtGStreamerMessage *, QbtGStreamerDataPointer *)));

    backendName = name;

    // For information purposes it's also useful to know which program created this backend.
    // After all, some problems might be in the program creating the backend.
    backendPid = QString::number(unsigned(getpid()));

    // Try to find more information about the parent program.
    findParentProgramDetails();

    rebuildXmlStructure();
    QString msgPath = QString::fromLatin1("/");
    QString msgInterface = QString::fromLatin1("org.kde.phonongstreamerbackenddebugger");
    QString msgName = QString::fromLatin1("backendCreated");
    QDBusMessage dbusMsg = QDBusMessage::createSignal(msgPath, msgInterface, msgName);
    dbusMsg << xmlStructure.toString();
    QDBusConnection::sessionBus().send(dbusMsg);

    checkAudioOutputs();
}

void GstBackendPrivate::findParentProgramDetails()
{
    FILE *statFile;
    char line[256];

    sprintf(line, "/proc/%d/stat", getpid());

    statFile = fopen(line, "rt");
    if (!statFile) {
        kDebug() << "Can not open the file " << line << endl;
        return;
    }

    fgets(line, 256, statFile);
    fclose(statFile);

    QString strLine(line);
    QString name = strLine.section('(', 1, 1);
    backendParentProgramName = name.section(')', 0, 0);
}

void GstBackendPrivate::addMediaObject(QPointer<MediaObject> object)
{
    mediaObjects << object;
    rebuildXmlStructure();
    QString msgPath = QString::fromLatin1("/");
    QString msgInterface = QString::fromLatin1("org.kde.phonongstreamerbackenddebugger");
    QString msgName = QString::fromLatin1("backendUpdated");
    QDBusMessage dbusMsg = QDBusMessage::createSignal(msgPath, msgInterface, msgName);
    dbusMsg << xmlStructure.toString();
    QDBusConnection::sessionBus().send(dbusMsg);
}

void GstBackendPrivate::rebuildXmlStructure()
{
    xmlStructure.clear();

    QDomElement rootElement = xmlStructure.createElement("PhononGStreamerBackendXMLStructure");
    xmlStructure.appendChild(rootElement);

    QDomElement backendElement = xmlStructure.createElement("Backend");
    backendElement.setAttribute("Name", backendName);
    backendElement.setAttribute("PID", backendPid);
    backendElement.setAttribute("ParentProgram", backendParentProgramName);
    rootElement.appendChild(backendElement);

    QDomElement definitionElement = xmlStructure.createElement("Definitions");
    backendElement.appendChild(definitionElement);

    QDomElement pipelineDefinition = xmlStructure.createElement("GStreamerPipeline");
    pipelineDefinition.setAttribute("Name", gstreamerPipeline()->name());
    definitionElement.appendChild(pipelineDefinition);

    QDomElement mediaObjectDefinition;
    QDomElement mediaObjectBinDefinition;
    foreach(QPointer<MediaObject> mediaObject, mediaObjects) {
        mediaObjectDefinition = xmlStructure.createElement("MediaObject");
        mediaObjectDefinition.setAttribute("Name", mediaObject->objectName());
        definitionElement.appendChild(mediaObjectDefinition);
        mediaObjectBinDefinition = xmlStructure.createElement("GStreamerBin");
        mediaObjectBinDefinition.setAttribute("Name", mediaObject->gstreamerBin()->name());
        mediaObjectDefinition.appendChild(mediaObjectBinDefinition);
    }
}

KSharedConfigPtr GstBackendPrivate::config()
{
    return m_config;
}

void GstBackendPrivate::setConfig(const KSharedConfigPtr &newConfig)
{
    m_config = newConfig;
}

void GstBackendPrivate::addAudioOutput(AudioDevice dev, QString driver)
{
    QString postfix;

    if (dev.driver() == Solid::AudioInterface::Alsa) {
        postfix = QLatin1String(" (ALSA)");
    } else if (dev.driver() == Solid::AudioInterface::OpenSoundSystem) {
        postfix = QLatin1String(" (OSS)");
    }

    AudioOutputInfo info(dev.index(), dev.cardName() + postfix,
            QString(), dev.iconName(), driver, dev.deviceIds());

    info.available = dev.isAvailable();
    m_audioOutputInfos << info;
}

void GstBackendPrivate::addAudioOutput(int index, const QString &name, const QString &description, const QString &icon, const QString &driver, const QStringList &deviceIds)
{
    AudioOutputInfo info(index, name, description, icon, driver, deviceIds);
    const int listIndex = m_audioOutputInfos.indexOf(info);

    if (listIndex == -1) {
        info.available = true;
        m_audioOutputInfos << info;
        KConfigGroup config(m_config, QLatin1String("AudioOutputDevice_") + QString::number(index));
        config.writeEntry("name", name);
        config.writeEntry("description", description);
        config.writeEntry("driver", driver);
        config.writeEntry("icon", icon);
    } else {
        m_audioOutputInfos[listIndex].devices = deviceIds;
        m_audioOutputInfos[listIndex].available = true;
    }
}

void GstBackendPrivate::checkAudioOutputs()
{
    debugmessage(0, 4, backendName, "Checking outputs.");

    if (m_audioOutputInfos.isEmpty()) {
        //kDebug(611) << "isEmpty" << endl;

        QStringList groups = m_config->groupList();

        int nextIndex = 10000;

        foreach (QString group, groups) {
            if (group.startsWith("AudioOutputDevice")) {
                const int index = group.right(group.size() - 18).toInt();
                if (index >= nextIndex) {
                    nextIndex = index + 1;
                }
                KConfigGroup config(m_config, group);
                m_audioOutputInfos << AudioOutputInfo(index,
                    config.readEntry("name", QString()),
                    config.readEntry("description", QString()),
                    config.readEntry("icon", QString()),
                    config.readEntry("driver", QString()),
                    QStringList()); // the device list can change and needs to be queried
                                    // from the actual hardware configuration
            }
        }


        findFeature *reg = new findFeature("sink/audio");
        QList<QbtGStreamerPluginFeature *> myList = reg->featureList();

        foreach (QbtGStreamerPluginFeature *f, myList) {
            if (f->name() == "alsasink") {
                debugmessage(0, 4, backendName, "Found alsa sink element: " + f->name());

                QList<AudioDevice> alsaDevices = AudioDeviceEnumerator::availablePlaybackDevices();
                foreach (AudioDevice dev, alsaDevices) {
                    //kDebug(611) << "Found audio device!" << endl;
                    if (dev.driver() == Solid::AudioInterface::Alsa) {
                        addAudioOutput(dev, QLatin1String("alsa"));
                    }
                }
            }
            if (f->name() == "osssink") {
                debugmessage(0, 4, backendName, "Found oss sink element: " + f->name());
            }
            if (f->name() == "pulsesink") {
                debugmessage(0, 4, backendName, "Found PulseAudio sink element");
                kDebug(611) << "Found PulseAudio sink element" << endl;
                addAudioOutput(nextIndex++, i18n("PulseAudio"),
                            i18n("<p>PulseAudio is a sound server for POSIX and Win32 systems. A sound server is basically a proxy for your sound applications. It allows you to do advanced operations on your sound data as it passes between your application and your hardware. Things like transferring the audio to a different machine, changing the sample format or channel count and mixing several sounds into one are easily achieved using a sound server.</p>"),
                            "pulseaudio", f->name(), QStringList());
            }
        }
    }
}

QSet<int> GstBackendPrivate::audioOutputIndexes()
{
    //checkAudioOutputs();
    QSet<int> set;

    for (int i = 0; i < m_audioOutputInfos.size(); ++i) {
        //if (that->m_audioOutputInfos[i].available) {
        set << m_audioOutputInfos[i].index;
        //}
    }

    return set;
}

QString GstBackendPrivate::audioOutputName(int audioDevice)
{
    checkAudioOutputs();

    for (int i = 0; i < m_audioOutputInfos.size(); ++i) {
        if (m_audioOutputInfos[i].index == audioDevice) {
            return m_audioOutputInfos[i].name;
        }
    }

    return QString();
}

QString GstBackendPrivate::audioOutputDescription(int audioDevice)
{
    checkAudioOutputs();

    for (int i = 0; i < m_audioOutputInfos.size(); ++i) {
        if (m_audioOutputInfos[i].index == audioDevice) {
            return m_audioOutputInfos[i].description;
        }
    }

    return QString();
}

QString GstBackendPrivate::audioOutputIcon(int audioDevice)
{
    checkAudioOutputs();

    for (int i = 0; i < m_audioOutputInfos.size(); ++i) {
        if (m_audioOutputInfos[i].index == audioDevice) {
            return m_audioOutputInfos[i].icon;
        }
    }

    return QString();
}

bool GstBackendPrivate::audioOutputAvailable(int audioDevice)
{
    checkAudioOutputs();

    for (int i = 0; i < m_audioOutputInfos.size(); ++i) {
        if (m_audioOutputInfos[i].index == audioDevice) {
            return m_audioOutputInfos[i].available;
        }
    }

    return false;
}

void GstBackendPrivate::debugmessage(int type, int level, const QString &objname, const QString &message)
{
    QString msgPath = QString::fromLatin1("/");
    QString msgInterface = QString::fromLatin1("org.kde.phonongstreamerbackenddebugger");
    QString msgName = QString::fromLatin1("message");
    QDBusMessage dbusMsg = QDBusMessage::createSignal(msgPath, msgInterface, msgName);
    dbusMsg << type << level << objname << message;
    QDBusConnection::sessionBus().send(dbusMsg);
}

QbtGStreamerPipeline *GstBackendPrivate::gstreamerPipeline()
{
    return m_pipeline;
}

void GstBackendPrivate::slotBusMessage(QbtGStreamerMessage *msg, QbtGStreamerDataPointer *data)
{
    Q_UNUSED(data)

    if (msg->type() == QbtGStreamerMessageError) {
        QString debugstr;
        QbtGStreamerError *err = msg->error(debugstr);

        switch (err->domain()) {
        case QbtGStreamerErrorCore:
            debugmessage(2, 0, backendName, "Core error:");
            break;
        case QbtGStreamerErrorLibrary:
            debugmessage(2, 0, backendName, "Library error:");
            break;
        case QbtGStreamerErrorResource:
            switch (err->code()) {
            case QbtGStreamerErrorResourceFailed:
                debugmessage(2, 0, backendName, "Resource error: General error");
                break;
            case QbtGStreamerErrorResourceTooLazy:
                debugmessage(2, 0, backendName, "Resource error: Lazy developer error");
                break;
            case QbtGStreamerErrorResourceNotFound:
                debugmessage(2, 0, backendName, "Resource error: Resource could not be found");
                break;
            case QbtGStreamerErrorResourceBusy:
                debugmessage(2, 0, backendName, "Resource error: Resource is busy");
                break;
            case QbtGStreamerErrorResourceOpenRead:
                debugmessage(2, 0, backendName, "Resource error: Can not open resource for reading");
                break;
            case QbtGStreamerErrorResourceOpenWrite:
                debugmessage(2, 0, backendName, "Resource error: Can not open resource for writing");
                break;
            case QbtGStreamerErrorResourceOpenReadWrite:
                debugmessage(2, 0, backendName, "Resource error: Can not open resource for reading or writing");
                break;
            case QbtGStreamerErrorResourceClose:
                debugmessage(2, 0, backendName, "Resource error: Can not close the resource");
                break;
            case QbtGStreamerErrorResourceRead:
                debugmessage(2, 0, backendName, "Resource error: Can not read from the resource");
                break;
            case QbtGStreamerErrorResourceWrite:
                debugmessage(2, 0, backendName, "Resource error: Can not write to the resource");
                break;
            case QbtGStreamerErrorResourceSeek:
                debugmessage(2, 0, backendName, "Resource error: Can not seek the resource");
                break;
            case QbtGStreamerErrorResourceSync:
                debugmessage(2, 0, backendName, "Resource error: Can not sync the resource");
                break;
            case QbtGStreamerErrorResourceSettings:
                debugmessage(2, 0, backendName, "Resource error: Can not manipulate the settings");
                break;
            case QbtGStreamerErrorResourceNoSpaceLeft:
                debugmessage(2, 0, backendName, "Resource error: No space left on the resource");
                break;
            /*case QbtGStreamerErrorResourceNumErrors:
                kWarning(611) << "Resource error:" << endl;
                break;*/
            }
        case QbtGStreamerErrorStream:
            switch (err->code()) {
            case QbtGStreamerErrorStreamFailed:
                debugmessage(2, 0, backendName, "Stream error: General error");
                break;
            case QbtGStreamerErrorStreamTooLazy:
                debugmessage(2, 0, backendName, "Stream error: Lazy developer error");
                break;
            case QbtGStreamerErrorStreamNotImplemented:
                debugmessage(2, 0, backendName, "Stream error: Functionality not implemented yet");
                break;
            case QbtGStreamerErrorStreamTypeNotFound:
                debugmessage(2, 0, backendName, "Stream error: Stream type not found");
                break;
            case QbtGStreamerErrorStreamWrongType:
                debugmessage(2, 0, backendName, "Stream error: Can not handle this stream type");
                break;
            case QbtGStreamerErrorStreamCodecNotFound:
                debugmessage(2, 0, backendName, "Stream error: There's no codec to handle this stream type");
                break;
            case QbtGStreamerErrorStreamDecode:
                debugmessage(2, 0, backendName, "Stream error: Decoding failed");
                break;
            case QbtGStreamerErrorStreamEncode:
                debugmessage(2, 0, backendName, "Stream error: Encoding failed");
                break;
            case QbtGStreamerErrorStreamDemux:
                debugmessage(2, 0, backendName, "Stream error: Demuxing failed");
                break;
            case QbtGStreamerErrorStreamMux:
                debugmessage(2, 0, backendName, "Stream error: Muxing failed");
                break;
            case QbtGStreamerErrorStreamFormat:
                debugmessage(2, 0, backendName, "Stream error: The stream is in the wrong format");
                break;
            case QbtGStreamerErrorStreamNumErrors:
                break;
        }
        case QbtGStreamerErrorSystem:
            debugmessage(2, 0, backendName, "System error:");
            break;
        }
    } else if (msg->type() == QbtGStreamerMessageStateChanged) {
        GstState oldState; 
        GstState newState;
        GstState pendingState;
        msg->stateChanged(&oldState, &newState, &pendingState);

        QString strOld;
        QString strNew;
        QString strPen;

        if (oldState == GST_STATE_VOID_PENDING)
            strOld = "VOID PENDING";
        else if (oldState == GST_STATE_NULL)
            strOld = "NULL"; 
        else if (oldState == GST_STATE_READY)
            strOld = "READY";
        else if (oldState == GST_STATE_PAUSED)
            strOld = "PAUSED";
        else if (oldState == GST_STATE_PLAYING)
            strOld = "PLAYING";
    
        if (newState == GST_STATE_VOID_PENDING)
            strNew = "VOID PENDING";
        else if (newState == GST_STATE_NULL)
            strNew = "NULL"; 
        else if (newState == GST_STATE_READY)
            strNew = "READY";
        else if (newState == GST_STATE_PAUSED)
            strNew = "PAUSED";
        else if (newState == GST_STATE_PLAYING)
            strNew = "PLAYING";

        if (pendingState == GST_STATE_VOID_PENDING)
            strPen = "VOID PENDING";
        else if (pendingState == GST_STATE_NULL)
            strPen = "NULL"; 
        else if (pendingState == GST_STATE_READY)
            strPen = "READY";
        else if (pendingState == GST_STATE_PAUSED)
            strPen = "PAUSED";
        else if (pendingState == GST_STATE_PLAYING)
            strPen = "PLAYING";

        debugmessage(0, 4, backendName, "State changed from " + strOld + " to " + strNew + ". Pending state: " + strPen);
    } else {
        debugmessage(1, 0, backendName, "Message of type: " + msg->typeName(msg->type()));
    }
}

}} // Phonon::GStreamer

#include "backendprivate.moc"

