/*****************************************************************************
 *   Copyright (C) 2003-2004 by Fred Schaettgen <kdebluetooth@schaettgen.de>
 *   Copyright (C) 2004-2005 by Alex Ibrado <alex@kdex.org>
 *
 *   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.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <dcopclient.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kstandarddirs.h>
#include <kconfig.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qxml.h>
#include <qdom.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "noatuncontroller.h"

NoatunController::NoatunController(QObject* parent) :
    Controller(parent)
{
    dcopClient = KApplication::dcopClient();
    controllerInfo.name = "noatun";
    controllerInfo.pathToApp = "noatun";
    controllerInfo.playlistMgr= "Server";

    noatunDCOPApp = "noatun";
    noatunPlaylist = "splitplaylist";
    localPlaylist = locateLocal("appdata", "noatun-playlist.xml");
    setupXMLPlaylist(true);
}

DCOPCall NoatunController::createDCOPCaller()
{
    QCStringList apps = dcopClient->registeredApplications();
    QString targetApp = "";
    for (uint n=0; n<apps.count(); ++n) {
        QString app = apps[n];
        if (app.startsWith("noatun-")) {
            targetApp = app;
            break;
        }
    }

    if(targetApp.isEmpty()) {
        targetApp = "noatun";
        /* if(!dcopClient->isApplicationRegistered("noatun")) {
            launchApp();
            unsigned int timeout = 0;
            while (dcopClient->isApplicationRegistered("noatun")
                && ++timeout<=5) ::sleep(1);
        } */
    }

    noatunDCOPApp = targetApp;
    return DCOPCall(dcopClient, targetApp, "Noatun");
}

void NoatunController::commandDINF(unsigned int &bitrate,
    unsigned int &sampleRate, unsigned int &channels)
{
    // Gets detailed information about the current song

    QString temp;

    DCOPCall caller = createDCOPCaller();
    caller.args() << QString("bitrate");
    caller.call("currentProperty(QString)");
    caller.ret() >> temp;
    bitrate = temp.toInt();

    caller.args() << QString("samplerate");
    caller.call("currentProperty(QString)");
    caller.ret() >> temp;
    sampleRate = temp.toInt() / 1000;

    caller.args() << QString("channels");
    caller.call("currentProperty(QString)");
    caller.ret() >> temp;
    channels = temp.toInt();

    /*
    // Update bitrate, samplerate, channels
    caller.args() << QString("track");
    if(caller.call("currentProperty(QString)") == "QString") {
        caller.ret() >> temp;
        if(temp != "") {
            unsigned int track = temp.toInt() - 1;
            playlistEntries[track].bitrate = bitrate;
            playlistEntries[track].samplerate = sampleRate;
            playlistEntries[track].channels = channels;
        }
    }
    */
}

void NoatunController::commandFADE() {
    // Stops the current song by fading it out
    // TODO: Port XMMS FADE
    commandSTOP();
}

void NoatunController::commandFFWD() {
    // Fast-forwards by five seconds
    setPosition(getPosition()+5);
}

Controller::SongInfo NoatunController::commandINFO() {
    // Gets information from the application
    Controller::SongInfo info;
    info.currentTime = getPosition();
    info.length = 0;
    info.playState = 0;
    info.repeat = false;
    info.shuffle = false;
    info.trackName = "Pure morning";

    DCOPCall caller = createDCOPCaller();
    if (caller.call("length()") != "int") {
        kdWarning() << "Error calling noatun::Noatun::length()" << endl;
    }
    else {
        int l;
        caller.ret() >> l;
        info.length = l/1000;
    }

    if (caller.call("state()") != "int") {
        kdWarning() << "Error calling noatun::Noatun::state()" << endl;
    }
    else {
        int s;
        caller.ret() >> s;
        //  Noatun: 0=stopped 1=paused  2=playing
        // Bemused: 0=stopped 1=playing 3=paused
        if(s > 0)
            s = (s==1)? 3 : 1;
        info.playState = s;
    }

    if (caller.call("title()") != "QString") {
        kdWarning() << "Error calling noatun::Noatun::title()" << endl;
    }
    else {
        QString t;
        caller.ret() >> t;
        info.trackName = t;
    }

    return info;
}

