/*
 *
 *  Dialogs for tdebluez device configuration
 *
 *  Copyright (C) 2018  Emanoil Kotsev <deloptes@gmail.com>
 *
 *
 *  This file is part of tdebluez.
 *
 *  tdebluez 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.
 *
 *  tdebluez 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 kbluetooth; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <tqradiobutton.h>
#include <tqpushbutton.h>
#include <tqprogressbar.h>
#include <tqcheckbox.h>
#include <tdeconfig.h>
#include <knotifydialog.h>
#include <knotifyclient.h>

#include <interfaces/device1Proxy.h>
#include <btuuids.h>
#include <devicemimeconverter.h>

#include "application.h"
#include "devicesetupwizard.h"

#define LOGOTIMEOUT       100 //100 msec
#define ASYNC_TIMEOUT   15000 //15 sec
#define CONNECT_TIMEOUT  5000 // 5 sec
#define PROGRESS_TIMEOUT   50 // 50 msec

DeviceSetupWizard::DeviceSetupWizard(ObjectManagerImpl* _manager) :
        DeviceSetupWizardDialog(), manager(_manager)
{
    device = 0;
    address = TQString();

    pairpage = page(0);
    setHelpEnabled(pairpage, false);

    pairingpage = page(1);
    setHelpEnabled(pairingpage, false);

    connectpage = page(2);
    setHelpEnabled(connectpage, false);

    connectingpage = page(3);
    setHelpEnabled(connectingpage, false);

    donepage = page(4);
    setHelpEnabled(donepage, false);
    setFinishEnabled(donepage, true);
    cancelButton()->setText(i18n("S&kip Wizard"));

    setModal(true);

    m_config = TDEGlobal::config();

    // create the first ListView
    tQListViewSrc->setRootIsDecorated(TRUE);
    tQListViewSrc->setSelectionMode(TQListView::Multi);
    tQListViewSrc->clear();

    // create the second ListView
    tQListViewDst->setRootIsDecorated(TRUE);
    tQListViewDst->setSelectionMode(TQListView::Multi);
    tQListViewDst->clear();

    // progress bars
    pairingProgressBar->setProgress(0,ASYNC_TIMEOUT);
    pairingProgressBar->setPercentageVisible(false);
    connectingProgressBar->setProgress(0,ASYNC_TIMEOUT);
    connectingProgressBar->setPercentageVisible(false);

    pairingTimer = new TQTimer(this);
    connectTimer = new TQTimer(this);
    connect(pairingTimer, SIGNAL(timeout()), this, TQT_SLOT(slotAdvancePairingProgressBar()));
    connect(connectTimer, SIGNAL(timeout()), this, TQT_SLOT(slotAdvanceConnectProgressBar()));

    connect(manager, SIGNAL(deviceServicesResolvedChanged(const TQString&, bool)),
            this, TQT_SLOT(slotDeviceServicesResolvedChanged(const TQString&, bool)));

    connect(buttonSrc2Dst, SIGNAL(clicked()), this, SLOT(slotCopySrc2Dst()));
    connect(buttonDst2Src, SIGNAL(clicked()), this, SLOT(slotCopyDst2Src()));
    connect(cancelPairingButton, SIGNAL(clicked()), this, SLOT(slotCancelPairing()));
    connect(cancelConnectButton, SIGNAL(clicked()), this, SLOT(slotCancelConnecting()));
}

DeviceSetupWizard::~DeviceSetupWizard()
{
}

void DeviceSetupWizard::next()
{
    if (pairingTimer->isActive())
    {
        pairingTimer->stop();
    }
    if (connectTimer->isActive())
    {
        connectTimer->stop();
    }

    if (currentPage() == pairpage)
    {
        if (pairingRadioButton1->isChecked())
        {
            pairingProgressBar->setProgress(0,ASYNC_TIMEOUT);
            pairingTimer->start(PROGRESS_TIMEOUT);
            setNextEnabled(pairpage, false);
            setNextEnabled(pairingpage, false);
            TQWizard::showPage(pairingpage);
            startPairing();
        }
        else
            TQWizard::showPage(donepage);
    }
    else if (currentPage() == connectpage)
    {
        preferredProfiles.clear();
        TQListViewItemIterator it2(tQListViewDst);
        while (it2.current())
        {
            TQString selText = it2.current()->text(0);
            for (TQStringList::iterator it3 = uuids.begin(); it3 != uuids.end();
                    ++it3)
            {
                TQString u = (*it3);
                if (selText == resolveUUID(u))
                {
                    kdDebug() << "REQUESTED UUID: " << u << endl;
                    preferredProfiles.append(u);
                }
            }
            ++it2;
        }

        m_config->setGroup(address);
        m_config->writeEntry("profile", preferredProfiles);
        m_config->sync();

        // set the progress bar depending on the number of profiles to be connected
        // and CONNECT_TIMEOUT value
//        connectingProgressBar->setProgress(0, (ASYNC_TIMEOUT + CONNECT_TIMEOUT) * preferredProfiles.count());
        connectingProgressBar->setProgress(0,ASYNC_TIMEOUT);

        connectTimer->start(PROGRESS_TIMEOUT);
        TQWizard::showPage(connectingpage);

        slotConnectNextProfile();
    }
//    else if (currentPage() == connectingpage)
//    {
//        TQT_DBusError error;
//        if (!device->getConnected(error))
//        {
//            int asyncCallId=0;
//            device->ConnectAsync(asyncCallId, error);
//            manager->getConnection()->scheduleDispatch();
//        }
//        else
//        {
//            TQWizard::next();
//        }
//        if (error.isValid())
//            tqDebug(i18n("Failed in connecting device: %1").arg(error.message()));
//    }
    else if (currentPage() == donepage)
    {
        if (trustedCheckBox->isChecked())
        {
            finishButton()->setFocus();
        }
        else
        {
            trustedCheckBox->setFocus();
        }
    }
}

void DeviceSetupWizard::back()
{
    TQWizard::back();
}

void DeviceSetupWizard::setDevice(DeviceImpl *_device)
{
    kdDebug() << "New device: " << _device << endl;

    if (device == _device)
        return;

    if (device)
        closeDevice();

    device = _device;

    TQWizard::showPage(pairpage);
    setNextEnabled(pairpage, true);

    TQT_DBusError error;
    address = device->getAddress(error);
    if (error.isValid())
        tqDebug(i18n("Failed to get address for the new device: %1").arg(error.message()));

    if (device->getPaired(error))
    {
        updateServiceList();
        preferredProfiles.clear();
        tQListViewDst->clear();
        m_config->setGroup(address);
        preferredProfiles = m_config->readListEntry("profile");
        TQStringList::iterator it = preferredProfiles.begin();
        for (it; it != preferredProfiles.end(); ++it)
        {
            (void) new TQListViewItem(tQListViewDst, resolveUUID(*it));
        }
        setAppropriate(pairpage, false);
        if (tQListViewDst->childCount() > 0)
            setNextEnabled(connectpage, true);
        TQWizard::showPage(connectpage);
    } else {
        tQListViewDst->clear();
    }
    if (error.isValid())
        tqDebug(i18n("Failed to get paired status for the new device: %1").arg(error.message()));

    if (device->getConnected(error))
    {
        setAppropriate(pairpage, false);
        setAppropriate(pairingpage, false);
        setAppropriate(connectpage, false);
        setAppropriate(connectingpage, false);
        TQWizard::showPage(donepage);
    }
    if (error.isValid())
        tqDebug(i18n("Failed to get connecting status of the new device: %1").arg(error.message()));

    if (device->getTrusted(error))
        trustedCheckBox->setChecked(true);
    if (error.isValid())
        tqDebug(i18n("Failed to get trusted status of the new device: %1").arg(error.message()));

    connect(device, SIGNAL(PairAsyncReply(int /*asyncCallId*/)),
            this, TQT_SLOT(slotPairAsyncReply(int /*asyncCallId*/)));
    connect(device, SIGNAL(CancelPairingAsyncReply(int /*asyncCallId*/)),
            this, TQT_SLOT(slotCancelPairingAsyncReply(int /*asyncCallId*/)));
    connect(device, SIGNAL(AsyncErrorResponseDetected(int /*asyncCallId*/, const TQT_DBusError)),
            this, TQT_SLOT(slotAsyncErrorResponseDetected(int /*asyncCallId*/, const TQT_DBusError)));
    connect(device, SIGNAL(ConnectAsyncReply(int /*asyncCallId*/)),
            this, TQT_SLOT(slotConnectAsyncReply(int /*asyncCallId*/)));
