//-*-c++-*-
/***************************************************************************
 *   Copyright (C) 2003 by Fred Schaettgen                                 *
 *   kbluetoothd@schaettgen.de                                             *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#include "trayicon.h"

#include <algorithm>
//#include <kaboutapplication.h>
#include <kaction.h>
#include <kapplication.h>
//#include <kbugreport.h>
#include <kconfig.h>
#include <kdebug.h>
#include <kglobal.h>
#include <khelpmenu.h>
#include <kiconloader.h>
#include <kicontheme.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <knotifydialog.h>
#include <knotifyclient.h>
#include <kpixmapeffect.h>
#include <kpopupmenu.h>
#include <kprocess.h>
#include <krun.h>
#include <qevent.h>
#include <qtooltip.h>

#include <libkbluetooth/deviceaddress.h>
#include <libkbluetooth/devicemimeconverter.h>

#include "devicenamecache.h"
#include "application.h"
#include "connectiondlg.h"
#include "devicescanner.h"
#include "mostrecentlyused.h"
#include "procinheritsock.h"
#include "mostrecentlyused.h"
#include "hcilistener.h"

using namespace KBluetooth;
using namespace std;

TrayIcon::TrayIcon(KBluetoothdApp* app) :
    usedServicesBase(1000),
    seenDevicesBase(2000)
{
    iconConnectingBlinkState = false;
    this->helpMenu = new KHelpMenu(this, KApplication::kApplication()->aboutData());
    this->app = app;
    //iconState = IDLE;
    KConfig *config = KGlobal::config();
    config->setGroup("UI");
    alwaysVisible = config->readBoolEntry("alwaysVisible", true);
    hasAdapter = true;
    acceptClose = true;
    blinkTimer = new QTimer(this);
    connect(blinkTimer, SIGNAL(timeout()), this, SLOT(updateIcon()));

    // Toplevel menu:
    // --------------

    // MRU services menu
    mruMenu = new KPopupMenu(this, "mru_menu");
    contextMenu()->insertItem(KGlobal::iconLoader()->loadIcon("history", KIcon::Small, 16),
        i18n("Open &Recent"), mruMenu);
    connect(mruMenu, SIGNAL(activated(int)),
        this, SLOT(mruItemClicked(int)));

    // show connection monitor
    showMonitorAction = new KAction(i18n("Connection &Details"),
        KGlobal::iconLoader()->loadIcon("info", KIcon::Small, 16),
        KShortcut::null(), this, "connection_monitor");
    connect(showMonitorAction, SIGNAL(activated()), this, SLOT(slotShowMonitor()));
    showMonitorAction->plug(contextMenu());

    contextMenu()->insertSeparator();

    // Configuration menu
    configActionMenu = new KActionMenu(i18n("&Configuration"),
        KGlobal::iconLoader()->loadIcon("configure", KIcon::Small, 16), this, "config_menu");
    configActionMenu->plug(contextMenu());

    // Help menu
    helpActionMenu = new KActionMenu(i18n("&Help"),
        KGlobal::iconLoader()->loadIcon("help", KIcon::Small, 16), this, "help_menu");
    helpActionMenu->plug(contextMenu());

    // Menu entries:
    // -------------

    // Service configuration
    serviceConfigAction = new KAction(i18n("Configure &Services..."),
        KGlobal::iconLoader()->loadIcon("configure", KIcon::Small, 16),
        KShortcut::null(), this, "service_config");
    connect(serviceConfigAction, SIGNAL(activated()), this, SLOT(slotServiceConfig()));
    configActionMenu->insert(serviceConfigAction);

    // Paired device configuration
    pairedConfigAction = new KAction(i18n("&Paired Devices..."),
        KGlobal::iconLoader()->loadIcon("configure", KIcon::Small, 16),
        KShortcut::null(), this, "paired_config");
    connect(pairedConfigAction, SIGNAL(activated()), this, SLOT(slotPairedConfig()));
    configActionMenu->insert(pairedConfigAction);

    // Disabled, since khciconfig is not yet a control center module...
    /*deviceConfigAction = new KAction(this, "device_config");
    deviceConfigAction->setText(i18n("Devices..."));
    connect(deviceConfigAction, SIGNAL(activated()), this, SLOT(slotDeviceConfig()));
    configActionMenu->insert(deviceConfigAction);*/

    // configure notifications
    KAction* notificationAction = KStdAction::configureNotifications(this,
        SLOT(slotConfigureNotifications()), actionCollection());
    configActionMenu->insert(notificationAction);

    // Always-visible switch
    showIconAction = new KToggleAction(this, "always_visible");
    connect(showIconAction, SIGNAL(toggled(bool)), this, SLOT(slotShowIconToggled(bool)));
    showIconAction->setChecked(alwaysVisible);
    showIconAction->setText(i18n("Always &Visible"));
    configActionMenu->insert(showIconAction);

    // Clear mru services list
    mruClearAction = new KAction(i18n("Clear List"), "history_clear", KShortcut::null(), this, "service_config");
    connect(mruClearAction, SIGNAL(activated()), this, SLOT(slotMruServicesClear()));

    // show documentation
    KAction* showHelpAction = KStdAction::help(this,
        SLOT(slotShowHelp()), actionCollection());
    helpActionMenu->insert(showHelpAction);

    // Report bug menu item
    KAction* reportBugAction = KStdAction::reportBug(this,
        SLOT(slotReportBug()), actionCollection());
    helpActionMenu->insert(reportBugAction);

    // "About" menu item
    KAction* aboutAction = KStdAction::aboutApp(this,
        SLOT(slotAbout()), actionCollection());
    helpActionMenu->insert(aboutAction);

    slotMruMenuUpdate();

    KPixmap logoPixmap = KGlobal::iconLoader()->loadIcon(
        "kdebluetooth", KIcon::Small, 22);
    iconIdle = logoPixmap;
    iconConnected = logoPixmap;
    iconNoAdapter = logoPixmap;
    KPixmapEffect::toGray(iconIdle);
    KPixmapEffect::fade(iconIdle, 0.3, QColor(255,255,255));
    KPixmapEffect::toGray(iconNoAdapter);
    KPixmapEffect::fade(iconNoAdapter, 0.5, QColor(128,128,128));
    updateIcon();

    connect(this, SIGNAL(quitSelected()),
        this, SLOT(slotQuitSelected()));

    connect(app->mru, SIGNAL(updateReceived()),
        this, SLOT(slotMruMenuUpdate()));
}

