/* vim: set et ts=8 sw=8: */
/*
 * Copyright 2014 Red Hat, Inc.
 *
 * Geoclue 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.
 *
 * Geoclue 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 Geoclue; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
 */

#include <glib.h>
#include "gclue-compass.h"
#include "gclue-location.h"
#include "compass-interface.h"

/**
 * SECTION:gclue-compass
 * @short_description: IIO compass support
 * @include: gclue-glib/gclue-compass.h
 **/

struct _GClueCompassPrivate
{
        Compass *proxy;

        GCancellable *cancellable;
};

G_DEFINE_TYPE_WITH_CODE (GClueCompass,
                         gclue_compass,
                         G_TYPE_OBJECT,
                         G_ADD_PRIVATE (GClueCompass))

enum
{
        PROP_0,
        PROP_HEADING,
        LAST_PROP
};

static GParamSpec *gParamSpecs[LAST_PROP];

static void
gclue_compass_get_property (GObject    *object,
                            guint       prop_id,
                            GValue     *value,
                            GParamSpec *pspec)
{
        GClueCompass *compass = GCLUE_COMPASS (object);

        switch (prop_id) {
        case PROP_HEADING:
                g_value_set_double (value,
                                    gclue_compass_get_heading (compass));
                break;

        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        }
}

static void
gclue_compass_finalize (GObject *object)
{
        GClueCompassPrivate *priv = GCLUE_COMPASS (object)->priv;

        g_cancellable_cancel (priv->cancellable);
        g_clear_object (&priv->cancellable);

        if (priv->proxy != NULL) {
                g_autoptr(GError) error = NULL;

                if (!compass_call_release_compass_sync (priv->proxy,
                                                        NULL,
                                                        &error)) {
                        g_warning ("Failed to release compass: %s",
                                   error->message);
                }
                g_debug ("IIO compass released");
                g_object_unref (priv->proxy);
        }

        G_OBJECT_CLASS (gclue_compass_parent_class)->finalize (object);
}

static void
gclue_compass_class_init (GClueCompassClass *klass)
{
        GObjectClass *object_class;

        object_class = G_OBJECT_CLASS (klass);
        object_class->get_property = gclue_compass_get_property;
        object_class->finalize = gclue_compass_finalize;

        /**
         * GClueCompass:heading
         *
         * The positive angle between the direction of movement and the North
         * direction, in clockwise direction. The angle is measured in degrees.
         */
        gParamSpecs[PROP_HEADING] =
                g_param_spec_double ("heading",
                                     "Heading",
                                     "The positive angle between the direction"
                                     " of movement and the North direction, in"
                                     " clockwise direction. The angle is "
                                     "measured in degrees.",
                                     GCLUE_LOCATION_HEADING_UNKNOWN,
                                     G_MAXDOUBLE,
                                     GCLUE_LOCATION_HEADING_UNKNOWN,
                                     G_PARAM_READABLE |
                                     G_PARAM_STATIC_STRINGS);
        g_object_class_install_property (object_class, PROP_HEADING, gParamSpecs[PROP_HEADING]);
}

static void
on_compass_heading_changed (GObject    *gobject,
                            GParamSpec *pspec,
                            gpointer    user_data)
{
        g_object_notify_by_pspec (G_OBJECT (user_data),
                                  gParamSpecs[PROP_HEADING]);
}

static void
on_compass_claimed (GObject      *source_object,
                    GAsyncResult *res,
                    gpointer      user_data)
{
        GClueCompass *compass;
        Compass *proxy = COMPASS (source_object);
        g_autoptr(GError) error = NULL;

        if (!compass_call_claim_compass_finish (proxy, res, &error)) {
                if (error && !g_error_matches (error, G_IO_ERROR,
                                               G_IO_ERROR_CANCELLED)) {
                        g_debug ("Failed to claim IIO proxy compass: %s",
                                 error->message);
                }

                g_object_unref (proxy);
                return;
        }
        g_debug ("IIO compass claimed");

        compass = GCLUE_COMPASS (user_data);
        compass->priv->proxy = proxy;

        g_object_notify_by_pspec (G_OBJECT (compass),
                                  gParamSpecs[PROP_HEADING]);
        g_signal_connect_object (G_OBJECT (proxy),
                                 "notify::compass-heading",
                                 G_CALLBACK (on_compass_heading_changed),
                                 compass,
                                 G_CONNECT_AFTER);
}