//    connect(device, SIGNAL(DisonnectAsyncReply(int /*asyncCallId*/)),
//            this, TQT_SLOT(slotDisconnectAsyncReply(int /*asyncCallId*/)));
    connect(device, SIGNAL(ConnectProfileAsyncReply(int /*asyncCallId*/)),
            this, TQT_SLOT(slotConnectProfileAsyncReply(int /*asyncCallId*/)));
//    connect(device, SIGNAL(DisconnectProfileAsyncReply(int /*asyncCallId*/)),
//            this, TQT_SLOT(slotDisconnectProfileAsyncReply(int /*asyncCallId*/)));
}

void DeviceSetupWizard::updateServiceList()
{
    TQT_DBusError error;
    uuids.clear();
    uuids = device->getUUIDs(error);
    if (error.isValid())
        tqDebug(i18n("Failed to get uuids: %1").arg(error.message()));

    tQListViewSrc->clear();
    for (TQStringList::iterator it = uuids.begin(); it != uuids.end(); ++it)
    {
        if (
                ((*it) == "00001203-0000-1000-8000-00805f9b34fb") || //Generic Audio
                ((*it) == "00001108-0000-1000-8000-00805f9b34fb") || //Headset
                ((*it) == "0000111e-0000-1000-8000-00805f9b34fb") || //Handsfree
                ((*it) == "0000111f-0000-1000-8000-00805f9b34fb") || //Handsfree AG
                ((*it) == "0000110a-0000-1000-8000-00805f9b34fb") || //A2DP Source
                ((*it) == "0000110b-0000-1000-8000-00805f9b34fb") || //A2DP Sink
                ((*it) == "00001103-0000-1000-8000-00805f9b34fb") || //DUN Gateway
                ((*it) == "00001800-0000-1000-8000-00805f9b34fb")    //GAP
        )
        {
            (void) new TQListViewItem(tQListViewSrc, resolveUUID((*it)));
        }
    }
}

