/***************************************************************************
                           Interface to access mpd
                             -------------------
    begin                : Tue Apr 19 18:31:00 BST 2005
    copyright            : (C) 2005 by William Robinson
    email                : airbaggins@yahoo.co.uk
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "mpdInterface.h"

#include <cstring>

#include <tqregexp.h>

#include <kmessagebox.h>
#include <kdebug.h>
#include <kurldrag.h>

MpdInterface::MpdInterface()
:   PlayerInterface()
,   sock()
,   sock_mutex()
,   messagebox_mutex()
,   hostname("localhost")
,   port(6600)
,   slider_timer(0)
,   reconnect_timer(0)
{
    connect(&sock, TQT_SIGNAL(error(int)), this, TQT_SLOT(connectionError(int)));
    connect(&sock, TQT_SIGNAL(error(int)), this, TQT_SLOT(stopSliderClock()));

    connect(&sock, TQT_SIGNAL(connected()), this, TQT_SLOT(startSliderClock()));
    connect(&sock, TQT_SIGNAL(connected()), this, TQT_SLOT(stopReconnectClock()));
    connect(&sock, TQT_SIGNAL(connected()), this, TQT_SLOT(connected()));

    connect(&sock, TQT_SIGNAL(connectionClosed()), this, TQT_SLOT(stopSliderClock()));
    connect(&sock, TQT_SIGNAL(connectionClosed()), this, TQT_SLOT(startReconnectClock()));
    connect(&sock, TQT_SIGNAL(connectionClosed()), this, TQT_SIGNAL(playerStopped()));

    reconnect();
}

MpdInterface::~MpdInterface()
{
}

void MpdInterface::startSliderClock()
{
    if (!slider_timer)
    {
        //kdDebug(90200) << "Starting slider clock\n";
        slider_timer = startTimer(SLIDER_TIMER_INTERVAL);
    }
}

void MpdInterface::stopSliderClock()
{
    if (slider_timer)
    {
        //kdDebug(90200) << "Stopping slider clock\n";
        killTimer(slider_timer);
        slider_timer=0;
    }
}
void MpdInterface::startReconnectClock()
{
    if (!reconnect_timer)
    {
        //kdDebug(90200) << "Starting Reconnect clock\n";
        reconnect_timer = startTimer(RECONNECT_TIMER_INTERVAL);
    }
}

void MpdInterface::stopReconnectClock()
{
    if (reconnect_timer)
    {
        //kdDebug(90200) << "Stopping Reconnect clock\n";
        killTimer(reconnect_timer);
        reconnect_timer=0;
    }
}


void MpdInterface::timerEvent(TQTimerEvent* te)
{
    if (te->timerId() == slider_timer) updateSlider();
    else if (te->timerId() == reconnect_timer) reconnect();
}


void MpdInterface::reconnect() const
{
    if (sock.state()==TQSocket::Idle)
    {
        sock_mutex.tryLock();
        //kdDebug(90200) << "Connecting to " << hostname.latin1() << ":" << port << "...\n";
        sock.connectToHost(hostname,port);
    }
}

void MpdInterface::connected()
{
    if (fetchOk()) // unlocks
    {
        //kdDebug(90200) << "Connected ok\n";
        emit playerStarted();
        emit playingStatusChanged(playingtqStatus());
    }
    else
    {
        //kdDebug(90200) << "Connection error\n";
        emit playerStopped();
    }
}

void MpdInterface::connectionError(int e)
{
    sock_mutex.unlock();
    emit playerStopped();
    TQString message;
    if (messagebox_mutex.tryLock())
    {
        switch (e)
        {
            case TQSocket::ErrConnectionRefused:
                message=i18n("Connection refused to %1:%2.\nIs mpd running?").tqarg(hostname).tqarg(port);
                break;
            case TQSocket::ErrHostNotFound:
                message=i18n("Host '%1' not found.").tqarg(hostname);
                break;
            case TQSocket::ErrSocketRead:
                message=i18n("Error reading socket.");
                break;
            default:
                message=i18n("Connection error");
                break;
        }
        // :TODO: KSimpleConfig to prompt for hostname/port values ?
        if (KMessageBox::warningContinueCancel( 0, message,
                                                i18n("MediaControl MPD Error"),
                                                i18n("Reconnect"))==KMessageBox::Continue)
        {
            startReconnectClock();
        }
        else
        {
            stopReconnectClock();
        }
        messagebox_mutex.unlock();
    }
}

bool MpdInterface::dispatch(const char* cmd) const
{
    if (sock.state()==TQSocket::Connected && sock_mutex.tryLock())
    {
        long cmd_len=strlen(cmd);
        //kdDebug(90200) << "sending: " << cmd;
        long written=sock.writeBlock(cmd,cmd_len);
        if (written==cmd_len)
        {
            //kdDebug(90200) << "All bytes written\n";
            sock.flush();
            return true;
        }
        else
        {
            //kdDebug(90200) << written << '/' << cmd_len << " bytes written\n";
        }
        sock.flush();
    }
    return false;
}

bool MpdInterface::fetchLine(TQString& res) const
{
    TQString errormessage;
    while (sock.state()==TQSocket::Connected)
    {
        if (!sock.canReadLine())
        {
            sock.waitForMore(20);
            continue;
        }
        res=sock.readLine().stripWhiteSpace();
        //kdDebug(90200) << "received: " << res.latin1() << "\n";
        if (res.startsWith("OK"))
        {
            sock_mutex.unlock();
            // if theres a message and we clear it and there's no other messagebox
            if (!errormessage.isEmpty()
                && dispatch("clearerror\n") && fetchOk()
                && messagebox_mutex.tryLock())
            {
                KMessageBox::error(0,errormessage,i18n("MediaControl MPD Error"));
                messagebox_mutex.unlock();
            }
            return false;
        }
        else if (res.startsWith("ACK"))
        {
            sock_mutex.unlock();
            return false;
        }
        else if (res.startsWith("error: "))
        {
            errormessage=i18n(res.latin1());
        }
        else
        {
            return true;
        }
    }
    sock_mutex.unlock();
    return false;
}

bool MpdInterface::fetchOk() const
{
    TQString res;
    while (fetchLine(res)) { }
    if (res.startsWith("OK"))
        return true;
    else
        return false;
}

void MpdInterface::updateSlider()
{
    //kdDebug(90200) << "update slider\n";
    if (!dispatch("status\n")) return;

    TQString res;
    TQRegExp time_re("time: (\\d+):(\\d+)");
    while(fetchLine(res))
    {
        if (res.startsWith("state: "))
        {
            if (res.endsWith("play"))
            {
                emit playingStatusChanged(Playing);
            }
            else if (res.endsWith("pause"))
            {
                emit playingStatusChanged(Paused);
            }
            else
            {
                emit playingStatusChanged(Stopped);
            }
        }
        else if (time_re.search(res)>=0)
        {
            TQStringList timeinfo=time_re.tqcapturedTexts();
            timeinfo.pop_front();
            int elapsed_seconds=timeinfo.first().toInt();
            timeinfo.pop_front();
            int total_seconds=timeinfo.first().toInt();
            emit newSliderPosition(total_seconds,elapsed_seconds);
        }
    }
}

void MpdInterface::sliderStartDrag()
{
    stopSliderClock();
}

void MpdInterface::sliderStopDrag()
{
    startSliderClock();
}

void MpdInterface::jumpToTime(int sec)
{
    reconnect();
    if (!dispatch("status\n")) return;

    long songid=-1;

    TQString res;
    TQRegExp songid_re("songid: (\\d+)");
    while(fetchLine(res))
    {
        if (songid_re.search(res)>=0)
        {
            TQStringList songidinfo=songid_re.tqcapturedTexts();
            songidinfo.pop_front();
            songid=songidinfo.first().toInt();
        }
    }

    if (songid>-1)
    {
        if (dispatch(TQString("seekid %1 %2\n").tqarg(songid).tqarg(sec).latin1()))
        {
            fetchOk(); // unlocks
        }
    }
}

void MpdInterface::playpause()
{
    reconnect();
    if (playingtqStatus()==Stopped ? dispatch("play\n") : dispatch("pause\n"))
    {
        fetchOk();
    }
}

void MpdInterface::stop()
{
    reconnect();
    if (dispatch("stop\n")) fetchOk();
}

void MpdInterface::next()
{
    reconnect();
    if (dispatch("next\n")) fetchOk();
}

void MpdInterface::prev()
{
    reconnect();
    if (dispatch("previous\n")) fetchOk();
}


void MpdInterface::changeVolume(int delta)
{
    reconnect();

    if (!dispatch("status\n")) return;

    int volume=-1;

    TQString res;
    TQRegExp volume_re("volume: (\\d+)");
    while(fetchLine(res))
    {
        if (volume_re.search(res)>=0)
        {
            TQStringList info=volume_re.tqcapturedTexts();
            info.pop_front();
            volume=info.first().toInt();
        }
    }

    if (volume>-1)
    {
        volume+=delta;
        if (volume<0) volume=0;
        if (volume>100) volume=100;
        if (dispatch(TQString("setvol %1\n").tqarg(volume).latin1()))
        {
            fetchOk();
        }
    }
}

void MpdInterface::volumeUp()
{
    reconnect();
    changeVolume(5);
}

void MpdInterface::volumeDown()
{
    reconnect();
    changeVolume(-5);
}

void MpdInterface::dragEnterEvent(TQDragEnterEvent* event)
{
    event->accept( KURLDrag::canDecode(event) );
}

void MpdInterface::dropEvent(TQDropEvent* event)
{
    reconnect();

    KURL::List list;
    if (KURLDrag::decode(event, list))
    {
        if (list.count()==1) // just one file dropped
        {
            // check to see if its in the playlist already
            if (dispatch("playlistid\n"))
            {
                long songid=-1;
                TQString file;
                TQString res;
                while(fetchLine(res))
                {
                    TQRegExp file_re("file: (.+)");
                    TQRegExp id_re("Id: (.+)");
                    if (file.isEmpty() && file_re.search(res)>=0)
                    {
                        TQStringList info=file_re.tqcapturedTexts();
                        info.pop_front();
                        // if the dropped file ends with the same name, record it
                        if (list.front().path().endsWith(info.first()))
                        {
                            file=info.first().toInt();
                        }
                    }
                    else if (!file.isEmpty() && id_re.search(res)>=0)
                    {
                        // when we have the file, pick up the id (file scomes first)
                        TQStringList info=id_re.tqcapturedTexts();
                        info.pop_front();
                        songid=info.first().toInt();
                        fetchOk(); // skip to the end
                        break;
                    }
                }

                // found song, so lets play it
                if (songid>-1)
                {
                    if (dispatch((TQString("playid %1\n").tqarg(songid)).latin1()))
                    {
                        if (fetchOk()) list.pop_front();
                        return;
                    }
                }
            }
        }

        // now if we have got this far, just try to add any files
        for (KURL::List::const_iterator i = list.constBegin(); i!=list.constEnd(); ++i)
        {
            if ((*i).isLocalFile())
            {
                TQStringList path=TQStringList::split("/",(*i).path());

                while (!path.empty())
                {
                    if (dispatch((TQString("add \"")
                                  +path.join("/").replace("\"","\\\"")
                                  +TQString("\"\n")).latin1()))
                    {
                        if (fetchOk()) break;
                    }
                    path.pop_front();
                }
            }
            else
            {
                // :TODO: can handle http:// urls but maybe should check port or something
            }
        }
    }
}

const TQString MpdInterface::getTrackTitle() const
{
    TQString result;

    reconnect();

    if (!dispatch("status\n")) return result;

    long songid=-1;
    TQString res;
    while(fetchLine(res))
    {
        TQRegExp songid_re("songid: (\\d+)");
        if (songid_re.search(res)>=0)
        {
            TQStringList songidinfo=songid_re.tqcapturedTexts();
            songidinfo.pop_front();
            songid=songidinfo.first().toInt();
        }
    }

    if (!(songid>-1)) return result;

    if (!dispatch(TQString("playlistid %1\n").tqarg(songid).latin1()))
        return result;

    TQString artist;
    TQString album;
    TQString title;
    TQString track;
    TQString file;
    while(fetchLine(res))
    {
        TQRegExp artist_re("Artist: (.+)");
        TQRegExp album_re("Album: (.+)");
        TQRegExp track_re("Album: (.+)");
        TQRegExp title_re("Title: (.+)");
        TQRegExp file_re("file: (.+)");
        if (artist_re.search(res)>=0)
        {
            TQStringList info=artist_re.tqcapturedTexts();
            info.pop_front();
            artist=info.first();
        }
        else if (album_re.search(res)>=0)
        {
            TQStringList info=album_re.tqcapturedTexts();
            info.pop_front();
            album=info.first();
        }
        else if (title_re.search(res)>=0)
        {
            TQStringList info=title_re.tqcapturedTexts();
            info.pop_front();
            title=info.first();
        }
        else if (track_re.search(res)>=0)
        {
            TQStringList info=track_re.tqcapturedTexts();
            info.pop_front();
            track=info.first();
        }
        else if (file_re.search(res)>=0)
        {
            TQStringList info=file_re.tqcapturedTexts();
            info.pop_front();
            file=info.first();
        }
    }

    if (!artist.isEmpty())
    {
        if (!title.isEmpty())
            return artist.append(" - ").append(title);
        else if (!album.isEmpty())
            return artist.append(" - ").append(album);
    }
    else if (!title.isEmpty())
    {
        if (!album.isEmpty())
            return album.append(" - ").append(title);
        else
            return title;
    }
    else if (!album.isEmpty())
    {
        if (!track.isEmpty())
            return album.append(" - ").append(track);
        else
            return album;
    }
    return i18n("No tags: %1").tqarg(file);
}

int MpdInterface::playingtqStatus()
{
    //kdDebug(90200) << "looking up playing status\n";
    if (!dispatch("status\n")) return Stopped;

    PlayingtqStatus status=Stopped;
    TQString res;
    while(fetchLine(res))
    {
        if (res.startsWith("state: "))
        {
            if (res.endsWith("play")) status=Playing;
            else if (res.endsWith("pause")) status=Paused;
            else status=Stopped;
        }
    }

    return status;
}

#include "mpdInterface.moc"
