/***************************************************************************
 *   Copyright (C) 2008  Tom Patzig <tpatzig@suse.de>                      *
 *   Copyright (C) 2008  Alex Fiestas <alex@eyeos.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.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA            *
 ***************************************************************************/

#include "wizarddialog.h"
#include "wizardagent.h"
#include "audio_interface.h"
#include "../kbluetoothutils.h"

#include <QTimer>
#include <QPushButton>
#include <QRadioButton>

#include <QDebug>
#include <klocalizedstring.h>
#include <kstandarddirs.h>
#include <KIcon>

#define HID "00001124-0000-1000-8000-00805F9B34FB"
#define PNP "00001200-0000-1000-8000-00805F9B34FB"
#define HEADSET "00001108-0000-1000-8000-00805F9B34FB"

#define AGENT_PATH "/kbluetooth4_wizard_agent"
#include <kcomponentdata.h>

enum {
    BTAddressRole = Qt::UserRole + 42
};

BTWizard::BTWizard(KApplication* parent) : QWizard(), btmanager(Solid::Control::BluetoothManager::self()), m_parent(parent)
{
    m_iface = 0;
    m_introPage = 0;
    m_discoveryPage = 0;
    m_connPage = 0;
    m_finalPage = 0;
    m_createThread = 0;
    m_pairedThread = 0;
    m_paired = false;
    m_manualPin = false;

    m_iface = new Solid::Control::BluetoothInterface(btmanager.defaultInterface());

    addPage(createIntroPage());
    addPage(createDiscoveryPage());
    addPage(createConnectionPage());
    addPage(createPinPage());
    addPage(createFinalPage());

    setWindowIcon(KIcon("kbluetooth"));
    setWindowTitle(i18n("Bluetooth Device Wizard"));

    connect(button(QWizard::CancelButton), SIGNAL(clicked()), this, SLOT(slotCancel()));
    connect(button(QWizard::NextButton), SIGNAL(clicked()), this, SLOT(slotNext()));
    connect(button(QWizard::BackButton), SIGNAL(clicked()), this, SLOT(slotBack()));
    connect(button(QWizard::FinishButton), SIGNAL(clicked()), this, SLOT(slotFinish()));

    m_normalPixmap = KIcon("kbluetooth").pixmap(128, 128);
    m_flashPixmap = KIcon("kbluetooth-flashing").pixmap(128, 128);

    setPixmap(QWizard::WatermarkPixmap, m_normalPixmap);
    m_logoTimer = new QTimer(this);
    connect(m_logoTimer, SIGNAL(timeout()), this, SLOT(setFlashingLogo()));

    if(!QDBusConnection::systemBus().registerObject("/testInput", parent)) {
        qDebug() << "The dbus object can't be registered";
    }

    m_wizardagent = new WizardAgent(parent);
    connect(m_wizardagent, SIGNAL(pinRequested(const QString&)), this, SLOT(pinRequested(const QString&)));
    connect(this, SIGNAL(manualPinChanged(QString)), m_wizardagent, SLOT(setManualPin(QString)));
}

BTWizard::~BTWizard()
{
    qDebug() << "Deleting Wizard";
    if(m_iface)
        m_iface->stopDiscovery();
}

WizardPage* BTWizard::createIntroPage()
{
    m_introPage = new WizardPage;

    m_introPage->setTitle(i18n("Setup Bluetooth Devices"));

    m_introLabel = new QLabel(i18n("This wizard will help to setup your Bluetooth devices."), m_introPage);
    m_introLabel->setWordWrap(true);

    m_introLayout = new QVBoxLayout(m_introPage);
    m_introLayout->addWidget(m_introLabel);
    m_introPage->setLayout(m_introLayout);

    m_introPage->setComplete(true);
    return m_introPage;
}

