/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtPositioning module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qgeosatelliteinfosource_gypsy_p.h"

#ifdef Q_LOCATION_GYPSY_DEBUG
#include <QDebug>
#endif
#include <QFile>

QT_BEGIN_NAMESPACE

#define UPDATE_TIMEOUT_COLD_START 120000


// Callback function for 'satellites-changed' -signal
static void satellites_changed (GypsySatellite *satellite,
                                GPtrArray *satellites,
                                gpointer userdata)
{
#ifdef Q_LOCATION_GYPSY_DEBUG
    qDebug() << "QGeoSatelliteInfoSourceGypsy Gypsy satellites-changed -signal received.";
#endif
    ((QGeoSatelliteInfoSourceGypsy *)userdata)->satellitesChanged(satellite, satellites);
}

SatelliteGypsyEngine::SatelliteGypsyEngine(QGeoSatelliteInfoSource *parent) :
    m_owner(parent)
{
}
SatelliteGypsyEngine::~SatelliteGypsyEngine()
{
}

// Glib symbols
gulong SatelliteGypsyEngine::eng_g_signal_connect(gpointer instance,
                                                  const gchar *detailed_signal,
                                                  GCallback c_handler,
                                                  gpointer data)
{
    return ::g_signal_connect(instance, detailed_signal, c_handler, data);
}
guint SatelliteGypsyEngine::eng_g_signal_handlers_disconnect_by_func (gpointer instance,
                                                                      gpointer func,
                                                                      gpointer data)
{
    return ::g_signal_handlers_disconnect_by_func(instance, func, data);
}

void SatelliteGypsyEngine::eng_g_free(gpointer mem)
{
    return ::g_free(mem);
}
// Gypsy symbols
GypsyControl *SatelliteGypsyEngine::eng_gypsy_control_get_default (void)
{
    return ::gypsy_control_get_default();
}
char *SatelliteGypsyEngine::eng_gypsy_control_create (GypsyControl *control, const char *device_name, GError **error)
{
    return ::gypsy_control_create(control, device_name, error);
}
GypsyDevice *SatelliteGypsyEngine::eng_gypsy_device_new (const char *object_path)
{
    return ::gypsy_device_new(object_path);
}
GypsySatellite *SatelliteGypsyEngine::eng_gypsy_satellite_new (const char *object_path)
{
    return ::gypsy_satellite_new (object_path);
}
gboolean SatelliteGypsyEngine::eng_gypsy_device_start (GypsyDevice *device, GError **error)
{
    return ::gypsy_device_start(device, error);
}
gboolean SatelliteGypsyEngine::eng_gypsy_device_stop (GypsyDevice *device, GError **error)
{
    // Unfortunately this cannot be done; calling this will stop the GPS device
    // (basically makes gypsy-daemon unusable for anyone), regardless of applications
    // using it (see bug http://bugs.meego.com/show_bug.cgi?id=11707).
    Q_UNUSED(device);
    Q_UNUSED(error);
    return true;
    //return ::gypsy_device_stop (device, error);
}
GypsyDeviceFixStatus SatelliteGypsyEngine::eng_gypsy_device_get_fix_status (GypsyDevice *device, GError **error)
{
    return ::gypsy_device_get_fix_status (device, error);
}
GPtrArray *SatelliteGypsyEngine::eng_gypsy_satellite_get_satellites (GypsySatellite *satellite, GError **error)
{
    return ::gypsy_satellite_get_satellites (satellite, error);
}
void SatelliteGypsyEngine::eng_gypsy_satellite_free_satellite_array (GPtrArray *satellites)
{
    return ::gypsy_satellite_free_satellite_array(satellites);
}
// GConf symbols (mockability due to X11 requirement)
GConfClient *SatelliteGypsyEngine::eng_gconf_client_get_default(void)
{
    return ::gconf_client_get_default();
}
gchar *SatelliteGypsyEngine::eng_gconf_client_get_string(GConfClient *client, const gchar *key, GError** err)
{
    return ::gconf_client_get_string(client, key, err);
}

QGeoSatelliteInfoSourceGypsy::QGeoSatelliteInfoSourceGypsy(QObject *parent) : QGeoSatelliteInfoSource(parent),
    m_engine(0), m_satellite(0), m_device(0), m_updatesOngoing(false), m_requestOngoing(false), m_requestTimer(this)
{
    m_requestTimer.setSingleShot(true);
    QObject::connect(&m_requestTimer, SIGNAL(timeout()), this, SLOT(requestUpdateTimeout()));
}

