/*
  probesettings.cpp

  This file is part of GammaRay, the Qt application inspection and
  manipulation tool.

  Copyright (C) 2013-2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
  Author: Volker Krause <volker.krause@kdab.com>

  Licensees holding valid commercial KDAB GammaRay licenses may use this file in
  accordance with GammaRay Commercial License Agreement provided with the Software.

  Contact info@kdab.com if any conditions of this licensing are not clear to you.

  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, see <http://www.gnu.org/licenses/>.
*/

#include <config-gammaray.h>
#include "probesettings.h"

#include "common/message.h"
#include "common/paths.h"

#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QLocalSocket>
#include <QMutex>
#include <QUrl>
#include <QThread>
#include <QWaitCondition>

using namespace GammaRay;

namespace GammaRay {
class ProbeSettingsReceiver;

struct ProbeSettingsData
{
    QHash<QByteArray, QByteArray> settings;
    ProbeSettingsReceiver *receiver;
};

Q_GLOBAL_STATIC(ProbeSettingsData, s_probeSettings)

class ProbeSettingsReceiver : public QObject
{
    Q_OBJECT
public:
    explicit ProbeSettingsReceiver(QObject *parent = Q_NULLPTR);
    ~ProbeSettingsReceiver();
    Q_INVOKABLE void sendServerAddress(const QUrl &address);

    void waitForSettingsReceived();

private slots:
    void readyRead();
    void settingsReceivedFallback();

private:
    Q_INVOKABLE void run();
    void setRootPathFromProbePath(const QString &probePath);
    QLocalSocket *m_socket;
    QWaitCondition m_waitCondition;
    QMutex m_mutex;
};

ProbeSettingsReceiver::ProbeSettingsReceiver(QObject *parent)
    : QObject(parent)
    , m_socket(Q_NULLPTR)
{
}

ProbeSettingsReceiver::~ProbeSettingsReceiver()
{
    delete m_socket;
}

void ProbeSettingsReceiver::run()
{
    m_mutex.lock(); // we only need this for ordering run after waitForSettingsReceived
    m_mutex.unlock();

    m_socket = new QLocalSocket;
    connect(m_socket, SIGNAL(disconnected()), this, SLOT(settingsReceivedFallback()));
    connect(m_socket, SIGNAL(error(QLocalSocket::LocalSocketError)), this,
            SLOT(settingsReceivedFallback()));
    connect(m_socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
    m_socket->connectToServer(QStringLiteral("gammaray-")
                              + QString::number(ProbeSettings::launcherIdentifier()));
    if (!m_socket->waitForConnected(10000)) {
        qWarning() << "Failed to connect to launcher, can't receive probe settings!"
                   << m_socket->errorString();
        settingsReceivedFallback();
    }
}

void ProbeSettingsReceiver::waitForSettingsReceived()
{
    m_mutex.lock();
    QMetaObject::invokeMethod(this, "run", Qt::QueuedConnection);
    m_waitCondition.wait(&m_mutex);
    m_mutex.unlock();
}

void ProbeSettingsReceiver::readyRead()
{
    while (Message::canReadMessage(m_socket)) {
        auto msg = Message::readMessage(m_socket);
        switch (msg.type()) {
        case Protocol::ServerVersion:
        {
            qint32 version;
            msg >> version;
            if (version != Protocol::version()) {
                qWarning()
                        <<
                        "Unable to receive probe settings, mismatching protocol versions (expected:"
                        << Protocol::version() << "got:" << version << ")";
                qWarning() << "Continuing anyway, but this is likely going to fail.";
                settingsReceivedFallback();
                return;
            }
            break;
        }
        case Protocol::ProbeSettings:
        {
            msg >> s_probeSettings()->settings;
            // qDebug() << Q_FUNC_INFO << s_probeSettings()->settings;
            const QString probePath = ProbeSettings::value(QStringLiteral("ProbePath")).toString();
            setRootPathFromProbePath(probePath);

            m_waitCondition.wakeAll();
            return;
        }
        default:
            continue;
        }
    }
}

void ProbeSettingsReceiver::sendServerAddress(const QUrl &address)
{
    if (!m_socket || m_socket->state() != QLocalSocket::ConnectedState)
        return;

    Message msg(Protocol::LauncherAddress, Protocol::ServerAddress);
    msg << address;
    msg.write(m_socket);

    m_socket->waitForBytesWritten();
    m_socket->close();

    deleteLater();
    s_probeSettings()->receiver = Q_NULLPTR;
    thread()->quit();
}

void ProbeSettingsReceiver::settingsReceivedFallback()
{
    // see if we got fallback data via environment variables
    const QString probePath = ProbeSettings::value(QStringLiteral("ProbePath")).toString();
    setRootPathFromProbePath(probePath);

    m_waitCondition.wakeAll();
}

void ProbeSettingsReceiver::setRootPathFromProbePath(const QString &probePath)
{
    if (probePath.isEmpty())
        return;

    QFileInfo fi(probePath);
    if (fi.isFile())
        Paths::setRootPath(fi.absolutePath() + QDir::separator() + GAMMARAY_INVERSE_PROBE_DIR);
    else
        Paths::setRootPath(probePath + QDir::separator() + GAMMARAY_INVERSE_PROBE_DIR);
}
}

QVariant ProbeSettings::value(const QString &key, const QVariant &defaultValue)
{
    QByteArray v = s_probeSettings()->settings.value(key.toUtf8());
    if (v.isEmpty())
        v = qgetenv("GAMMARAY_" + key.toLocal8Bit());
    if (v.isEmpty())
        return defaultValue;

    switch (defaultValue.type()) {
    case QVariant::String:
        return QString::fromUtf8(v);
    case QVariant::Bool:
        return v == "true" || v == "1" || v == "TRUE";
    case QVariant::Int:
        return v.toInt();
    default:
        return v;
    }
}

void ProbeSettings::receiveSettings()
{
    auto t = new QThread;
    QObject::connect(t, SIGNAL(finished()), t, SLOT(deleteLater()));
    t->start();
    auto receiver = new ProbeSettingsReceiver;
    s_probeSettings()->receiver = receiver;
    receiver->moveToThread(t);
    receiver->waitForSettingsReceived();
}

qint64 ProbeSettings::launcherIdentifier()
{
    bool ok;
    const qint64 id = qgetenv("GAMMARAY_LAUNCHER_ID").toLongLong(&ok);
    if (ok && id > 0)
        return id;
    return QCoreApplication::applicationPid();
}

void ProbeSettings::resetLauncherIdentifier()
{
    // if we were launch by GammaRay, and we later try to re-attach, we need to make sure
    // to not end up with an outdated launcher id
    qputenv("GAMMARAY_LAUNCHER_ID", "");
}

void ProbeSettings::sendServerAddress(const QUrl &addr)
{
    Q_ASSERT(s_probeSettings()->receiver);
    QMetaObject::invokeMethod(s_probeSettings()->receiver, "sendServerAddress", Q_ARG(QUrl, addr));
}

#include "probesettings.moc"