Controller::SongInfo NoatunController::commandINF2() {
    return commandINFO();
}

void NoatunController::commandLADD(QString filename) {
    // Adds the specified file to the playlist

    // TODO: If a playlist, add the individual files (PlayList class)
    //   else just the file
    ++numTracks;
    PlaylistEntry entry;

    entry.track = numTracks;
    entry.local = filename;

    // Awkward workaround since the current dcopiface is so puny
    DCOPCall caller = createDCOPCaller();
    // TODO: Use "adding plays" setting
    caller.args() << filename << true;
    if (caller.call("addFile(QString,bool)") != "void") {
        kdWarning() << "Error calling noatun::Noatun::addFile()" << endl;
    }

    caller.args() << QString("track") << QString("%1").arg(entry.track);
    if(caller.call("setCurrentProperty(QString,QString)") != "void") {
        kdWarning() << "Error calling setCurrentProperty('track'," << QString("%1").arg(entry.track) << ")" << endl;
    }

    unsigned int br, sr, ch;
    commandDINF(br, sr, ch);
    entry.bitrate = br;
    entry.samplerate = sr;
    entry.channels = ch;

    bool gotTitle = false;
    unsigned int timeout = 0;
    while(!gotTitle) {
        caller.args() << QString("title");
        if(caller.call("currentProperty(QString)") == "QString") {
            caller.ret() >> entry.title;
            if(!entry.title.isEmpty()) {
                gotTitle = true;
            } else {
                if(++timeout < 5)
                    ::sleep(1);
                else
                    break;
            }
        } else {
            QFileInfo fi(filename);
            entry.title = fi.fileName().mid(0, filename.find(".", -1));
            gotTitle = true;
        }
    }

    caller.args() << QString("url");
    if(caller.call("currentProperty(QString)") == "QString") {
        caller.ret() >> entry.url;
    } else {
        // TODO: Needs url encoding
        entry.url = "file:"+filename;
    }

    playlistEntries.push_back(entry);
    saveXMLPlaylist();

}

void NoatunController::commandNEXT() {
    // Plays the next song in the playlist
    DCOPCall caller = createDCOPCaller();
    if (caller.call("forward()") != "void") {
        kdWarning() << "Error calling noatun::Noatun::forward()" << endl;
    }
}

void NoatunController::commandPAUS() {
    // Pauses the current song
    DCOPCall caller = createDCOPCaller();
    if (caller.call("playpause()") != "void") {
        kdWarning() << "Error calling noatun::Noatun::pause()" << endl;
    }
}

void NoatunController::commandPLAY(QString filename, bool clear) {
    // Plays the specified file
    if(clear)
        commandRMAL();

    DCOPCall caller = createDCOPCaller();
    caller.args() << filename << true;
    if (caller.call("addFile(QString,bool)") != "void") {
        kdWarning() << "Error calling noatun::Noatun::addFile()" << endl;
    }
}

QStringList NoatunController::commandPLST(int &curPos, bool useFilenames) {
    // Writes the current playlist to the phone
    curPos = 0;
    QStringList playlist;
    std::vector<PlaylistEntry>::iterator it;

    for (it = playlistEntries.begin(); it!=playlistEntries.end(); ++it) {
        // TODO: Use it->local or it->url
        playlist.append((useFilenames || it->title.isEmpty())?  it->local : it->title);
    }
    return playlist;
}

void NoatunController::commandPREV() {
    // Plays the previous song in the playlist
    DCOPCall caller = createDCOPCaller();
    if (caller.call("back()") != "void") {
        kdWarning() << "Error calling noatun::Noatun::back()" << endl;
    }
}

void NoatunController::commandREPT(bool /*repeat*/) {
    // Enables or disables repeat mode
    DCOPCall caller = createDCOPCaller();
    if (caller.call("loop()") != "void") {
        kdWarning() << "Error calling noatun::Noatun::back()" << endl;
    }
}