void QGeoSatelliteInfoSourceGypsy::createEngine()
{
    delete m_engine;
    m_engine = new SatelliteGypsyEngine(this);
}

QGeoSatelliteInfoSourceGypsy::~QGeoSatelliteInfoSourceGypsy()
{
    GError *error = NULL;
    if (m_device) {
        m_engine->eng_gypsy_device_stop (m_device, &error);
        g_object_unref(m_device);
    }
    if (m_satellite)
        g_object_unref(m_satellite);
    if (error)
        g_error_free(error);
    delete m_engine;
}

void QGeoSatelliteInfoSourceGypsy::satellitesChanged(GypsySatellite *satellite,
                                                     GPtrArray *satellites)
{
    if (!satellite || !satellites)
        return;
    // We have satellite data and assume it is valid.
    // If a single updateRequest was active, send signals right away.
    // If a periodic timer was running (meaning that the client wishes
    // to have updates at defined intervals), store the data for later sending.
    QList<QGeoSatelliteInfo> lastSatellitesInView;
    QList<QGeoSatelliteInfo> lastSatellitesInUse;

    unsigned int i;
    for (i = 0; i < satellites->len; i++) {
        GypsySatelliteDetails *details = (GypsySatelliteDetails *)satellites->pdata[i];
        QGeoSatelliteInfo info;
        info.setAttribute(QGeoSatelliteInfo::Elevation, details->elevation);
        info.setAttribute(QGeoSatelliteInfo::Azimuth, details->azimuth);
        info.setSignalStrength(details->snr);
        if (details->in_use)
            lastSatellitesInUse.append(info);
        lastSatellitesInView.append(info);
    }
    bool sendUpdates(false);
    // If a single updateRequest() has been issued:
    if (m_requestOngoing) {
        sendUpdates = true;
        m_requestTimer.stop();
        m_requestOngoing = false;
        // If there is no regular updates ongoing, disconnect now.
        if (!m_updatesOngoing) {
            m_engine->eng_g_signal_handlers_disconnect_by_func(G_OBJECT(m_satellite), (void *)satellites_changed, this);
        }
    }
    // If regular updates are to be delivered as they come:
    if (m_updatesOngoing)
        sendUpdates = true;

    if (sendUpdates) {
        emit satellitesInUseUpdated(lastSatellitesInUse);
        emit satellitesInViewUpdated(lastSatellitesInView);
    }
}

int QGeoSatelliteInfoSourceGypsy::init()
{
    GError *error = NULL;
    char *path;
    GConfClient *client;
    gchar *device_name;

    g_type_init ();
    createEngine();

    client = m_engine->eng_gconf_client_get_default();
    if (!client) {
        qWarning ("QGeoSatelliteInfoSourceGypsy client creation failed.");
        return -1;
    }
    device_name = m_engine->eng_gconf_client_get_string(client, "/apps/geoclue/master/org.freedesktop.Geoclue.GPSDevice", NULL);
    g_object_unref(client);
    QString deviceName(QString::fromLatin1(device_name));
    if (deviceName.isEmpty() ||
            (deviceName.trimmed().at(0) == '/' && !QFile::exists(deviceName.trimmed()))) {
        qWarning ("QGeoSatelliteInfoSourceGypsy Empty/nonexistent GPS device name detected.");
        qWarning ("Use gconftool-2 to set it, e.g. on terminal: ");
        qWarning ("gconftool-2 -t string -s /apps/geoclue/master/org.freedesktop.Geoclue.GPSDevice /dev/ttyUSB0");
        m_engine->eng_g_free(device_name);
        return -1;
    }
    GypsyControl *control = NULL;
    control = m_engine->eng_gypsy_control_get_default();
    if (!control) {
        qWarning("QGeoSatelliteInfoSourceGypsy unable to create Gypsy control.");
        m_engine->eng_g_free(device_name);
        return -1;
    }
    // (path is the DBus path)
    path = m_engine->eng_gypsy_control_create (control, device_name, &error);
    m_engine->eng_g_free(device_name);
    g_object_unref(control);
    if (!path) {
        qWarning ("QGeoSatelliteInfoSourceGypsy error creating client.");
        if (error) {
            qWarning ("error message: %s", error->message);
            g_error_free (error);
        }
        return -1;
    }
    m_device = m_engine->eng_gypsy_device_new (path);
    m_satellite = m_engine->eng_gypsy_satellite_new (path);
    m_engine->eng_g_free(path);
    if (!m_device || !m_satellite) {
        qWarning ("QGeoSatelliteInfoSourceGypsy error creating satellite device.");
        qWarning ("Is GPS device set correctly? If not, use gconftool-2 to set it, e.g.: ");
        qWarning ("gconftool-2 -t string -s /apps/geoclue/master/org.freedesktop.Geoclue.GPSDevice /dev/ttyUSB0");
        if (m_device)
            g_object_unref(m_device);
        if (m_satellite)
            g_object_unref(m_satellite);
        return -1;
    }
    m_engine->eng_gypsy_device_start (m_device, &error);
    if (error) {
        qWarning ("QGeoSatelliteInfoSourceGypsy error starting device: %s ",
                   error->message);
        g_error_free(error);
        g_object_unref(m_device);
        g_object_unref(m_satellite);
        return -1;
    }
    return 0;
}