WizardPage* BTWizard::createDiscoveryPage()
{
    m_discoveryPage = new WizardPage;
    m_discoveryPage->setComplete(false);

    m_discoveryPage->setTitle(i18n("Select your Device"));

    QLabel *discoveryLabel = new QLabel(i18n("Please select your Bluetooth device."), m_discoveryPage);
    discoveryLabel->setWordWrap(true);

    m_discoveryWidget = new QListWidget(m_discoveryPage);
    connect(m_discoveryWidget, SIGNAL(itemSelectionChanged()), this, SLOT(slotDeviceSelectionChanged()));

    QLabel *discoveryLabel1 = new QLabel(i18n("<b>If your Device is not in the List:</b>"), m_discoveryPage);
    QLabel *discoveryLabel2 = new QLabel(i18n("* Make sure bluetooth is enabled on the device"), m_discoveryPage);
    QLabel *discoveryLabel3 = new QLabel(i18n("* Make sure the device is in discoverable mode"), m_discoveryPage);
    QLabel *discoveryLabel4 = new QLabel(i18n("* Search again"), m_discoveryPage);
    m_discoveryButton = new QPushButton(i18n("&Search"), m_discoveryPage);

    QHBoxLayout *discoveryLayout1 = new QHBoxLayout;
    discoveryLayout1->addWidget(m_discoveryButton);
    discoveryLayout1->addStretch();

    m_discoveryButton->setEnabled(false);
    connect(m_discoveryButton, SIGNAL(clicked()), this, SLOT(slotSearch()));

    QVBoxLayout *discoveryLayout = new QVBoxLayout;
    discoveryLayout->addWidget(discoveryLabel);
    discoveryLayout->addWidget(m_discoveryWidget);
    discoveryLayout->addWidget(discoveryLabel1);
    discoveryLayout->addWidget(discoveryLabel2);
    discoveryLayout->addWidget(discoveryLabel3);
    discoveryLayout->addWidget(discoveryLabel4);

    discoveryLayout->addLayout(discoveryLayout1);

    m_discoveryPage->setLayout(discoveryLayout);

    return m_discoveryPage;
}

WizardPage* BTWizard::createPinPage()
{
    m_pinPage = new WizardPage;
    m_pinPage->setComplete(true);
    m_pinPage->setTitle(i18n("Select the PIN method to pair with the device"));

    QVBoxLayout *layout = new QVBoxLayout(m_pinPage);
    QRadioButton *autoBtn = new QRadioButton("Automatic", m_pinPage);
    autoBtn->setChecked(true);

    QRadioButton *manualBtn = new QRadioButton("Introduce Manually", m_pinPage);
    connect(manualBtn, SIGNAL(toggled(bool)), this, SLOT(manualPinSelected(bool)));

    m_pinLineEdit = new QLineEdit(m_pinPage);
    m_pinLineEdit->setDisabled(true);
    m_pinLineEdit->setMaxLength(4);
    m_pinLineEdit->setMaximumWidth(200);
    m_pinLineEdit->setInputMask("9999");
    m_pinLineEdit->setCursorPosition(0);

    connect(m_pinLineEdit, SIGNAL(textChanged(QString)), this, SLOT(checkManualPin(QString)));

    layout->addWidget(autoBtn);
    layout->addWidget(manualBtn);
    layout->addWidget(m_pinLineEdit);
    m_pinPage->setLayout(layout);

    return m_pinPage;
}

void BTWizard::checkManualPin(const QString& pin)
{
    if(pin.size() < 4) {
        m_pinPage->setComplete(false);
    } else {
        m_pinPage->setComplete(true);
    }
}


void BTWizard::manualPinSelected(bool checked)
{
    qDebug() << "Manual pin selected: " << checked;
    m_pinLineEdit->setEnabled(checked);
    m_manualPin = checked;

    if(checked) {
        if(m_pinLineEdit->text().size() < 4) {
            m_pinPage->setComplete(false);
        }
        m_pinLineEdit->setFocus();
    } else {
        m_pinPage->setComplete(true);
    }
}

WizardPage* BTWizard::createConnectionPage()
{
    m_connPage = new WizardPage;
    m_connPage->setComplete(false);
    m_connPage->setTitle(i18n("Summary"));
    m_connLabel = new QLabel(i18n("Connection will be established to this Bluetooth device"), m_connPage);
    m_connLabel->setWordWrap(true);


    m_connLabel1 = new QLabel(m_connPage);
    m_connLabel2 = new QLabel(m_connPage);
    m_connLabel3 = new QLabel(m_connPage);
    m_connLabel4 = new QLabel(m_connPage);
    m_connLabel5 = new QLabel(m_connPage);
    m_connInfiniteBar = new QProgressBar(m_connPage);
    m_connInfiniteBar->setMinimum(0);
    m_connInfiniteBar->setMaximum(0);
    m_connLabel5->setText("Getting information of the device...");

    QVBoxLayout *connLayout = new QVBoxLayout(m_connPage);
    connLayout->addWidget(m_connLabel);
    connLayout->addWidget(m_connLabel1);
    connLayout->addWidget(m_connLabel2);
    connLayout->addWidget(m_connLabel3);
    connLayout->addWidget(m_connLabel4);
    connLayout->addWidget(m_connLabel5);
    connLayout->addWidget(m_connInfiniteBar);
    m_connPage->setLayout(connLayout);

    return m_connPage;
}