void NoatunController::commandRMAL() {
    // Removes all songs from the playlist
    DCOPCall caller = createDCOPCaller();
    if (caller.call("clear()") != "void") {
        kdWarning() << "Error calling noatun::Noatun::back()" << endl;
    }
    playlistEntries.clear();
    QString playlistFile = localPlaylist;
    if(QFile::exists(playlistFile))
        QFile::remove(playlistFile);
}

void NoatunController::commandRWND() {
    // Rewinds by five seconds
    setPosition(getPosition()-5);
}

void NoatunController::commandSHFL(bool /*shuffle*/) {
    // Enables or disables shuffle mode
    // TODO: Implement SHFL
}

void NoatunController::commandSLCT(Q_UINT16 index) {
    // Selects song at [index] in playlist
    DCOPCall caller = createDCOPCaller();

    int track = index+1;
    int curTrack = currentIndex();
    if(curTrack == 0 && numTracks>0)
        curTrack = 1; // not started yet

    if(curTrack > 0) {
        // Gaaah!
        if(curTrack < track) {
            for(int i=curTrack; i<track; ++i) {
                caller.call("forward()");
            }
        } else if(curTrack > track) {
            for(int i=curTrack; i>track; --i) {
                caller.call("back()");
            }
        }
    }
}

void NoatunController::commandSTEN() {
    // Stops playing at the end of the current song
    // TODO: Port XMMS STEN
    commandSTOP();
}

void NoatunController::commandSTOP() {
    // Stops the current song immediately
    // TODO: Port XMMS double-STOP
    DCOPCall caller = createDCOPCaller();
    if (caller.call("stop()") != "void") {
        kdWarning() << "Error calling noatun::Noatun::stop()" << endl;
    }
}

void NoatunController::commandSTRT() {
    // Starts playing the current song
    DCOPCall caller = createDCOPCaller();
    if (caller.call("play()") != "void") {
        kdWarning() << "Error calling noatun::Noatun::play()" << endl;
    }
}

void NoatunController::commandVOLM(Q_UINT8 volume) {
    // Sets the volume to the value specified
    DCOPCall caller = createDCOPCaller();
    caller.args() << volume*100/255;
    if (caller.call("setVolume(int)") != "void") {
        kdWarning() << "Error calling noatun::Noatun::setVolume(int)" << endl;
    }
}

bool NoatunController::commandFULL() {
    // Toggle fullscreen mode
    // TODO: Visualization? or check if in video playback mode?
    return false;
}

int NoatunController::getPosition()
{
    DCOPCall caller = createDCOPCaller();
    if (caller.call("position()") != "int") {
        kdWarning() << "Error calling noatun::Noatun::position(int)" << endl;
        return 0;
    }
    int ret = 0;
    caller.ret() >> ret;
    return ret/1000;
}

void NoatunController::setPosition(int pos)
{
    DCOPCall caller = createDCOPCaller();
    caller.args() << int(pos*1000);
    if (caller.call("skipTo(int)") != "void") {
        kdWarning() << "Error calling noatun::Noatun::skipTo(int)" << endl;
    }
}

void NoatunController::commandSEEK(Q_UINT32 seconds) {
    // Jump to specified position
    setPosition(seconds*1000); // milliseconds
}

Q_UINT32 NoatunController::commandPLEN() {
    return numTracks;
}

bool NoatunController::commandGVOL(Q_UINT8 &volume) {
    // Get current volume
    DCOPCall caller = createDCOPCaller();
    if (caller.call("volume()") != "int") {
        kdWarning() << "Error calling noatun::Noatun::setVolume(int)" << endl;
        return false;
    }
    Q_UINT8 ret = 0;
    caller.ret() >> ret;
    volume=Q_UINT8(2.55*ret);
    return true;
}

int NoatunController::currentIndex() {
    DCOPCall caller = createDCOPCaller();
    caller.args() << QString("track");
    if(caller.call("currentProperty(QString)") == "QString") {
        QString track;
        caller.ret() >> track;
        return track.toInt();
    }
    return 0;
}

