/****************************************************************************
**
** 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 "nxtconnectionmanager.h"

#include <vm/nxtconnection.h>
#include <nxtconstants.h>
#include <qtc/nxtsdkkitinformation.h>
#include <qtc/nxtsdkmanager.h>
#include <vm/nxtvirtualboxmanager.h>

#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/icontext.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/modemanager.h>
#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <ssh/sshconnection.h>
#include <utils/qtcassert.h>

#include <QAction>
#include <QIcon>

using namespace Core;
using namespace ProjectExplorer;
using namespace QSsh;

namespace Nxt {
namespace Internal {

class NxtConnectionAction : public QObject
{
    Q_OBJECT
public:
    explicit NxtConnectionAction(QObject *parent = 0);
    ~NxtConnectionAction() override;

    void setId(Core::Id id) { m_id = id; }
    void setName(const QString &name) { m_name = name; }
    void setIconOff(const QIcon &icon) { m_iconOff = icon; }
    void setIconOn(const QIcon &icon) { m_iconOn = icon; }
    void setStartTip(const QString &tip) { m_startTip = tip; }
    void setStopTip(const QString &tip) { m_stopTip = tip; }
    void setConnectingTip(const QString &tip) { m_connTip = tip; }
    void setDisconnectingTip(const QString &tip) { m_discoTip = tip; }
    void setStartingTip(const QString &tip) { m_startingTip = tip; }
    void setClosingTip(const QString &tip) { m_closingTip = tip; }
    void setVisible(bool visible) { m_visible = visible; }
    void setEnabled(bool enabled) { m_enabled = enabled; }

    void initialize();

    void setConnection(NxtConnection *connection);

public slots:
    void update();

private:
    QString vmName() const;

private slots:
    void handleTriggered();

private:
    QPointer<NxtConnection> m_connection;
    Core::Id m_id;
    QAction *m_action;
    QIcon m_iconOff;
    QIcon m_iconOn;
    QString m_name;
    QString m_startTip;
    QString m_stopTip;
    QString m_connTip;
    QString m_discoTip;
    QString m_closingTip;
    QString m_startingTip;
    bool m_uiInitalized;
    bool m_visible;
    bool m_enabled;
};

NxtConnectionAction::NxtConnectionAction(QObject *parent)
    : QObject(parent)
    , m_connection(0)
    , m_action(new QAction(this))
    , m_uiInitalized(false)
    , m_visible(false)
    , m_enabled(false)
{
    setEnabled(false);
}

NxtConnectionAction::~NxtConnectionAction()
{
}

void NxtConnectionAction::initialize()
{
    if (m_uiInitalized)
        return;

    m_action->setText(m_name);
    m_action->setIcon(m_iconOff);
    m_action->setToolTip(m_startTip.arg(vmName()));
    connect(m_action, &QAction::triggered, this, &NxtConnectionAction::handleTriggered);

    Command *command = ActionManager::registerAction(m_action, m_id, Context(Core::Constants::C_GLOBAL));
    command->setAttribute(Command::CA_UpdateText);
    command->setAttribute(Command::CA_UpdateIcon);

    m_action->setEnabled(m_enabled);
    m_action->setVisible(m_visible);

    m_uiInitalized = true;
}

void NxtConnectionAction::setConnection(NxtConnection *connection)
{
    if (m_connection == connection)
        return;

    if (m_connection)
        m_connection->disconnect(this);

    m_connection = connection;

    if (m_connection) {
        connect(m_connection.data(), &NxtConnection::stateChanged, this, &NxtConnectionAction::update);
    }

    update();
}

void NxtConnectionAction::update()
{
    if (!m_connection) {
        setEnabled(false);
        return;
    }

    QIcon::State state = QIcon::Off;
    QString toolTip;
    bool enabled = m_enabled;

    switch (m_connection->state()) {
    case NxtConnection::Connected:
        state = QIcon::On;
        toolTip = m_stopTip;
        break;
    case NxtConnection::Disconnected:
        toolTip = m_startTip;
        break;
    case NxtConnection::StartingVm:
        enabled = false;
        state = QIcon::On;
        toolTip = m_startingTip;
        break;
    case NxtConnection::Connecting:
        enabled = false;
        state = QIcon::On;
        toolTip = m_connTip;
        break;
    case NxtConnection::Disconnecting:
        enabled = false;
        toolTip = m_discoTip;
        break;
    case NxtConnection::ClosingVm:
        enabled = false;
        toolTip = m_closingTip;
        break;
    case NxtConnection::Error:
        MessageManager::write(tr("Error connecting to \"%1\" virtual machine: %2")
            .arg(m_connection->virtualMachine())
            .arg(m_connection->errorString()),
            MessageManager::Flash);
        toolTip = m_startTip;
        break;
    default:
        qWarning() << "IDS NXT" << "NxtConnection::update() - unknown state";
        break;
    }

    if (toolTip.contains(QLatin1String("%1")))
        toolTip = toolTip.arg(vmName());

    m_action->setEnabled(enabled);
    m_action->setVisible(m_visible);
    m_action->setToolTip(toolTip);
    m_action->setIcon(state == QIcon::Off ? m_iconOff : m_iconOn);
}

QString NxtConnectionAction::vmName() const
{
    if (m_connection) {
        return m_connection->virtualMachine();
    } else {
        return tr("<UNKNOWN>");
    }
}

void NxtConnectionAction::handleTriggered()
{
    QTC_ASSERT(m_connection, return);

    if (m_connection->state() == NxtConnection::Disconnected) {
        m_connection->connectTo();
    } else if (m_connection->state() == NxtConnection::Connected) {
        m_connection->disconnectFrom();
    } else if (m_connection->state() == NxtConnection::Error) {
        m_connection->connectTo();
    }
}

NxtConnectionManager *NxtConnectionManager::m_instance = 0;
Project *NxtConnectionManager::m_project = 0;

NxtConnectionManager::NxtConnectionManager():
    m_sdkAction(new NxtConnectionAction(this))
{
    QIcon sdkIconOff(QLatin1String(":/images/sdk-run.png"));
    QIcon sdkIconOn(QLatin1String(":/images/sdk-stop.png"));

    m_sdkAction->setName(tr("Start/Stop a IDS NXT Build Engine"));
    m_sdkAction->setId(Constants::NXT_SDK_CONNECTON_ACTION_ID);
    m_sdkAction->setIconOff(sdkIconOff);
    m_sdkAction->setIconOn(sdkIconOn);
    m_sdkAction->setStartTip(tr("Start '%1'"));
    m_sdkAction->setStopTip(tr("Stop '%1'"));
    m_sdkAction->setConnectingTip(tr("Connecting..."));
    m_sdkAction->setDisconnectingTip(tr("Disconnecting..."));
    m_sdkAction->setClosingTip(tr("Closing..."));
    m_sdkAction->setStartingTip(tr("Starting..."));
    m_sdkAction->initialize();

    connect(KitManager::instance(), &KitManager::kitUpdated, this, &NxtConnectionManager::handleKitUpdated);
    connect(SessionManager::instance(), &SessionManager::startupProjectChanged, this, &NxtConnectionManager::handleStartupProjectChanged);
    connect(NxtSdkManager::instance(), &NxtSdkManager::sdksUpdated, this, &NxtConnectionManager::update);
    connect(DeviceManager::instance(), &DeviceManager::updated, this, &NxtConnectionManager::update);

    m_instance = this;
}

NxtConnectionManager::~NxtConnectionManager()
{
    m_instance = 0;
}

NxtConnectionManager* NxtConnectionManager::instance()
{
    Q_ASSERT(m_instance);
    return m_instance;
}

void NxtConnectionManager::handleKitUpdated(Kit *kit)
{
    if (NxtSdkManager::isNxtKit(kit))
        update();
}

void NxtConnectionManager::handleStartupProjectChanged(Project *project)
{
    if (m_project) {
        disconnect(m_project, &Project::addedTarget, this, &NxtConnectionManager::handleTargetAdded);
        disconnect(m_project, &Project::removedTarget, this, &NxtConnectionManager::handleTargetRemoved);
        disconnect(m_project, &Project::activeTargetChanged, this, &NxtConnectionManager::update);
    }

    m_project = project;

    if (m_project) {
        connect(m_project, &Project::addedTarget, this, &NxtConnectionManager::handleTargetAdded);
        connect(m_project, &Project::removedTarget, this, &NxtConnectionManager::handleTargetRemoved);
        connect(m_project, &Project::activeTargetChanged, this, &NxtConnectionManager::update);
    }

    update();
}

void NxtConnectionManager::handleTargetAdded(Target *target)
{
    if (target && NxtSdkManager::isNxtKit(target->kit()))
        update();
}

void NxtConnectionManager::handleTargetRemoved(Target *target)
{
    if (target && NxtSdkManager::isNxtKit(target->kit()))
        update();
}

void NxtConnectionManager::update()
{
    bool sdkRemoteButtonEnabled = false;
    bool sdkRemoteButtonVisible = false;

    const Project* const p = SessionManager::startupProject();
    if (p) {
        const Target* const activeTarget = p->activeTarget();
        const QList<Target *> targets =  p->targets();

        foreach (const Target *t , targets) {
            if (NxtSdkManager::isNxtKit(t->kit())) {
                sdkRemoteButtonVisible = true;
                if (t == activeTarget) {
                    sdkRemoteButtonEnabled = true;
                    NxtSdk* sdk = NxtSdkKitInformation::sdk(t->kit());
                    QTC_ASSERT(sdk, continue);
                    m_sdkAction->setConnection(sdk->connection());
                }
            }

            if (NxtSdkManager::hasNxtDevice(t->kit())) {
                if (t == activeTarget) {
                    const IDevice::ConstPtr device = DeviceKitInformation::device(t->kit());
                }
            }
        }
    }

    m_sdkAction->setEnabled(sdkRemoteButtonEnabled);
    m_sdkAction->setVisible(sdkRemoteButtonVisible);
    m_sdkAction->update();
}

QString NxtConnectionManager::testConnection(const SshConnectionParameters &params, bool *ok)
{
    SshConnectionParameters p = params;
    SshConnection connection(p);
    QEventLoop loop;
    connect(&connection, &SshConnection::connected, &loop, &QEventLoop::quit);
    connect(&connection, &SshConnection::error, &loop, &QEventLoop::quit);
    connect(&connection, &SshConnection::disconnected, &loop, &QEventLoop::quit);
    connection.connectToHost();
    loop.exec();
    if (ok != nullptr)
        *ok = connection.errorState() == SshNoError;
    QString result;
    if (connection.errorState() != SshNoError)
        result = connection.errorString();
    else
        result = tr("Connected");
    return result;
}

} // namespace Internal
} // namespace Nxt

#include "nxtconnectionmanager.moc"