static void
on_compass_proxy_ready (GObject      *source_object,
                        GAsyncResult *res,
                        gpointer      user_data)
{
        GClueCompass *compass;
        Compass *proxy;
        g_autoptr(GError) error = NULL;

        proxy = compass_proxy_new_for_bus_finish (res, &error);
        if (proxy == NULL) {
                if (error && !g_error_matches (error, G_IO_ERROR,
                                               G_IO_ERROR_CANCELLED)) {
                        g_debug ("Failed to connect to IIO compass proxy: %s",
                                 error->message);
                }

                return;
        }

        compass = GCLUE_COMPASS (user_data);

        compass_call_claim_compass (proxy,
                                    compass->priv->cancellable,
                                    on_compass_claimed,
                                    compass);
}

static void
gclue_compass_init (GClueCompass *compass)
{
        compass->priv = gclue_compass_get_instance_private(compass);
        compass->priv->cancellable = g_cancellable_new ();

        compass_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
                                   G_DBUS_PROXY_FLAGS_NONE,
                                   "net.hadess.SensorProxy",
                                   "/net/hadess/SensorProxy/Compass",
                                   compass->priv->cancellable,
                                   on_compass_proxy_ready,
                                   compass);
}

static void
on_compass_destroyed (gpointer data,
                      GObject *where_the_object_was)
{
        GClueCompass **compass = (GClueCompass **) data;

        *compass = NULL;
}

/**
 * gclue_compass_get_singleton:
 *
 * Get the #GClueCompass singleton.
 *
 * Returns: (transfer full): a new ref to #GClueCompass. Use g_object_unref()
 * when done.
 **/
GClueCompass *
gclue_compass_get_singleton (void)
{
        static GClueCompass *compass = NULL;

        if (compass == NULL) {
                compass = g_object_new (GCLUE_TYPE_COMPASS, NULL);
                g_object_weak_ref (G_OBJECT (compass),
                                   on_compass_destroyed,
                                   &compass);
        } else
                g_object_ref (compass);

        return compass;
}

/**
 * gclue_compass_get_heading:
 * @compass: a #GClueCompass
 *
 * Gets the positive angle between direction of movement and North direction.
 * The angle is measured in degrees.
 *
 * Returns: The heading, or %GCLUE_LOCATION_HEADING_UNKNOWN if heading is
 *          unknown.
 **/
gdouble
gclue_compass_get_heading (GClueCompass *compass)
{
        gdouble ret = GCLUE_LOCATION_HEADING_UNKNOWN;

        g_return_val_if_fail (GCLUE_IS_COMPASS (compass), ret);

        if (compass->priv->proxy == NULL)
                return GCLUE_LOCATION_HEADING_UNKNOWN;

        /* FIXME:
         *
         * IIO compass gives us raw magnetic heading so we need to translate it
         * to true heading here. Some pointers on that from elad:
         *
         * A Python implementation:
         * https://github.com/cmweiss/geomag/blob/master/geomag/geomag/geomag.py
         *
         * It seems to use the magnetic model from NOAA:
         * http://www.ngdc.noaa.gov/geomag/WMM/
         *
         * C implementation: http://www.ngdc.noaa.gov/geomag/WMM/soft.shtml
         */
        ret = compass_get_compass_heading (compass->priv->proxy);

        if (ret >= 0)
                return ret;
        else
                return GCLUE_LOCATION_HEADING_UNKNOWN;
}
