/*
   Copyright 2011-2012 Bastien Nocera

   The Gnome Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome Library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301  USA.

   Authors: Bastien Nocera <hadess@hadess.net>

 */

#include <config.h>

#include <string.h>
#include <locale.h>
#include <gio/gio.h>
#include <glib/gi18n-lib.h>
#include <geocode-glib/geocode-glib.h>
#include <geocode-glib/geocode-error.h>
#include <geocode-glib/geocode-reverse.h>
#include <geocode-glib/geocode-backend.h>
#include <geocode-glib/geocode-nominatim.h>
#include <geocode-glib/geocode-glib-private.h>

/**
 * SECTION:geocode-reverse
 * @short_description: Geocode reverse geocoding object
 * @include: geocode-glib/geocode-glib.h
 *
 * Contains functions for reverse geocoding using the
 * <ulink url="http://wiki.openstreetmap.org/wiki/Nominatim">OSM Nominatim APIs</ulink>
 **/

struct _GeocodeReversePrivate {
	GeocodeLocation *location;
	GeocodeBackend  *backend;
};

G_DEFINE_TYPE_WITH_CODE (GeocodeReverse, geocode_reverse, G_TYPE_OBJECT,
                         G_ADD_PRIVATE (GeocodeReverse))

static void
geocode_reverse_finalize (GObject *gobject)
{
	GeocodeReverse *object = (GeocodeReverse *) gobject;
	GeocodeReversePrivate *priv;

	priv = geocode_reverse_get_instance_private (object);

	g_clear_object (&priv->location);
	g_clear_object (&priv->backend);

	G_OBJECT_CLASS (geocode_reverse_parent_class)->finalize (gobject);
}

static void
geocode_reverse_class_init (GeocodeReverseClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

	bindtextdomain (GETTEXT_PACKAGE, GEOCODE_LOCALEDIR);
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");

	gobject_class->finalize = geocode_reverse_finalize;
}

static void
geocode_reverse_init (GeocodeReverse *object)
{
}

/**
 * geocode_reverse_new_for_location:
 * @location: a #GeocodeLocation object
 *
 * Creates a new #GeocodeReverse to perform reverse geocoding with.
 * Use geocode_reverse_resolve_async() to perform the resolution.
 *
 * Returns: a new #GeocodeReverse. Use g_object_unref() when done.
 **/
GeocodeReverse *
geocode_reverse_new_for_location (GeocodeLocation *location)
{
	GeocodeReverse *object;
	GeocodeReversePrivate *priv;

	g_return_val_if_fail (GEOCODE_IS_LOCATION (location), NULL);

	object = g_object_new (GEOCODE_TYPE_REVERSE, NULL);
	priv = geocode_reverse_get_instance_private (object);
	priv->location = g_object_ref (location);

	return object;
}

static void
ensure_backend (GeocodeReverse *object)
{
	GeocodeReversePrivate *priv;

	priv = geocode_reverse_get_instance_private (object);
	/* If no backend is specified, default to the GNOME Nominatim backend */
	if (priv->backend == NULL)
		priv->backend = GEOCODE_BACKEND (geocode_nominatim_get_gnome ());
}

static GValue *
double_to_value (gdouble val)
{
	GValue *value;

	value = g_new0 (GValue, 1);
	g_value_init (value, G_TYPE_DOUBLE);
	g_value_set_double (value, val);

	return value;
}

static void
free_value (GValue *value)
{
	g_value_unset (value);
	g_free (value);
}

static GHashTable *
_geocode_location_to_params (GeocodeLocation *location)
{
	GHashTable *ht;

	/* Semantics from http://xmpp.org/extensions/xep-0080.html */
	ht = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
	                            (GDestroyNotify) free_value);
	g_hash_table_insert (ht, (gpointer) "lat",
	                     double_to_value (geocode_location_get_latitude (location)));
	g_hash_table_insert (ht, (gpointer) "lon",
	                     double_to_value (geocode_location_get_longitude (location)));

	return ht;
}

static void
places_list_free (GList *places)
{
	g_list_free_full (places, g_object_unref);
}

static void
backend_reverse_resolve_ready (GeocodeBackend *backend,
                               GAsyncResult   *res,
                               GTask          *task)
{
	GList *places = NULL;  /* (element-type GeocodePlace) */
	GError *error = NULL;

	/* Extract the first result from the list and return that. */
	places = geocode_backend_reverse_resolve_finish (backend, res, &error);
	if (places != NULL)
		g_task_return_pointer (task, g_object_ref (places->data),
		                       g_object_unref);
	else
		g_task_return_error (task, error);
	g_object_unref (task);
	g_clear_pointer (&places, places_list_free);
}