WizardPage* BTWizard::createFinalPage()
{
    m_finalPage = new WizardPage;
    m_finalPage->setComplete(false);
    m_finalPage->setTitle(i18n("Pairing..."));

    m_finalLabel = new QLabel(i18n("Please wait, until the pairing to your Bluetooth Device is done..."), m_connPage);
    m_finalLabel->setWordWrap(true);
    m_hiddenFinalLabel = new QLabel("");
    QFont font;
    font.setWeight(QFont::Bold);
    font.setPixelSize(65);
    m_hiddenFinalLabel->setFont(font);

    m_finalInfiniteBar = new QProgressBar(m_finalPage);
    m_finalInfiniteBar->setMinimum(0);
    m_finalInfiniteBar->setMaximum(0);

    m_finalLayout = new QVBoxLayout(m_finalPage);
    m_finalLayout->addWidget(m_finalLabel);
    m_finalLayout->addWidget(m_hiddenFinalLabel);
    m_finalLayout->addWidget(m_finalInfiniteBar);
    m_finalPage->setLayout(m_finalLayout);

    return m_finalPage;
}

void BTWizard::slotDeviceFound(const QString& addr, const QMap< QString, QVariant > & props)
{
    QString address = addr;
    QString alias = props["Alias"].toString();
    QList<QListWidgetItem *> l;

    l = m_discoveryWidget->findItems(alias, Qt::MatchExactly);

    foreach(QListWidgetItem * i, l) {
        if(i->data(BTAddressRole).toString() == address)
            return;
    }

    addDeviceFound(addr, props);
    m_macMap.insert(address, props);
}

void BTWizard::slotDeviceDisappeared(const QString& addr)
{
    foreach(Solid::Control::BluetoothRemoteDevice * dev, m_devList) {
        if(dev->address() == addr)
            return;
    }

    QString address = addr;

    removeDeviceDisappeared(addr);
    m_macMap.remove(address);
}

void BTWizard::slotDeviceSelectionChanged()
{
    qDebug() << m_discoveryWidget->currentItem();
    m_discoveryPage->setComplete(m_discoveryWidget->currentItem() ? true : false);
}

void BTWizard::slotSearch()
{
    qDebug() << "Text " << m_discoveryButton->text();
    if(m_discoveryButton->text() == i18n("&Search")) {
        qDebug() << "searching..." ;
        m_devList = m_iface->listDevices();
        qDebug() << "Configured Devices: " << m_devList.size();
        m_discoveryPage->setComplete(false);
        m_discoveryButton->setEnabled(true);
        m_discoveryButton->setText(i18n("&Stop Search"));
        m_iface->startDiscovery();
        m_discoveryWidget->clear();
        connect(m_iface, SIGNAL(deviceFound(const QString&, const QMap< QString, QVariant > &)), this, SLOT(slotDeviceFound(const QString&, const QMap< QString, QVariant > &)));
        connect(m_iface, SIGNAL(deviceDisappeared(const QString&)), this, SLOT(slotDeviceDisappeared(const QString&)));
        m_logoTimer->start(400);
    } else {
        qDebug() << "Stop searching" ;
        m_discoveryPage->setComplete(false);
        m_discoveryButton->setEnabled(true);
        m_discoveryButton->setText(i18n("&Search"));
        m_iface->stopDiscovery();
        disconnect(m_iface, SIGNAL(deviceFound(const QString&, const QMap< QString, QVariant > &)), this, SLOT(slotDeviceFound(const QString&, const QMap< QString, QVariant > &)));
        disconnect(m_iface, SIGNAL(deviceDisappeared(const QString&)), this, SLOT(slotDeviceDisappeared(const QString&)));
        m_discoveryPage->setComplete(m_discoveryWidget->currentItem() ? true : false);
        m_logoTimer->stop();
        setNormalLogo();
    }
}

