/****************************************************************************
**
** Copyright (C) 2012 - 2014 Jolla Ltd.
** Contact: http://jolla.com/
**
** This file is part of Qt Creator.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Digia.
**
****************************************************************************/

#include "nxthardwaredevicewizardpages.h"
#include "ui_nxthardwaredevicewizardselectionpage.h"
#include "ui_nxthardwaredevicewizardsetuppage.h"

#include <vm/nxtconnectionmanager.h>
#include <nxtconstants.h>
#include <device/hardware/nxthardwaredevicewizard.h>
#include <device/hardware/nxthardwaredevicerest.h>
#include <qtc/nxtsdkmanager.h>
#include <vm/nxtvirtualboxmanager.h>

#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/processparameters.h>
#include <ssh/sshremoteprocessrunner.h>
#include <utils/portlist.h>
#include <utils/qtcassert.h>

#include <QDir>
#include <QFuture>
#include <QtConcurrent>

using namespace ProjectExplorer;
using namespace QSsh;
using namespace Utils;

namespace Nxt {
namespace Internal {

NxtHardwareDeviceWizardSelectionPage::NxtHardwareDeviceWizardSelectionPage(QWidget *parent)
    : QWizardPage(parent)
    , m_ui(new Ui::NxtHardwareDeviceWizardSelectionPage)
    , m_architecture(ProjectExplorer::Abi::ArmArchitecture)
    , m_deviceName("")
    , m_deviceType("IDS NXT")
    , m_isIdle(true)
    , m_connectionTestOk(false)
{
    m_ui->setupUi(this);
    setTitle(tr("Select Device"));

    m_ui->hostNameLineEdit->setText(QLatin1String(Constants::NXT_DEVICE_DEFAULT_HOST));
    m_ui->usernameLineEdit->setText(QLatin1String(Constants::NXT_DEVICE_DEFAULTUSER));
    m_ui->passwordLineEdit->setText(QLatin1String(Constants::NXT_DEVICE_DEFAULTPASS));

#if 0 /* ssh */
    m_ui->sshPortSpinBox->setMinimum(1);
    m_ui->sshPortSpinBox->setMaximum(65535);
    m_ui->sshPortSpinBox->setValue(22);

    m_ui->timeoutSpinBox->setMinimum(1);
    m_ui->timeoutSpinBox->setMaximum(65535);
    m_ui->timeoutSpinBox->setValue(10);
#endif /* ssh */

    m_ui->connectionTestLabel->setText(tr("Not connected."));

    connect(m_ui->hostNameLineEdit, &QLineEdit::textChanged, this, &NxtHardwareDeviceWizardSelectionPage::completeChanged);
    connect(m_ui->usernameLineEdit, &QLineEdit::textChanged, this, &NxtHardwareDeviceWizardSelectionPage::completeChanged);
    connect(m_ui->passwordLineEdit, &QLineEdit::textChanged, this, &NxtHardwareDeviceWizardSelectionPage::completeChanged);

    m_ui->testButton->setEnabled(false);
    connect(m_ui->passwordLineEdit, &QLineEdit::textChanged, this, [this](const QString &text) { m_ui->testButton->setEnabled(!text.isEmpty()); });
    connect(m_ui->testButton, &QPushButton::clicked, this, &NxtHardwareDeviceWizardSelectionPage::handleTestConnectionClicked);
    connect(m_ui->passwordLineEdit, &QLineEdit::returnPressed, this, &NxtHardwareDeviceWizardSelectionPage::handleTestConnectionClicked);

    if (m_ui->passwordLineEdit->text().length() > 0)
    {
        m_ui->testButton->setEnabled(true);
    }
}

bool NxtHardwareDeviceWizardSelectionPage::isComplete() const
{
    return
            !hostName().isEmpty()
            && !userName().isEmpty()
            // && !password().isEmpty()// allow empty passwords!
            && m_isIdle
            //&& m_connectionTestOk// do not force connection test
            ;
}

QString NxtHardwareDeviceWizardSelectionPage::hostName() const
{
    return m_ui->hostNameLineEdit->text().trimmed();
}

QString NxtHardwareDeviceWizardSelectionPage::userName() const
{
    return m_ui->usernameLineEdit->text().trimmed();
}

QString NxtHardwareDeviceWizardSelectionPage::password() const
{
    return m_ui->passwordLineEdit->text();
}

#if 0 /* ssh */
int NxtHardwareDeviceWizardSelectionPage::sshPort() const
{
    return m_ui->sshPortSpinBox->value();
}

int NxtHardwareDeviceWizardSelectionPage::timeout() const
{
    return m_ui->timeoutSpinBox->value();
}
#endif /* ssh */

Abi::Architecture NxtHardwareDeviceWizardSelectionPage::architecture() const
{
    return m_architecture;
}

QString NxtHardwareDeviceWizardSelectionPage::deviceName() const
{
    return m_deviceName;
}

#if 0 /* ssh */
Abi::Architecture NxtHardwareDeviceWizardSelectionPage::architecture() const
{
    return m_architecture;
}

QString NxtHardwareDeviceWizardSelectionPage::deviceName() const
{
    return m_deviceName;
}
#endif /* ssh */

QString NxtHardwareDeviceWizardSelectionPage::deviceType() const
{
    return m_deviceType;
}

void NxtHardwareDeviceWizardSelectionPage::replyFinished(QNetworkReply *response)
{
    qDebug() << "replyFinished";
    qDebug() << response->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
}

void NxtHardwareDeviceWizardSelectionPage::handleTestConnectionClicked()
{
    // Without this focus would be moved to the "Cancel" button
    wizard()->setFocus();

    m_ui->testButton->setEnabled(false);
    m_isIdle = false;
    completeChanged();

#if 0 /* ssh */
    SshConnectionParameters sshParams;
    sshParams.setHost(hostName());
    sshParams.setUserName(userName());
    sshParams.setPort(sshPort());
    sshParams.timeout = timeout();
    sshParams.authenticationType = SshConnectionParameters::AuthenticationTypePassword;
    sshParams.setPassword(password());

    m_ui->connectionTestLabel->setText(tr("Connecting to machine %1 ...").arg(hostName()));
    m_ui->connectionTestLabel->setText(NxtConnectionManager::testConnection(sshParams, &m_connectionTestOk));
    if (!m_connectionTestOk)
        goto end;

    m_ui->connectionTestLabel->setText(tr("Detecting device properties..."));
    m_architecture = detectArchitecture(sshParams, &m_connectionTestOk);
    m_deviceName = detectDeviceName(sshParams, &m_connectionTestOk);
    if (!m_connectionTestOk) {
        m_ui->connectionTestLabel->setText(tr("Could not autodetect device properties"));
        goto end;
#endif /* ssh */

    NxtHardwareDeviceREST nxt(hostName(), userName(), password());
    nxt.setTimeout(1);

    auto palette = m_ui->hostNameLabel->palette();
    QColor colorDefault = palette.color(QPalette::WindowText);
    QColor colorSuccess(44, 155, 0);
    QColor colorFailure(210, 0, 0);
    m_ui->connectionTestLabel->setPalette(palette);

    m_ui->connectionTestLabel->setText(tr("Connecting to device %1...").arg(hostName()));
    if (nxt.available())
    {
        m_ui->connectionTestLabel->setText(tr("Authenticating..."));
        int status = nxt.authenticate();
        if (status >= 200 && status < 400)
        {
            m_ui->connectionTestLabel->setText(tr("Detecting device properties..."));
            auto deviceInfo = nxt.deviceInfo();
            if (deviceInfo.contains("DeviceName")) {
                m_deviceName = deviceInfo["DeviceName"].toString();
            }
            if (deviceInfo.contains("DeviceType")) {
                m_deviceType = deviceInfo["DeviceType"].toString();
            }
            m_architecture = ProjectExplorer::Abi::ArmArchitecture;

            m_ui->connectionTestLabel->setText(tr("Connected to device."));
            m_connectionTestOk = true;
        }
        else
        {
            m_ui->connectionTestLabel->setText(tr("Authentication failed! (Code %1)").arg(status));
            m_connectionTestOk = false;
        }
    }
    else
    {
        m_ui->connectionTestLabel->setText(tr("Device not reachable. IP address correct?"));
        m_connectionTestOk = false;
    }

    palette.setColor(QPalette::WindowText, m_connectionTestOk ? colorSuccess : colorFailure);
    m_ui->connectionTestLabel->setPalette(palette);

#if 0 /* ssh */
end:
#endif /* ssh */
    m_ui->testButton->setEnabled(true);
    if (!m_connectionTestOk) {
        m_ui->passwordLineEdit->setFocus(Qt::TabFocusReason);
    }
    m_isIdle = true;
    completeChanged();
}

#if 0 /* ssh */
Abi::Architecture NxtHardwareDeviceWizardSelectionPage::detectArchitecture(
        const SshConnectionParameters &sshParams, bool *ok)
{
    if (!*ok) {
        return Abi::UnknownArchitecture;
    }

    SshRemoteProcessRunner runner;
    QEventLoop loop;
    connect(&runner, &SshRemoteProcessRunner::connectionError, &loop, &QEventLoop::quit);
    connect(&runner, &SshRemoteProcessRunner::processClosed, &loop, &QEventLoop::quit);
    runner.run("uname --machine", sshParams);
    loop.exec();

    if (runner.lastConnectionError() != SshNoError
            || runner.processExitStatus() != SshRemoteProcess::NormalExit
            || runner.processExitCode() != 0) {
        *ok = false;
        qWarning() << "IDS NXT" << "Failed to execute uname on target";
        return Abi::UnknownArchitecture;
    }

    const QString output = QString::fromLatin1(runner.readAllStandardOutput()).trimmed();
    if (output.isEmpty()) {
        *ok = false;
        qWarning() << "IDS NXT" << "Empty output from uname executed on target";
        return Abi::UnknownArchitecture;
    }

    // Does not seem ideal, but works well
    Abi::Architecture architecture = Abi::abiFromTargetTriplet(output).architecture();
    if (architecture == Abi::UnknownArchitecture) {
        *ok = false;
        qWarning() << "IDS NXT" << "Could not parse architecture from uname executed on target (output:" << output << ")";
        return Abi::UnknownArchitecture;
    }

    *ok = true;
    return architecture;
}

QString NxtHardwareDeviceWizardSelectionPage::detectDeviceName(const SshConnectionParameters &sshParams, bool *ok)
{
    if (!*ok) {
        return QString();
    }

#if 0
    SshRemoteProcessRunner runner;
    QEventLoop loop;
    connect(&runner, &SshRemoteProcessRunner::connectionError, &loop, &QEventLoop::quit);
    connect(&runner, &SshRemoteProcessRunner::processClosed, &loop, &QEventLoop::quit);
    runner.run("dbus-send --print-reply=literal --system --type=method_call --dest=org.nemo.ssu "
               "/org/nemo/ssu org.nemo.ssu.displayName int32:1", sshParams);
    loop.exec();

    if (runner.lastConnectionError() != SshNoError
            || runner.processExitStatus() != SshRemoteProcess::NormalExit
            || runner.processExitCode() != 0) {
        *ok = false;
        qWarning() << "IDS NXT" << "Failed to query device model name on target";
        return QString();
    }

    const QString deviceName = QString::fromLatin1(runner.readAllStandardOutput()).trimmed();
    if (deviceName.isEmpty()) {
        *ok = false;
        qWarning() << "IDS NXT" << "Empty output from querying device model name on target";
        return QString();
    }
#else
    const QString deviceName = "NXT vegas";
    Q_UNUSED(sshParams);
#endif

    *ok = true;
    return deviceName;
}
#endif /* ssh */

////////////////////////////////////////////////////////////////////////////////////////////////

NxtHardwareDeviceWizardSetupPage::NxtHardwareDeviceWizardSetupPage(QWidget *parent)
    : QWizardPage(parent)
    , m_ui(new Ui::NxtHardwareDeviceWizardSetupPage)
{
    m_ui->setupUi(this);
    setTitle(tr("Configure Connection"));

#if 0 /* ssh */
    m_ui->freePortsLineEdit->setText(QLatin1String("10000-10100"));

    m_ui->sshCheckBox->setChecked(true);
#endif /* ssh */

    static QRegExp regExp(tr("Build Engine"));
    QList<NxtSdk*> sdks = NxtSdkManager::sdks();
    foreach (const NxtSdk *s, sdks) {
        m_ui->nxtSdkComboBox->addItem(s->virtualMachineName());
        if (regExp.indexIn(s->virtualMachineName()) != -1) {
            // preselect sdk
            m_ui->nxtSdkComboBox->setCurrentIndex(m_ui->nxtSdkComboBox->count()-1);
        }
    }

    connect(m_ui->configLineEdit, &QLineEdit::textChanged, this, &NxtHardwareDeviceWizardSetupPage::completeChanged);
    connect(m_ui->nxtSdkComboBox, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentIndexChanged), this, &NxtHardwareDeviceWizardSetupPage::handleSdkVmChanged);
}

void NxtHardwareDeviceWizardSetupPage::initializePage()
{
    const NxtHardwareDeviceWizard* wizard = qobject_cast<NxtHardwareDeviceWizard*>(this->wizard());
    QTC_ASSERT(wizard,return);

#if 0 /* ssh */
    // wizard->architecture() will fail here
    const QString arch = Abi::toString(wizard->architecture()).toUpper();
#else
    const QString arch = wizard->deviceType();
#endif /* ssh */

    QString preferredName = "";
    QString name = wizard->deviceName();
    if (name.isEmpty()) {
        NxtHardwareDeviceREST nxt(wizard->hostName(), wizard->userName(), wizard->password());
        nxt.setTimeout(1);
        if (nxt.available())
        {
            int status = nxt.authenticate();
            if (status >= 200 && status < 400)
            {
                auto deviceInfo = nxt.deviceInfo();
                if (deviceInfo.contains("DeviceName")) {
                    name = deviceInfo["DeviceName"].toString();
                }
            }
        }
    }
    if (!name.isEmpty()) {
        preferredName = QStringLiteral("%1 (%2)").arg(name).arg(arch);
    }
    int i = 1;
    QString tryName = preferredName;
    while (DeviceManager::instance()->hasDevice(tryName)) {
       tryName = preferredName + QString::number(++i);
    }
    m_ui->configLineEdit->setText(tryName);

    m_ui->hostLabelEdit->setText(wizard->hostName());
    m_ui->usernameLabelEdit->setText(wizard->userName());
#if 0 /* ssh */
    m_ui->sshPortLabelEdit->setText(QString::number(wizard->sshPort()));
#endif /* ssh */
#if 0 /* hidden password */
    m_ui->passwordLabelEdit->setText(QString("*").repeated(wizard->password().length()));
#else
    m_ui->passwordLabelEdit->setText(wizard->password());
#endif /* hidden password */
    handleSdkVmChanged(m_ui->nxtSdkComboBox->currentText());
}

QString NxtHardwareDeviceWizardSetupPage::configName() const
{
    return m_ui->configLineEdit->text().trimmed();
}

#if 0 /* ssh */
QString NxtHardwareDeviceWizardSetupPage::freePorts() const
{
    return m_ui->freePortsLineEdit->text().trimmed();
}
#endif /* ssh */

void NxtHardwareDeviceWizardSetupPage::handleSdkVmChanged(const QString &vmName)
{
    NxtSdk* sdk = NxtSdkManager::sdk(vmName);
    QTC_ASSERT(sdk, return);
    const NxtHardwareDeviceWizard* wizard = qobject_cast<NxtHardwareDeviceWizard*>(this->wizard());
    QTC_ASSERT(wizard, return);
#if 0 /* ssh */
    QString index(QLatin1String("/ssh/private_keys/%1/"));
    //TODO: fix me
    QString sshKeyPath(QDir::toNativeSeparators(sdk->sharedConfigPath() + index.arg(wizard->configurationName()).replace(QLatin1Char(' '),QLatin1Char('_')) + wizard->userName()));
    m_ui->privateSshKeyLabelEdit->setText(sshKeyPath);
    m_ui->publicSshKeyLabelEdit->setText(sshKeyPath + QLatin1String(".pub"));
#endif /* ssh */
}

#if 0 /* ssh */
QString NxtHardwareDeviceWizardSetupPage::privateKeyFilePath() const
{
    return QDir::fromNativeSeparators(m_ui->privateSshKeyLabelEdit->text());
}

QString NxtHardwareDeviceWizardSetupPage::publicKeyFilePath() const
{
    return QDir::fromNativeSeparators(m_ui->publicSshKeyLabelEdit->text());
}

bool NxtHardwareDeviceWizardSetupPage::isNewSshKeysRquired() const
{
    return m_ui->sshCheckBox->isChecked();
}
#endif /* ssh */

QString NxtHardwareDeviceWizardSetupPage::sharedSshPath() const
{
    NxtSdk* sdk = NxtSdkManager::sdk(m_ui->nxtSdkComboBox->currentText());
    QTC_ASSERT(sdk,return QString());
    return sdk->sharedConfigPath();
}

bool NxtHardwareDeviceWizardSetupPage::isComplete() const
{
    return !configName().isEmpty()
#if 0 /* ssh */
        && !freePorts().isEmpty()
#endif /* ssh */
        && !DeviceManager::instance()->hasDevice(configName());
}

} // namespace Internal
} // namespace Nxt