void DeviceSetupWizard::startPairing()
{
    TQT_DBusError error;
    int asyncCallId = 0;
    if (!device->PairAsync(asyncCallId, error))
    {
        if (error.isValid())
            tqDebug(i18n("Failed to get paired status for the new device: %1").arg(error.message()));
    }
    manager->getConnection()->scheduleDispatch();
}

void DeviceSetupWizard::slotPairingTimeOut()
{
    if(pairingTimer->isActive())
        pairingTimer->stop();

    if (!device)
        return;

    TQT_DBusError error;
    if (!device->getPaired(error))
    {
        if (!error.isValid())
        {
            TQWizard::showPage(pairpage);
            setNextEnabled(pairpage, true);
        }
        else
            tqDebug(i18n("Failed pairing the new device: %1").arg(error.message()));
    }
    else
    {
        if (tQListViewDst->childCount() > 0)
            setNextEnabled(connectpage, true);
        TQWizard::showPage(connectpage);
    }
}

void DeviceSetupWizard::slotConnectTimeOut()
{
    if(connectTimer->isActive())
        connectTimer->stop();

    if (!device)
        return;

    TQT_DBusError error;
    if (!device->getConnected(error))
    {
        if (!error.isValid())
        {
            TQWizard::showPage(connectpage);
            if (tQListViewDst->childCount() > 0)
                setNextEnabled(connectpage, true);
            setNextEnabled(connectpage, true);
        }
        else
            tqDebug(i18n("Failed connecting the new device: %1").arg(error.message()));
    }
    else
    {
        setNextEnabled(connectingpage, false);
        setBackEnabled(donepage, false);
        TQWizard::showPage(donepage);
    }
}

/** the cancel button is connected to the reject() slot of TQDialog,
 *  so we have to reimplement this here to add a dialogbox to ask if we
 *  really want to quit the wizard.
 */
void DeviceSetupWizard::reject()
{
    close(); // this will trigger the close event caught below
}

void DeviceSetupWizard::closeEvent(TQCloseEvent* e)
{
    if (askClose())
    {
        hide();
        closeDevice();
    }
    else
    {
        e->ignore();
    }
}

/** maybe call a dialog that the wizard has finished. */
void DeviceSetupWizard::accept()
{
    TQT_DBusError error;
    if (trustedCheckBox->isChecked())
    {
        if (!device->getTrusted(error))
        {
            device->setTrusted(true, error);
        }
        if (error.isValid())
            tqDebug(i18n("Could not set trusted for %1\nError: %2").arg(address).arg(error.message()));
    }

    hide();
    closeDevice();
}