/**
 * geocode_reverse_resolve_async:
 * @object: a #GeocodeReverse representing a query
 * @cancellable: optional #GCancellable object, %NULL to ignore.
 * @callback: a #GAsyncReadyCallback to call when the request is satisfied
 * @user_data: the data to pass to callback function
 *
 * Asynchronously gets the result of a reverse geocoding
 * query using a web service. Use geocode_reverse_resolve() to do the same
 * thing synchronously.
 *
 * When the operation is finished, @callback will be called. You can then call
 * geocode_reverse_resolve_finish() to get the result of the operation.
 **/
void
geocode_reverse_resolve_async (GeocodeReverse     *object,
                               GCancellable       *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer            user_data)
{
	GTask *task;
	GeocodeReversePrivate *priv;
	g_autoptr (GHashTable) params = NULL;

	g_return_if_fail (GEOCODE_IS_REVERSE (object));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));

	ensure_backend (object);
	priv = geocode_reverse_get_instance_private (object);
	g_assert (priv->backend != NULL);

	params = _geocode_location_to_params (priv->location);

	task = g_task_new (object, cancellable, callback, user_data);
	geocode_backend_reverse_resolve_async (priv->backend,
	                                       params,
	                                       cancellable,
	                                       (GAsyncReadyCallback) backend_reverse_resolve_ready,
	                                       g_object_ref (task));
	g_object_unref (task);
}

/**
 * geocode_reverse_resolve_finish:
 * @object: a #GeocodeReverse representing a query
 * @res: a #GAsyncResult.
 * @error: a #GError.
 *
 * Finishes a reverse geocoding operation. See geocode_reverse_resolve_async().
 *
 * Returns: (transfer full): A #GeocodePlace instance, or %NULL in case of
 * errors. Free the returned instance with #g_object_unref() when done.
 **/
GeocodePlace *
geocode_reverse_resolve_finish (GeocodeReverse *object,
                                GAsyncResult   *res,
                                GError        **error)
{
	g_return_val_if_fail (GEOCODE_IS_REVERSE (object), NULL);
	g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	return GEOCODE_PLACE (g_task_propagate_pointer (G_TASK (res), error));
}

/**
 * geocode_reverse_resolve:
 * @object: a #GeocodeReverse representing a query
 * @error: a #GError
 *
 * Gets the result of a reverse geocoding
 * query using the current backend (see geocode_reverse_set_backend()). By
 * default the GNOME Nominatim server is used. See #GeocodeBackend for more
 * information.
 *
 * If no result could be found, a %GEOCODE_ERROR_NOT_SUPPORTED error will be
 * returned. This typically happens if the coordinates to geocode are in the
 * middle of the ocean.
 *
 * Returns: (transfer full): A #GeocodePlace instance, or %NULL in case of
 * errors. Free the returned instance with #g_object_unref() when done.
 **/
GeocodePlace *
geocode_reverse_resolve (GeocodeReverse *object,
                         GError        **error)
{
	GeocodeReversePrivate *priv;
	GList *places = NULL;  /* (element-type GeocodePlace) */
	GeocodePlace *place = NULL;
	g_autoptr (GHashTable) params = NULL;

	g_return_val_if_fail (GEOCODE_IS_REVERSE (object), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	priv = geocode_reverse_get_instance_private (object);
	ensure_backend (object);
	g_assert (priv->backend != NULL);

	params = _geocode_location_to_params (priv->location);
	places = geocode_backend_reverse_resolve (priv->backend,
	                                          params,
	                                          NULL,
	                                          error);

	if (places != NULL)
		place = g_object_ref (places->data);

	g_list_free_full (places, g_object_unref);

	return place;
}

/**
 * geocode_reverse_set_backend:
 * @object: a #GeocodeReverse representing a query
 * @backend: (nullable): a #GeocodeBackend, or %NULL to use the default one.
 *
 * Specifies the backend to use in the reverse geocoding operation.
 *
 * If none is given, the default GNOME Nominatim server is used.
 *
 * Since: 3.23.1
 */
void
geocode_reverse_set_backend (GeocodeReverse *object,
                             GeocodeBackend *backend)
{
	GeocodeReversePrivate *priv;

	g_return_if_fail (GEOCODE_IS_REVERSE (object));
	g_return_if_fail (backend == NULL || GEOCODE_IS_BACKEND (backend));

	priv = geocode_reverse_get_instance_private (object);

	g_set_object (&priv->backend, backend);
}