void BTWizard::slotNext()
{
    qDebug() << "Slot Next Current Id:: " << currentId();
    switch(currentId()) {
    case 1 : {
        qDebug() << "page 1 reached!";
        m_logoTimer->stop();
        setNormalLogo();
        slotSearch();
        break;
    }
    case 2 : {
        qDebug() << "page 2 reached!";
        //Finishing the last step
        disconnect(m_iface, SIGNAL(deviceFound(const QString&, const QMap< QString, QVariant > &)), this, SLOT(slotDeviceFound(const QString&, const QMap< QString, QVariant > &)));
        disconnect(m_iface, SIGNAL(deviceDisappeared(const QString&)), this, SLOT(slotDeviceDisappeared(const QString&)));
        m_iface->stopDiscovery();
        m_logoTimer->stop();
        setNormalLogo();

        //Starting the new step
        if(m_discoveryWidget->currentItem()) {
            m_finalDev = m_discoveryWidget->currentItem()->data(BTAddressRole).toString();
            qDebug() << m_finalDev;
            m_connPage->setComplete(true);
            createRemoteDevice(m_finalDev);
        }
        break;
    }
    case 4: {
        qDebug() << "page 4 reached!";
        if(m_manualPin) {
            qDebug() << "Emitting manualPinChanged";
            emit manualPinChanged(m_pinLineEdit->text());
        }
        connectToDevice();
    }
    }
}

void BTWizard::createRemoteDevice(const QString& addr)
{
    qDebug() << "Instancing a new CreateRemoteDevice Thread";
    m_createThread = new CreateRemoteDevice(addr, this);
    connect(m_createThread, SIGNAL(deviceCreated(Solid::Control::BluetoothRemoteDevice*)), this, SLOT(slotDeviceCreated(Solid::Control::BluetoothRemoteDevice*)));
    connect(m_createThread, SIGNAL(finished()), this, SLOT(slotCreateThreadFinished()));
    m_createThread->start();
}

void BTWizard::connectToDevice()
{
    qDebug() << "trying to pair... " << m_finalDev << m_macMap[m_finalDev]["Name"].toString();

    QMap<QString, QVariant> map = m_dev->getProperties();
    bool paired = map["QMap<QString,QVariant>"].toBool();
    qDebug() << paired;
    if(!paired) {
        qDebug() << "Pairing the device....";
        m_pairedThread = new CreatePairedDevice(m_finalDev, this);
        connect(m_pairedThread, SIGNAL(finished()), this, SLOT(slotPairedThreadFinished()));
        m_pairedThread->start();
        qDebug() << "Pairing......";
    } else {
        slotDevicePropertyChanged("Paired", true);
    }
}

void BTWizard::slotDevicePropertyChanged(const QString& prop, const QVariant& value)
{
    qDebug() << "Property Changed " << prop << " " << value;
    if(prop == "Paired" && value.toBool() == true) {
        qDebug() << "Checking how many valid services the device have...";
        qDebug() << m_dev->uuids();

        QStringList validList;
        QStringList uuids = m_dev->uuids();
        validList.append(uuids.filter(HID, Qt::CaseInsensitive));
        validList.append(uuids.filter(HEADSET, Qt::CaseInsensitive));

        if(!validList.isEmpty()) {
            if(validList.size() > 1) {
                qDebug() << "more than one valid service. showing new page";
                qDebug() << validList;

            } else if(validList.size() == 1) {
                QString service = validList.first();
                qDebug() << "Only one valid service: " << service;

                if(service.startsWith(HID, Qt::CaseInsensitive)) {
                    qDebug() << "Connecting to the input service...";
                    Solid::Control::BluetoothInputDevice* inputDev = m_iface->findBluetoothInputDeviceUBI(m_macMap[m_finalDev]["UBI"].toString());
                    inputDev->connect();
                } else if(service.startsWith(HEADSET, Qt::CaseInsensitive)) {
                    qDebug() << "Connecting to the Audio service...";
                    OrgBluezAudioInterface *audio = new OrgBluezAudioInterface("org.bluez", m_macMap[m_finalDev]["UBI"].toString(), QDBusConnection::systemBus());
                    audio->Connect();
                } else {
                    qDebug() << "UNKNOW Service? this shouldn't happend";
                }
            }
        }

        m_paired = true;
        //Force the event, maybe we're already conencted
        slotDevicePropertyChanged("Connected", true);
    } else if(prop == "Connected" && value.toBool() == true && m_paired == true) {
        if(m_createThread) {
            m_createThread->quit();
        }
        if(m_pairedThread) {
            m_pairedThread->quit();
        }

        m_finalPage->setTitle("Finished");
        m_finalLabel->setText(i18n("Your Device is setup properly and should work now."));
        m_hiddenFinalLabel->setHidden(true);
        m_finalInfiniteBar->hide();
        m_finalPage->setComplete(true);
    }
    qDebug() << "Final of Propety changed method";
}