TrayIcon::~TrayIcon()
{
}

void TrayIcon::setAlwaysShowIcon(bool state) {
    showIconAction->setChecked(state);
    slotShowIconToggled(state);
}

void TrayIcon::slotShowIconToggled(bool state)
{
    KConfig *config = KGlobal::config();
    config->setGroup("UI");
    config->writeEntry("alwaysVisible", state);
    config->sync();
    alwaysVisible = state;
    if (state == false) {
        KMessageBox::information(this,
            i18n("KBluetoothD will run in hidden mode now and only show up when there is \
a connection. To enable the permanent tray icon start KBluetoothD again."),
            i18n("Hidden Mode"), "hiddenmode_messagebox");
    }
    updateIcon();
}

void TrayIcon::updateIcon()
{
    QString oldText = QToolTip::textFor(this);
    QString newText = "";
    if (connections.size() == 0 || !hasAdapter) {
        if (alwaysVisible) {
            if (hasAdapter) {
               setPixmap(iconIdle);
            }
            else {
                setPixmap(iconNoAdapter);
            }
            show();
        } else {
            hide();
        }
        //showMonitorAction->setEnabled(false);
        newText = i18n("Not connected");
    }
    else {
        bool connecting = false;
        std::map<KBluetooth::DeviceAddress, Connection>::iterator it;
        for (it = connections.begin(); it != connections.end(); ++it) {
            if (it->second.state == KBluetooth::Adapter::CONNECTING) {
                connecting = true;
            }
        }
        if (connecting) {
            if (!blinkTimer->isActive()) blinkTimer->start(250);
            iconConnectingBlinkState = !iconConnectingBlinkState;
            if (iconConnectingBlinkState) {
                setPixmap(iconConnected);
            }
            else {
                setPixmap(iconIdle);
            }
        }
        else {
            blinkTimer->stop();
            setPixmap(iconConnected);
        }
        show();
        //showMonitorAction->setEnabled(true);
        QString toolTipText = QString("<b></b>")+i18n("Connected to ");
        std::map<DeviceAddress, Connection>::iterator conIt;
        for (conIt = connections.begin(); conIt != connections.end(); ++conIt) {
            QString devName;
            if (connections.size() > 1) {
                toolTipText += "<br/>";
            }
            if ((devName = app->deviceNameCache->getCachedDeviceName(conIt->first))
                != QString::null)
            {
                toolTipText += QString(
                    "<b>%1</b> [%2]").arg(devName).arg(QString(conIt->first));
            }
            else {
                toolTipText += QString("[<b>%1</b>]").arg(QString(conIt->first));
            }
        }
        toolTipText += "</p>";
        newText = toolTipText;
    }
    if (oldText != newText) {
        QToolTip::remove(this);
        QToolTip::add(this, newText);
    }
}