void DeviceSetupWizard::closeDevice()
{
// make sure timers go down
    if (pairingTimer->isActive())
    {
        pairingTimer->stop();
    }
    if (connectTimer->isActive())
    {
        connectTimer->stop();
    }

    if (!device)
        return;

    disconnect(device, SIGNAL(PairAsyncReply(int /*asyncCallId*/)),
            this, TQT_SLOT(slotPairAsyncReply(int /*asyncCallId*/)));
    disconnect(device, SIGNAL(CancelPairingAsyncReply(int /*asyncCallId*/)),
            this, TQT_SLOT(slotCancelPairingAsyncReply(int /*asyncCallId*/)));
    disconnect(device, SIGNAL(AsyncErrorResponseDetected(int /*asyncCallId*/, const TQT_DBusError)),
            this, TQT_SLOT(slotAsyncErrorResponseDetected(int /*asyncCallId*/, const TQT_DBusError)));
    disconnect(device, SIGNAL(ConnectAsyncReply(int /*asyncCallId*/)),
            this, TQT_SLOT(slotConnectAsyncReply(int /*asyncCallId*/)));
//    disconnect(device, SIGNAL(DisonnectAsyncReply(int /*asyncCallId*/)),
//            this, TQT_SLOT(slotDisconnectAsyncReply(int /*asyncCallId*/)));
    disconnect(device, SIGNAL(ConnectProfileAsyncReply(int /*asyncCallId*/)),
            this, TQT_SLOT(slotConnectProfileAsyncReply(int /*asyncCallId*/)));
//    connect(device, SIGNAL(DisconnectProfileAsyncReply(int /*asyncCallId*/)),
//            this, TQT_SLOT(slotDisconnectProfileAsyncReply(int /*asyncCallId*/)));

    preferredProfiles.clear();
    address = TQString();
    device = 0;
}

void DeviceSetupWizard::slotDeviceServicesResolvedChanged(const TQString &path, bool resolved)
{
    if (!device)
        return;

    if (path != device->getPath())
        return;

    updateServiceList();
}

void DeviceSetupWizard::slotConnectNextProfile()
{
    if (preferredProfiles.isEmpty())
    {
        slotConnectTimeOut();
    }
    else
    {
        TQString connect = preferredProfiles.first();
        //disable next button while profiles are being handled
        setBackEnabled(connectpage, false);
        setNextEnabled(connectpage, false);
        setBackEnabled(connectingpage, false);
        setNextEnabled(connectingpage, false);
        int asyncCallId = 0;
        TQT_DBusError error;
        if (!device->ConnectProfileAsync(asyncCallId, connect, error))
        {
            if (error.isValid())
                tqDebug(i18n("Failed to call DBus ConnectProfileAsync: %1").arg(error.message()));
        }
        manager->getConnection()->scheduleDispatch();
    }
}

void DeviceSetupWizard::slotAsyncErrorResponseDetected(int asyncCallId, const TQT_DBusError error)
{
    tqDebug(i18n("AsyncErrorResponseDetected: %1 %2 %3").arg(error.type()).arg(error.name()).arg(error.message()));

    if(pairingTimer->isActive())
        pairingTimer->stop();

    if(connectTimer->isActive())
        connectTimer->stop();

    switch (error.type())
    {
    case 5: //org.freedesktop.DBus.Error.NoReply
    case 22: // org.bluez.Error.InProgress, org.bluez.Error.Failed Host is down
    default:
        if (currentPage() == pairingpage)
        {
            slotPairingTimeOut();
        }
        if (currentPage() == connectingpage)
        {
            slotConnectTimeOut();
        }
    }
//    TQMessageBox::critical( 0, "Device Setup Wizard",
//            TQString("AsyncErrorResponseDetected: %1\n%2\n%3")
//            .arg(error.type())
//            .arg(error.name())
//            .arg(error.message()),
//            TQMessageBox::Ok, TQMessageBox::NoButton, TQMessageBox::NoButton);

    KNotifyClient::event(TDEApplication::kApplication()->mainWidget()->winId(),
            "ConnectionError", i18n("AsyncErrorResponseDetected: %1\n%2\n%3")
                        .arg(error.type())
                        .arg(error.name())
                        .arg(error.message()));
}

void DeviceSetupWizard::slotConnectAsyncReply(int asyncCallId)
{
    slotConnectTimeOut();
}

//void DeviceSetupWizard::slotDisconnectAsyncReply(int asyncCallId)
//{
//    slotConnectNextProfile();
//}