// TODO: Move the following to a separate PlayList class?

QString NoatunController::setupXMLPlaylist(bool copyOrig)
{
    QString playlistFile = localPlaylist;
    if(copyOrig || !QFile::exists(playlistFile)) {
        KConfig* ncfg = new KConfig("noatunrc");
        QStringList modules=ncfg->readListEntry("Modules");
        noatunPlaylist = modules[0].mid(0, modules[0].find("."));
        QString xmlPlaylist =  noatunPlaylist + ".xml";
        QString sourceFile = locate("data", "noatun/" + xmlPlaylist);

        readXMLPlaylist(sourceFile); // adds a "track" property
        saveXMLPlaylist(); // our local copy
        saveXMLPlaylist(sourceFile); // noatun's startup playlist, tagged with track
        delete ncfg;
    }
    return playlistFile;
}

// Implementation of QXmlDefaultHandler virtual
bool NoatunController::startElement(const QString &/*namespaceURI*/, const QString &/*localName*/,
    const QString &qName, const QXmlAttributes &atts )
{
    if (qName != "item")
        return true;

    PlaylistEntry entry;
    for (int i=0; i<atts.count(); i++) {
        QString att = atts.qName(i);
        QString val= atts.value(i);
        if(att == "title")
            entry.title = val;
        else if(att == "local")
            entry.local = val;
        else if(att == "url")
            entry.url = val;
        else if(att == "samplerate")
            entry.samplerate = val.toInt();
        else if(att == "bitrate")
            entry.bitrate = val.toInt();
        else if(att == "channels")
            entry.channels = val.toInt();
    }

    entry.track = ++numTracks;
    playlistEntries.push_back(entry);

    return true;
}

void NoatunController::readXMLPlaylist(QString srcfile)
{
    QString playlistFile;
    if(srcfile.isEmpty())
        playlistFile = localPlaylist;
    else
        playlistFile = srcfile;

    QFile f(playlistFile);
    if(f.open( IO_ReadOnly )) {
        QXmlInputSource source(&f);
        QXmlSimpleReader reader;
        reader.setContentHandler(this);
        playlistEntries.clear();
        numTracks = 0;
        reader.parse(source);
        f.close();
    }
}

bool NoatunController::saveXMLPlaylist(QString destfile)
{
    bool result = false;
    QString playlistFile;
    if(destfile.isEmpty())
        playlistFile = localPlaylist;
    else
        playlistFile = destfile;

    QDomDocument doc("XMLPlaylist");
    QDomElement root = doc.createElement("playlist");
    root.setAttribute("client", "noatun");
    root.setAttribute("version", "1.0");
    doc.appendChild(root);

    numTracks = 0;
    unsigned int track = 0;
    std::vector<PlaylistEntry>::iterator it;
    for (it = playlistEntries.begin(); it!=playlistEntries.end(); ++it) {
        QDomElement item = doc.createElement("item");
        item.setAttribute("track", QString("%1").arg(track+1)); // starting track = 1
        item.setAttribute("title", playlistEntries[track].title);
        item.setAttribute("local", playlistEntries[track].local);
        item.setAttribute("url", playlistEntries[track].url);
        item.setAttribute("samplerate", playlistEntries[track].samplerate);
        item.setAttribute("bitrate", playlistEntries[track].bitrate);
        item.setAttribute("channels", playlistEntries[track].channels);
        item.setAttribute("enabled", "true");
        root.appendChild(item);
        ++track;
    }
    numTracks = track;

    QFile f(playlistFile);
    if(f.open( IO_WriteOnly )) {
        QTextStream xmlText(&f);
        xmlText << doc.toString();
        f.close();
        result = true;
    }
    return result;
}


void NoatunController::loadPlaylist()
{
    readXMLPlaylist();
}


void NoatunController::savePlaylist()
{
    saveXMLPlaylist();
}


NoatunController::~NoatunController()
{
}


#include "noatuncontroller.moc"