void TrayIcon::slotServiceConfig()
{
    // We have to run the control center module with
    // KProcessInheritSocket, because otherwise the kcm
    // would inherit all open sockets and cause problems
    // when kbluetoothd::MetaServer::reload is called somewhere
    KProcessInheritSocket process(0);
    process << "kcmshell" << "kcm_kbluetoothd";
    if (!process.start(KProcess::DontCare)) {
        KMessageBox::information(this,
            i18n("Could not execute the kbluetoothd Control Center module."),
            i18n("KBluetoothD"));

    }
}

void TrayIcon::slotPairedConfig()
{
    KProcessInheritSocket process(0);
    process << "kdesu" << "--nonewdcop" << "-i" << "kdebluetooth" << "kcmshell" << "kcm_btpaired";
    if (!process.start(KProcess::DontCare)) {
        KMessageBox::information(this,
            i18n("Could not execute the btpaired Control Center module."),
            i18n("KBluetoothD"));

    }
}

void TrayIcon::slotDeviceConfig()
{
    KProcessInheritSocket process(0);
    process << "kdesu" << "--nonewdcop" << "kcmshell" << "kcm_khciconfig";
    if (!process.start(KProcess::DontCare)) {
        KMessageBox::information(this,
            i18n("Could not execute the khciconfig Control Center module."),
            i18n("KBluetoothD"));

    }
}

void TrayIcon::slotConfigureNotifications()
{
    KNotifyDialog::configure(this);
}

void TrayIcon::mousePressEvent(QMouseEvent *e)
{
    if (e->button() == QMouseEvent::LeftButton) {
        e->accept();
        KProcessInheritSocket proc(0);
        proc << "kfmclient" << "openURL" << "bluetooth:/";
        proc.start(KProcess::DontCare);
    }
    else {
        KSystemTray::mousePressEvent(e);
    }
}

void TrayIcon::slotConnectionStateChanged()
{
    kdDebug() << "slotConnectionStateChanged" << endl;
    Adapters adapters;
    Adapter::ConnectionInfoVector::iterator conIt;
    if (adapters.count() > 0) {
        Adapter::ConnectionInfoVector curConnections = adapters[0].getAclConnections();

        // Update the connection state for every connection
        std::map<DeviceAddress, Connection> newConnections;
        for (conIt = curConnections.begin(); conIt != curConnections.end(); ++conIt) {
            Adapter::ConnectionState state = adapters[0].getAclConnectionState(conIt->address);
            kdDebug() << "Checking state" << QString(conIt->address)
                << " " << int(state) << endl;
            if (!state == Adapter::NOT_CONNECTED) {
                Connection c;
                c.state = state;
                newConnections[conIt->address] = c;
            }
        }
        connections = newConnections;
    }
    else {
        connections.clear();
    }

    kdDebug() << "connections: " << connections.size() << endl;
    updateIcon();
}