void DeviceSetupWizard::slotConnectProfileAsyncReply(int asyncCallId)
{
    kdDebug() << __func__ << endl;
    if (!preferredProfiles.isEmpty())
        preferredProfiles.pop_front();

    if (!preferredProfiles.isEmpty() && connectTimer->isActive())
        // delay connecting next profile to prevent error InProgress
        TQTimer::singleShot(CONNECT_TIMEOUT, this, TQT_SLOT(slotConnectNextProfile()));
    else
        slotConnectTimeOut();
}

//void DeviceSetupWizard::slotDisconnectProfileAsyncReply(int asyncCallId)
//{
//    slotConnectTimeOut();
//}

void DeviceSetupWizard::slotPairAsyncReply(int asyncCallId)
{
    slotPairingTimeOut();
}

void DeviceSetupWizard::slotCancelPairingAsyncReply(int asyncCallId)
{
    slotPairingTimeOut();
}

void DeviceSetupWizard::slotCancelPairing()
{
    int asyncCallId = 0;
    TQT_DBusError error;
    if (!device->CancelPairingAsync(asyncCallId, error))
    {
        if (error.isValid())
            tqDebug(i18n("Failed to call DBus CancelPairingAsync: %1").arg(error.message()));
    }

    if(pairingTimer->isActive())
        pairingTimer->stop();
}

void DeviceSetupWizard::slotCancelConnecting()
{
    int asyncCallId = 0;
    TQT_DBusError error;
    if (device->getConnected(error))
    {
        if (!device->DisconnectAsync(asyncCallId, error))
            tqDebug(i18n("Failed to call DisconnectAsync: %1").arg(error.message()));
    }
    if (error.isValid())
        tqDebug(i18n("Failed in slotCancelConnecting: %1").arg(error.message()));

    if(connectTimer->isActive())
        connectTimer->stop();
}

void DeviceSetupWizard::slotAdvancePairingProgressBar()
{
    if (pairingProgressBar->progress() < pairingProgressBar->totalSteps())
    {
        pairingProgressBar->setProgress(pairingProgressBar->progress() + ASYNC_TIMEOUT/PROGRESS_TIMEOUT);
    }
    else
        pairingProgressBar->setProgress(0,ASYNC_TIMEOUT);
}

void DeviceSetupWizard::slotAdvanceConnectProgressBar()
{
    if (connectingProgressBar->progress() < connectingProgressBar->totalSteps())
    {
        connectingProgressBar->setProgress(connectingProgressBar->progress() + ASYNC_TIMEOUT/PROGRESS_TIMEOUT);
    }
    else
        connectingProgressBar->setProgress(0,ASYNC_TIMEOUT);
}

void DeviceSetupWizard::slotNext()
{
    TQWizard::next();
}

bool DeviceSetupWizard::askClose()
{
    TQString text;
    if (currentPage() == page(0))
    {
        text = i18n("<p>Are you sure you want to quit the Device Settings Wizard?</p>"
                "<p>The Device Settings Wizard helps you to configure the BT device and use it later.</p>"
                "<p>Click <b>Cancel</b> to return and finish your setup.</p>");
    }
    else
    {
        text = i18n("<p>Are you sure you want to quit the Device Settings Wizard?</p>"
                "<p>If yes, click <b>Quit</b> and all changes will be lost."
                "<br>If not, click <b>Cancel</b> to return and finish your setup.</p>");
    }
    int status = KMessageBox::warningContinueCancel(this, text, i18n("All Changes Will Be Lost"), KStdGuiItem::quit());
    if (status == KMessageBox::Continue)
        return true;
    else
        return false;
}

void DeviceSetupWizard::slotCopySrc2Dst()
{
    tQListViewDst->clear();
    // iterate through the first ListView...
    TQListViewItemIterator it(tQListViewSrc, TQListViewItemIterator::Selected);
    while (it.current())
    {
        (void) new TQListViewItem(tQListViewDst, it.current()->text(0));
        ++it;
    }
    if (tQListViewDst->childCount() > 0)
        setNextEnabled(connectpage, true);
}

void DeviceSetupWizard::slotCopyDst2Src()
{
    // iterate through the first ListView...
    TQListViewItemIterator it(tQListViewDst, TQListViewItemIterator::Selected);
    while (it.current())
    {
        TQListViewItem *item = it.current();
        ++it;
        delete item;
    }
    if (tQListViewDst->childCount() == 0)
        setNextEnabled(connectpage, false);
}

#include "devicesetupwizard.moc"