void BTWizard::showConnPageInformation(Solid::Control::BluetoothRemoteDevice* dev)
{
    connect(dev, SIGNAL(propertyChanged(QString, QVariant)), this, SLOT(slotDevicePropertyChanged(QString, QVariant)));

    m_connInfiniteBar->hide();
    m_connLabel5->hide();
    m_connLabel->setText(i18n("Connection will be established to this Bluetooth device"));
    m_connLabel1->setText(i18n("<b>Name: </b>%1", (dev->name())));
    m_connLabel2->setText(i18n("<b>Address: </b>%1", (dev->address())));
    m_connLabel3->setText(i18n("<b>Device class: </b>%1", (dev->icon())));
    m_connLabel4->setPixmap(KIcon(dev->icon()).pixmap(32, 32));
    m_macMap[dev->address()]["UBI"] = dev->ubi();
}


void BTWizard::slotDeviceCreated(Solid::Control::BluetoothRemoteDevice* dev)
{
    qDebug() << "device created " << dev->ubi();
    disconnect(m_createThread, SIGNAL(deviceCreated(Solid::Control::BluetoothRemoteDevice*)), this, SLOT(slotDeviceCreated(Solid::Control::BluetoothRemoteDevice*)));
    m_dev = dev;
    showConnPageInformation(dev);
}

void BTWizard::slotDeviceConnected()
{
    qDebug() << "device connected ";
}

void BTWizard::slotCancel()
{
    if(m_iface->isDiscovering()) {
        m_iface->stopDiscovery();
    }

//Seems that quit is bloking the quit action which is what we want. In case that it doesn't we'll
//need to use QThead::wait
    if(m_createThread) {
        m_createThread->quit();
    }

    if(m_pairedThread) {
        m_pairedThread->quit();
    }

    close();
}


void BTWizard::addDeviceFound(const QString& addr, const QMap< QString, QVariant > & props)
{
    QString icon = props["Icon"].toString();
    QString alias = props["Alias"].toString();
    QString address = addr;

    qDebug() << "addDeviceFound " << alias << " " << address;

    if(m_macMap.contains(addr)) {
        QString old = m_macMap[addr]["Alias"].toString();
        QList<QListWidgetItem *> l;
        l = m_discoveryWidget->findItems(old, Qt::MatchExactly);
        foreach(QListWidgetItem * i, l) {
            if(i->data(BTAddressRole).toString() == addr) {
                i->setText(alias);
                return;
            }
        }
    }

    QListWidgetItem *item = new QListWidgetItem(KIcon(icon), alias, m_discoveryWidget);
    item->setData(BTAddressRole, addr);
    m_discoveryWidget->addItem(item);
}

void BTWizard::removeDeviceDisappeared(const QString& addr)
{
    QString alias = m_macMap[addr]["Alias"].toString();
    QString address = addr;
    QList<QListWidgetItem *> l;

    qDebug() << "removeDeviceDisappeared " << alias << " " << address;

    l = m_discoveryWidget->findItems(alias, Qt::MatchExactly);

    if(l.isEmpty())
        return;

    foreach(QListWidgetItem * i, l) {
        if(i->data(BTAddressRole).toString() == address)
            delete i;
    }
}

void BTWizard::setFlashingLogo()
{
    setPixmap(QWizard::WatermarkPixmap, m_flashPixmap);
    QTimer::singleShot(200, this, SLOT(setNormalLogo()));
}

void BTWizard::setNormalLogo()
{
    setPixmap(QWizard::WatermarkPixmap, m_normalPixmap);
}

void BTWizard::pinRequested(const QString& pin)
{
    m_finalLabel->setText("Please, type this number in your keyboard and press enter");
    m_finalInfiniteBar->hide();
    m_hiddenFinalLabel->setText(pin);
}

void BTWizard::slotCreateThreadFinished()
{
    qDebug() << "Deleting the thread...";
    delete m_createThread;
    m_createThread = 0; //We don't want more crashes, don't we?
}

void BTWizard::slotPairedThreadFinished()
{
    qDebug() << "Deleting paired thread...";
    delete m_pairedThread;
    m_pairedThread = 0;
}

void BTWizard::slotFinish()
{
    close();
}