/* -*- c++ -*-
 *
 * mldonkeyapplet.cpp
 *
 * Copyright (C) 2002, 2003 Petter E. Stokke <gibreel@kmldonkey.org>
 * Copyright (C) 2003 Sebastian Sauer <mail@dipe.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.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include <qmessagebox.h>
#include <qtimer.h>
#include <qlayout.h>
#include <qcheckbox.h>
#include <qpushbutton.h>

#include <kaboutapplication.h>
#include <kconfig.h>
#include <kapplication.h>
#include <knuminput.h>
#include <kpopupmenu.h>
#include <kiconloader.h>
#include <kurldrag.h>
#include <kglobalsettings.h>

#include <dcopclient.h>

#include <donkeyprotocol.h>

#include "version.h"
#include "appletconfig.h"
#include "mldonkeyappletgui.h"
#include "mldonkeyapplet.h"
#include "mldonkeyapplet.moc"


extern "C"
{
    KPanelApplet* init( QWidget *parent, const QString& configFile )
    {
        return new MLDonkeyApplet( configFile, KPanelApplet::Normal,
				   KPanelApplet::Preferences |
				   KPanelApplet::About,
				   parent, "kmldonkey" );
    }
}

MLDonkeyApplet::MLDonkeyApplet( const QString& configFile,
				Type type, int actions,
				QWidget *parent, const char *name )
    : DCOPObject("MLDonkeyAppletIface")
    , KPanelApplet( configFile, type, actions, parent, name )
{
    aboutData = new KAboutData("mldonkeyapplet", I18N_NOOP("MLDonkey Applet"), KMLDONKEY_VERSION,
			       I18N_NOOP("<h2>MLDonkey Applet</h2><p>MLDonkey status displays and KMLDonkey launcher.</p>"),
			       KAboutData::License_GPL_V2, I18N_NOOP("Copyright (C) 2002-2004 The KMLDonkey Authors"),
			       I18N_NOOP("<p>Part of the KMLDonkey package.</p>"),
			       "http://www.kmldonkey.org");
    aboutData->addAuthor("Petter E. Stokke", I18N_NOOP("Maintainer"), "gibreel@kmldonkey.org");
    aboutData->addAuthor("Sebastian Sauer", I18N_NOOP("Developer"), "mail@dipe.org");
    aboutData->setTranslator(I18N_NOOP("_: NAME OF TRANSLATORS\nYour names"), I18N_NOOP("_: EMAIL OF TRANSLATORS\nYour emails"));

    setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed, 0, 0, sizePolicy().hasHeightForWidth() ) );

    client = new DCOPClient();
    client->registerAs("mldonkeyapplet", false);
    client->setNotifications(true);
    connect(client, SIGNAL(applicationRemoved(const QCString&)), this, SLOT(applicationRemoved(const QCString&)));
    connectDCOPSignal("kmldonkey", "KMLDonkeyIface", "kmldonkeyAppeared(bool)", "kmldonkeyAppeared(bool)", false);

    restoreConfiguration();

    configDialog = new AppletConfig(this);
    connect(configDialog, SIGNAL(applyClicked()), this, SLOT(applyConfig()));
    connect(configDialog, SIGNAL(okClicked()), this, SLOT(applyConfig()));

    setFrameStyle( StyledPanel | Sunken );
    QHBoxLayout *layout = new QHBoxLayout(this);
    layout->setAlignment(Qt::AlignRight);
    gui = new MLDonkeyAppletGUI(this);
    layout->add(gui);
    gui->setLaunchState(isGUIVisible());
    updateLabels();

    connect(gui, SIGNAL(toggledLaunch(bool)), this, SLOT(showGUI(bool)));
    connect(gui, SIGNAL(toggledMute(bool)), this, SLOT(muteDonkey(bool)));

    hostManager = new HostManager(this);
    donkey = new DonkeyProtocol(true, this);
    connect(donkey, SIGNAL(clientStats(int64, int64, int64, int, int, int, int, int, int, int, QMap<int,int>*)),
	    this, SLOT(updateStatus(int64, int64, int64, int, int, int, int, int, int, int, QMap<int,int>*)));
    connect(donkey, SIGNAL(signalDisconnected(int)), this, SLOT(donkeyDisconnected(int)));
    connect(donkey, SIGNAL(signalConnected()), this, SLOT(donkeyConnected()));
    connect(donkey, SIGNAL(updatedDownloadFiles()), this, SLOT(updateDownloadFiles()));
    connect(donkey, SIGNAL(updatedDownloadedFiles()), this, SLOT(updateDownloadedFiles()));
    connect(donkey, SIGNAL(updatedConnectedServers()), this, SLOT(updateServers()));
    connect(donkey, SIGNAL(consoleMessage(QString&)), this, SLOT(consoleMessage(QString&)));
    connect(hostManager, SIGNAL(hostListUpdated()), this, SLOT(connectToCore()));

    setAcceptDrops(true);

    menu = new KPopupMenu(this);
    menu->insertTitle(SmallIcon("kmldonkey"), i18n("MLDonkey Applet"));
    menu->insertItem(SmallIcon("configure"), i18n("Configure MLDonkey Applet..."), 1);
    menu->insertSeparator();
    menu->insertItem(SmallIcon("kmldonkey"), i18n("About MLDonkey Applet..."), 2);
    menu->insertSeparator();
    menu->connectItem(1, this, SLOT(configure()));
    menu->connectItem(2, this, SLOT(showAbout()));
    menu->setCheckable(true);

    m_connectAction = new HostSelectAction(i18n("Connect To..."), "connect_creating", hostManager, this);
    m_connectAction->plug(menu);
    connect(m_connectAction, SIGNAL(hostSelected(HostInterface*)), SLOT(connectToCore(HostInterface*)));

    reconnect = 0;

    timer = new QTimer(this);
    connect( timer, SIGNAL( timeout() ), SLOT( refreshDisplay() ));
    timer->start(5000);

    connectToCore();
}

MLDonkeyApplet::~MLDonkeyApplet()
{
    delete client;
}

void MLDonkeyApplet::updateLabels()
{
    QString first, second;
    QString tfirst, tsecond;
    if (activeDisplays.count() >= 1) {
	first = configDialog->itemTitles[activeDisplays[0]];
	tfirst = configDialog->itemTooltips[activeDisplays[0]];
    }
    if (activeDisplays.count() >= 2) {
	second = configDialog->itemTitles[activeDisplays[1]];
	tsecond = configDialog->itemTooltips[activeDisplays[1]];
    }
    gui->updateLabels(first, second);
    gui->updateTooltips(tfirst, tsecond);
    emit updateLayout();
}

QString humanReadableSize(int64 rsz)
{
    QString foo;
    double sz = (double)rsz;
    if (sz >= (double)(1024 * 1024 * 1024)) {
	sz = sz / (1024 * 1024 * 1024);
	foo = i18n("gigabyte suffix", "%1G").arg(KGlobal::locale()->formatNumber(sz, 1));
    } else if (sz >= (double)(1024 * 1024)) {
	sz = sz / (1024 * 1024);
	foo = i18n("megabyte suffix", "%1M").arg(KGlobal::locale()->formatNumber(sz, 1));
    } else if (sz >= (double)1024) {
	sz = sz / 1024;
	foo = i18n("kilobyte suffix", "%1K").arg(KGlobal::locale()->formatNumber(sz, 1));
    } else foo = KGlobal::locale()->formatNumber(sz, 0);
    return foo;
}

QString MLDonkeyApplet::produceStatus(const QString& key, int64 ul, int64 dl, int64 sh, int nsh,
				   int tul, int tdl, int uul, int udl, int ndl, int ncp)
{
    QString txt;
    QTextOStream s(&txt);
    if (key == "speed")
	s << QString::number((double)(tul+uul) / 1024.0, 'f', 1) << "/" << QString::number((double)(tdl+udl) / 1024.0, 'f', 1);
    else if (key == "files")
	s << QString::number(ncp) << "/" << QString::number(ndl);
    else if (key == "transfer")
	s << humanReadableSize(ul) << "/" << humanReadableSize(dl);
    else if (key == "shared")
	s << QString::number(nsh) << "/" << humanReadableSize(sh);
    return txt;
}

void MLDonkeyApplet::updateStatus(int64 ul, int64 dl, int64 sh, int nsh, int tul, int tdl, int uul, int udl, int ndl, int ncp, QMap<int,int>*)
{
    QString first, second;

    if (activeDisplays.count() >= 1)
	first = produceStatus(activeDisplays[0], ul, dl, sh, nsh, tul, tdl, uul, udl, ndl, ncp);
    if (activeDisplays.count() >= 2)
	second = produceStatus(activeDisplays[1], ul, dl, sh, nsh, tul, tdl, uul, udl, ndl, ncp);

    gui->updateStatus(first, second);
    emit updateLayout();

    //delete map;
}

void MLDonkeyApplet::donkeyDisconnected(int err)
{
    switch (err) {
    case ProtocolInterface::AuthenticationError:
	QMessageBox::critical(this, i18n("KMLDonkey Applet"),
			      i18n("Authentication error. Incorrect name or password."));
	break;
    case ProtocolInterface::ConnectionRefusedError:
	reconnect = 1;
	break;
    case ProtocolInterface::HostNotFoundError: {
        HostInterface* host = hostManager->defaultHost();
	QMessageBox::critical(this, i18n("KMLDonkey Applet"),
                              host ? i18n("Host '%1' not found!").arg(host->address()) : i18n("No Host defined!"));
    } break;
    case ProtocolInterface::CommunicationError:
	if (!reconnect) QMessageBox::critical(this, i18n("KMLDonkey Applet"),
					      i18n("Disconnected due to a communication error."));
	reconnect = 1;
	break;
    case ProtocolInterface::IncompatibleProtocolError:
	QMessageBox::critical(this, i18n("KMLDonkey Applet"),
			      i18n("Your mldonkey core uses an obsolete communications protocol. "
				   "Please upgrade it to a more recent version!"));
	break;
    case ProtocolInterface::NoError:
        reconnect = 1;
        break;
    default:
	QMessageBox::critical(this, i18n("KMLDonkey Applet"),
                                    i18n("Unknown error!"));
	break;
    }
    gui->donkeyDisconnected();
    emit updateLayout();
}

void MLDonkeyApplet::donkeyConnected()
{
    reconnect = 0;
}

void MLDonkeyApplet::refreshDisplay()
{
    if (reconnect && (!donkey || !donkey->isConnected())) {
	if (!donkey) donkey = new DonkeyProtocol(this);
	connectToCore();
    }
}

void MLDonkeyApplet::connectToCore()
{
    connectToCore(hostManager->defaultHost());
}

void MLDonkeyApplet::connectToCore(HostInterface* host)
{
    donkey->setHost(host);
    donkey->connectToCore();
}

void MLDonkeyApplet::restoreConfiguration()
{
    KConfig *conf = config();

    conf->setGroup("Helper");
    bool usedBefore = conf->readBoolEntry("UsedBefore", false);

    conf->setGroup("Layout");
    showLabels = conf->readBoolEntry("ShowLabels", false);
    showDouble = conf->readBoolEntry("ShowLabelsOnlyInDouble", false);
    showMute = conf->readBoolEntry("ShowMuteInSingle", true);
    activeDisplays = conf->readListEntry("ActiveDisplays");
    if (!usedBefore && activeDisplays.empty()) {
	activeDisplays.push_back("files");
	activeDisplays.push_back("speed");
    }

    appletfont = KGlobalSettings::generalFont();
    appletfont = conf->readFontEntry("font", &appletfont);

    conf->setGroup("Mute");
    muteUploadRate = conf->readUnsignedNumEntry("MuteUploadRate",4);
    muteDownloadRate = conf->readUnsignedNumEntry("MuteDownloadRate",4);
    normalUploadRate = conf->readUnsignedNumEntry("NormalUploadRate",0);
    normalDownloadRate = conf->readUnsignedNumEntry("NormalDownloadRate",0);
}

void MLDonkeyApplet::writeConfiguration()
{
    KConfig *conf = config();

    conf->setGroup("Helper");
    conf->writeEntry("UsedBefore", true);

    conf->setGroup("Layout");
    conf->writeEntry("ShowLabels", showLabels);
    conf->writeEntry("ShowLabelsOnlyInDouble", showDouble);
    conf->writeEntry("ShowMuteInSingle", showMute);
    conf->writeEntry("ActiveDisplays", activeDisplays);

    conf->writeEntry("font", appletfont);

    conf->setGroup("Mute");
    conf->writeEntry("MuteUploadRate", muteUploadRate);
    conf->writeEntry("MuteDownloadRate", muteDownloadRate);
    conf->writeEntry("NormalUploadRate", normalUploadRate);
    conf->writeEntry("NormalDownloadRate", normalDownloadRate);

    conf->sync();
}

void MLDonkeyApplet::applyConfig()
{
    showLabels = configDialog->showLabelsSelect->isChecked();
    showDouble = configDialog->showDoubleSelect->isChecked();
    showMute = configDialog->showMuteSelect->isChecked();
    activeDisplays = configDialog->active();

    appletfont = configDialog->appletFont();

    normalDownloadRate = configDialog->normalDownloadEntry->value();
    normalUploadRate = configDialog->normalUploadEntry->value();
    muteDownloadRate = configDialog->muteDownloadEntry->value();
    muteUploadRate = configDialog->muteUploadEntry->value();

    writeConfiguration();
    gui->updateLayout();
    updateLabels();
    emit updateLayout();
}

void MLDonkeyApplet::preferences()
{
    configDialog->showLabelsSelect->setChecked(showLabels);
    configDialog->showDoubleSelect->setChecked(showDouble);
    configDialog->showMuteSelect->setChecked(showMute);
    configDialog->setActive(activeDisplays);

    configDialog->setAppletFont(appletfont);

    configDialog->normalDownloadEntry->setValue(normalDownloadRate);
    configDialog->normalUploadEntry->setValue(normalUploadRate);
    configDialog->muteDownloadEntry->setValue(muteDownloadRate);
    configDialog->muteUploadEntry->setValue(muteUploadRate);

    configDialog->show();
}

void MLDonkeyApplet::configure()
{
    preferences();
}

int MLDonkeyApplet::widthForHeight(int height) const
{
    if (height < 32) gui->relayoutWidgets(1);
    else gui->relayoutWidgets(0);
    return gui->width();
}

int MLDonkeyApplet::heightForWidth(int) const
{
    return gui->height();
}

void MLDonkeyApplet::resizeEvent(QResizeEvent* e)
{
    KPanelApplet::resizeEvent(e);
    emit updateLayout();
}

void MLDonkeyApplet::about()
{
    KAboutApplication dialog(aboutData);
    dialog.exec();
}

void MLDonkeyApplet::showAbout()
{
    about();
}

void MLDonkeyApplet::applicationRemoved(const QCString& appId)
{
    if (appId == QCString("kmldonkey"))
        gui->setLaunchState(false);
}

void MLDonkeyApplet::showGUI(bool state)
{
    if (state && !isGUIRunning()) {
        // launch gui
        if (KApplication::startServiceByDesktopName(QString("kmldonkey")) > 0)
            gui->setLaunchState(false);
        return;
    }
    QByteArray data;
    QDataStream arg(data, IO_WriteOnly);
    if (state)
        arg << true;
    else
        arg << false;
    client->send("kmldonkey", "KMLDonkey", "setShown(bool)", data);
}

bool MLDonkeyApplet::isGUIRunning() const
{
    QCString name = "kmldonkey";
    QCStringList apps = client->registeredApplications();
    QCStringList::iterator it;
    for (it = apps.begin(); it != apps.end(); ++it)
        if (*it == name) return true;
    return false;
}

bool MLDonkeyApplet::isGUIVisible() const
{
    QCString replyType;
    QByteArray data, replyData;
    QDataStream reply(replyData, IO_ReadOnly);
    if (!client->call("kmldonkey", "KMLDonkey", "isVisible()", data, replyType, replyData, true))
        return false;
    if (replyType != "bool") return false;
    bool state;
    reply >> state;
    return state;
}

void MLDonkeyApplet::muteDonkey(bool m)
{
    donkey->setOption("max_hard_upload_rate", QString::number(m ? muteUploadRate : normalUploadRate));
    donkey->setOption("max_hard_download_rate", QString::number(m ? muteDownloadRate : normalDownloadRate));
}

void MLDonkeyApplet::dragEnterEvent(QDragEnterEvent* event)
{
    event->accept(QUriDrag::canDecode(event));
}

void MLDonkeyApplet::dropEvent(QDropEvent* event)
{
    KURL::List uri;

    if (KURLDrag::decode(event, uri))
    {
        QValueListConstIterator<KURL> it;
        for (it = uri.begin(); it != uri.end(); ++it)
            donkey->submitURL((*it).url());
    }
}

void MLDonkeyApplet::mousePressEvent(QMouseEvent *event)
{
    if (event == 0) return;
    if (event->button() == RightButton)
        menu->popup(mapToGlobal(event->pos()));
}


// DCOP methods

void MLDonkeyApplet::kmldonkeyAppeared(bool a)
{
    gui->setLaunchState(a);
}

void MLDonkeyApplet::mute(bool m)
{
    gui->mute(m);
}

bool MLDonkeyApplet::isMute()
{
    return gui->isMute();
}