void TrayIcon::slotShowMonitor()
{
    ConnectionDlg* m = new ConnectionDlg(this);
    m->show();
}

void TrayIcon::closeEvent(QCloseEvent *e)
{
    if (acceptClose) {
        QWidget::closeEvent(e);
    }
    else {
        e->ignore();
    }
    acceptClose = true;
}

void TrayIcon::slotQuitSelected()
{
    // Ask if the user want to simply quit or disable
    // automatic start of kbluetoothd
    int autoStart = KMessageBox::questionYesNoCancel( 0,
                                                      i18n("Should KBluetoothD still be restarted when you login?"),
                                                      i18n("Automatically Start KBluetoothD?"),i18n("Start"), i18n("Do Not Start") );
    acceptClose = true;
    KConfig *config = KGlobal::config();
    config->setGroup("General");
    if (autoStart == KMessageBox::Yes) {
        config->writeEntry("AutoStart", true);
    }
    else if (autoStart == KMessageBox::No) {
        config->writeEntry("AutoStart", false);
    } else {
        acceptClose = false;
        return;
    }
    KMessageBox::information(this,
        i18n("You can reenable the Bluetooth daemon at any time by running 'kbluetoothd'."),
        i18n("KDE Bluetooth Daemon"), "quitrestart_messagebox");

    config->sync();
}

void TrayIcon::slotDeviceFound()
{
    hasAdapter = true;
    updateIcon();
}

void TrayIcon::slotDeviceLost()
{
    hasAdapter = false;
    updateIcon();
    QToolTip::remove(this);
    QToolTip::add(this, i18n("No adapter"));
}

void TrayIcon::slotNoStartupDevice()
{
    hasAdapter = false; // for later use if !adapterMonitor
    slotDeviceLost();
}

void TrayIcon::slotReportBug()
{
    helpMenu->reportBug();
}

void TrayIcon::slotAbout()
{
    helpMenu->aboutApplication();
}

void TrayIcon::slotShowHelp()
{
    // TODO: This is surely not the correct way to jump to the
    // right chapter. Do I really have to mention the html-file,
    // or is the id enough?
    KApplication::kApplication()->invokeHelp("",
        "kdebluetooth/components.html#components.kbluetoothd");
}

void TrayIcon::slotMruMenuUpdate()
{
    kdDebug() << "TrayIcon::slotMruMenuUpdate" << endl;
    for (int n=mruMenu->count()-1; n>=0; --n) {
        mruMenu->removeItemAt(n);
    }

    QStringList neighbors;
    if (app->deviceScanner) {
        neighbors = app->deviceScanner->getCurrentNeighbours();
    }
    if (neighbors.size() > 0) {
        mruMenu->insertTitle(KGlobal::iconLoader()->loadIcon("find", KIcon::Small, 16),
            i18n("Recently Seen Devices"));
        kdDebug() << "mruMenuUpdate neighbors=" << neighbors.size() << endl;
        for (size_t n=0; n<neighbors.size(); ++n) {
            int deviceClass = app->deviceNameCache->getCachedDeviceClass(neighbors[n]);
            QString iconName = DeviceClassMimeConverter::classToIconName(deviceClass);
            if (devClassIconMap.find(iconName) == devClassIconMap.end()) {
                devClassIconMap[iconName] = KGlobal::iconLoader()->loadIcon(
                    iconName, KIcon::Small, 16);
            }
            QString devName = app->deviceNameCache->getCachedDeviceName(neighbors[n]);
            if (devName.isNull()) devName = neighbors[n];
            mruMenu->insertItem(devClassIconMap[iconName], devName, n+seenDevicesBase);
        }
    }

    if (!app || !app->mru) return;
    std::deque<MostRecentlyUsed::MruEntry> mrus = app->mru->mruEntries;
    mruClearAction->setEnabled(mrus.size() > 0);
    mruMenu->insertTitle(KGlobal::iconLoader()->loadIcon("history", KIcon::Small, 16),
        i18n("Recently Used Services"));
    for (int n=0; n<min(8, int(mrus.size())); ++n) {
        QString addrName = mrus[n].deviceaddress;
        KBluetooth::DeviceAddress addr(addrName);
        addrName = app->deviceNameCache->getCachedDeviceName(addr);
        if (mruAppIconMap.find(mrus[n].iconname) == mruAppIconMap.end()) {
            KPixmap icon = KGlobal::iconLoader()->loadIcon(
                mrus[n].iconname, KIcon::Small, 16);
            mruAppIconMap[mrus[n].iconname] = icon;
        }
        mruMenu->insertItem(mruAppIconMap[mrus[n].iconname],
            QString("%2 - %1").arg(mrus[n].label).arg(addrName),
            n+usedServicesBase);
    }

    mruMenu->insertSeparator();
    mruClearAction->plug(mruMenu);
}