int QGeoSatelliteInfoSourceGypsy::minimumUpdateInterval() const
{
    return 1;
}

QGeoSatelliteInfoSource::Error QGeoSatelliteInfoSourceGypsy::error() const
{
    return NoError;
}

void QGeoSatelliteInfoSourceGypsy::startUpdates()
{
    if (m_updatesOngoing)
        return;
    // If there is a request timer ongoing, we've connected to the signal already
    if (!m_requestTimer.isActive()) {
        m_engine->eng_g_signal_connect (m_satellite, "satellites-changed",
                          G_CALLBACK (satellites_changed), this);
    }
    m_updatesOngoing = true;
}

void QGeoSatelliteInfoSourceGypsy::stopUpdates()
{
    if (!m_updatesOngoing)
        return;
    m_updatesOngoing = false;
    // Disconnect only if there is no single update request ongoing. Once single update request
    // is completed and it notices that there is no active update ongoing, it will disconnect
    // the signal.
    if (!m_requestTimer.isActive())
        m_engine->eng_g_signal_handlers_disconnect_by_func(G_OBJECT(m_satellite), (void *)satellites_changed, this);
}

void QGeoSatelliteInfoSourceGypsy::requestUpdate(int timeout)
{
    if (m_requestOngoing)
        return;
    if (timeout < 0) {
        emit requestTimeout();
        return;
    }
    m_requestOngoing = true;
    GError *error = 0;
    // If GPS has a fix a already, request current data.
    GypsyDeviceFixStatus fixStatus = m_engine->eng_gypsy_device_get_fix_status(m_device, &error);
    if (!error && (fixStatus != GYPSY_DEVICE_FIX_STATUS_INVALID &&
            fixStatus != GYPSY_DEVICE_FIX_STATUS_NONE)) {
#ifdef Q_LOCATION_GYPSY_DEBUG
        qDebug() << "QGeoSatelliteInfoSourceGypsy fix available, requesting current satellite data";
#endif
        GPtrArray *satelliteData = m_engine->eng_gypsy_satellite_get_satellites(m_satellite, &error);
        if (!error) {
            // The fix was available and we have satellite data to deliver right away.
            satellitesChanged(m_satellite, satelliteData);
            m_engine->eng_gypsy_satellite_free_satellite_array(satelliteData);
            return;
        }
    }
    // No fix is available. If updates are not ongoing already, start them.
    m_requestTimer.setInterval(timeout == 0? UPDATE_TIMEOUT_COLD_START: timeout);
    if (!m_updatesOngoing) {
        m_engine->eng_g_signal_connect (m_satellite, "satellites-changed",
                          G_CALLBACK (satellites_changed), this);
    }
    m_requestTimer.start();
    if (error) {
#ifdef Q_LOCATION_GYPSY_DEBUG
        qDebug() << "QGeoSatelliteInfoSourceGypsy error asking fix status or satellite data: " << error->message;
#endif
        g_error_free(error);
    }
}

void QGeoSatelliteInfoSourceGypsy::requestUpdateTimeout()
{
#ifdef Q_LOCATION_GYPSY_DEBUG
    qDebug("QGeoSatelliteInfoSourceGypsy request update timeout occurred.");
#endif
    // If we end up here, there has not been valid satellite update.
    // Emit timeout and disconnect from signal if regular updates are not
    // ongoing (as we were listening just for one single requestUpdate).
    if (!m_updatesOngoing) {
        m_engine->eng_g_signal_handlers_disconnect_by_func(G_OBJECT(m_satellite), (void *)satellites_changed, this);
    }
    m_requestOngoing = false;
    emit requestTimeout();
}

QT_END_NAMESPACE