void TrayIcon::slotMruServicesClear()
{
    if (!app || !app->mru) return;
    app->mru->mruClear();
    slotMruMenuUpdate();
}

void TrayIcon::mruItemClicked(int id)
{
    if (!app || !app->mru) return;

    if (id >= usedServicesBase && id < int(app->mru->mruEntries.size())+usedServicesBase) {
        kdDebug() << "mruItemClicked: service " << endl;
        MostRecentlyUsed::MruEntry e = app->mru->mruEntries[id-usedServicesBase];
        KProcessInheritSocket proc(0);
        for (size_t n=0; n<e.commandline.count(); ++n) {
            proc << e.commandline[n];
        }
        proc.start(KProcess::DontCare);
    }
    if (id >= seenDevicesBase &&
        id < int(app->deviceScanner->getCurrentNeighbours().size())+seenDevicesBase)
    {
        kdDebug() << "mruItemClicked: device " << endl;

        QString deviceName = mruMenu->text(id);
        KProcessInheritSocket proc(0);
        proc << "kfmclient" << "openURL" << QString("sdp://[%1]/").arg(deviceName);
        proc.start(KProcess::DontCare);
    }
}

QString TrayIcon::localAndEnglish(const QCString& s)
{
    if (QString(s) != i18n(s)) {
        return QString("%1 (\"%2\")").arg(i18n(s)).arg(s);
    }
    else {
        return s;
    }
}

void TrayIcon::slotConnectionComplete(int status, KBluetooth::DeviceAddress addr)
{
    if (status == 0) return;
    kdDebug() << "TrayIcon::slotConnectionComplete()" << endl;
    QString name = app->deviceNameCache->getCachedDeviceName(QString(addr));
    if (name.length() == 0) name = QString(addr);

    QString errorMessage = i18n("error code 0x%1").arg(status, 0, 16);
    switch (status) {
    case 0x01: errorMessage = localAndEnglish(I18N_NOOP("No Connection")); break; // Unknown HCI command
    case 0x02: errorMessage = localAndEnglish(I18N_NOOP("No Connection")); break;
    case 0x03: errorMessage = localAndEnglish(I18N_NOOP("Hardware failure")); break;
    case 0x04: errorMessage = localAndEnglish(I18N_NOOP("Page Timeout")); break;
    case 0x05: errorMessage = localAndEnglish(I18N_NOOP("Authentication Error")); break;
    case 0x06: errorMessage = localAndEnglish(I18N_NOOP("Not paired")); break; // key missing
    case 0x07: errorMessage = localAndEnglish(I18N_NOOP("Memory full")); break;
    case 0x08: errorMessage = localAndEnglish(I18N_NOOP("Connection timeout")); break;
    case 0x09: errorMessage = localAndEnglish(I18N_NOOP("Maximum number of connections")); break;
    case 0x0a: errorMessage = localAndEnglish(I18N_NOOP("Maximum number of SCO connections to a device")); break;
    case 0x0b: errorMessage = localAndEnglish(I18N_NOOP("ACL connection already exists")); break;
    case 0x0c: errorMessage = localAndEnglish(I18N_NOOP("ACL connection already exists")); break;
    case 0x0d: errorMessage = localAndEnglish(I18N_NOOP("Host rejected due to limited resources")); break;
    case 0x0e: errorMessage = localAndEnglish(I18N_NOOP("Host rejected for security reasons")); break;
    case 0x0f: errorMessage = localAndEnglish(I18N_NOOP("Host rejected because remote device is only a personal device")); break;
    case 0x10: errorMessage = localAndEnglish(I18N_NOOP("Host timeout")); break;
    case 0x11: errorMessage = localAndEnglish(I18N_NOOP("Unsupported feature or parameter value")); break;
    case 0x12: errorMessage = localAndEnglish(I18N_NOOP("Invalid HCI command parameters")); break;
    case 0x13: errorMessage = localAndEnglish(I18N_NOOP("Other end terminated connection: User ended connection")); break;
    case 0x14: errorMessage = localAndEnglish(I18N_NOOP("Other end terminated connection: Low resources")); break;
    case 0x15: errorMessage = localAndEnglish(I18N_NOOP("Other end terminated connection: Imminent power off")); break;
    case 0x16: errorMessage = localAndEnglish(I18N_NOOP("Connection terminated by local host")); break;
    case 0x17: errorMessage = localAndEnglish(I18N_NOOP("Repeated attempts")); break;
    case 0x18: errorMessage = localAndEnglish(I18N_NOOP("Pairing not allowed")); break;
    case 0x19: errorMessage = localAndEnglish(I18N_NOOP("Unknown LMP PDU")); break;
    case 0x1a: errorMessage = localAndEnglish(I18N_NOOP("Unsupported remote feature")); break;
    case 0x1b: errorMessage = localAndEnglish(I18N_NOOP("SCO offset rejected")); break;
    case 0x1c: errorMessage = localAndEnglish(I18N_NOOP("SCO interval rejected")); break;
    case 0x1d: errorMessage = localAndEnglish(I18N_NOOP("SCO air mode rejected")); break;
    case 0x1e: errorMessage = localAndEnglish(I18N_NOOP("Invalid LMP parameter")); break;
    case 0x1f: errorMessage = localAndEnglish(I18N_NOOP("Unspecified error")); break;
    case 0x20: errorMessage = localAndEnglish(I18N_NOOP("Unsupported LMP parameter value")); break;
    case 0x21: errorMessage = localAndEnglish(I18N_NOOP("Role change not allowed")); break;
    case 0x22: errorMessage = localAndEnglish(I18N_NOOP("LMP response timeout")); break;
    case 0x23: errorMessage = localAndEnglish(I18N_NOOP("LMP error transaction collision")); break;
    case 0x24: errorMessage = localAndEnglish(I18N_NOOP("LMP PDU not allowed")); break;
    case 0x25: errorMessage = localAndEnglish(I18N_NOOP("Encryption mode not acceptable")); break;
    case 0x26: errorMessage = localAndEnglish(I18N_NOOP("Unit key used")); break;
    case 0x27: errorMessage = localAndEnglish(I18N_NOOP("QoS is not supported")); break;
    case 0x28: errorMessage = localAndEnglish(I18N_NOOP("Instant passed")); break;
    case 0x29: errorMessage = localAndEnglish(I18N_NOOP("Pairing with unit key not supported")); break;
    // 0x2a-0xff 'reserved for future use'
    }


    KNotifyClient::event(
#if (QT_VERSION >= 0x030200)
        KApplication::kApplication()->mainWidget()->winId(),
#endif
        "ConnectionError",
        i18n("<i>Bluetooth Monitor</i><br/> Problem connecting with <b>%1</b>:")
            .arg(name)+"<br/>"+errorMessage);
}


#include "trayicon.moc"
