/* sml_location.c
 *
 * Copyright (C) 2009  Michael Bell <michael.bell@opensync.org>
 *
 * This 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.
 *
 * This 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 */

#include "sml_location.h"
#include <string.h>

G_DEFINE_TYPE (SmlLocation, sml_location, G_TYPE_OBJECT)

enum
{
	PROP_0,
	PROP_URI,
	PROP_NAME,
	PROP_PARENT_URI
};

struct _SmlLocationPrivate
{
	gchar*  uri;
	gchar*  name;
	gchar*  parent_uri;
	gchar*  full_uri;
	GMutex* lock;
};

static void
sml_location_get_property (GObject    *object,
                           guint       property_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
	switch (property_id) {
	case PROP_URI:
		g_value_set_string (value, SML_LOCATION (object)->priv->uri);
		break;
	case PROP_NAME:
		g_value_set_string (value, SML_LOCATION (object)->priv->name);
		break;
	case PROP_PARENT_URI:
		g_value_set_string (value, SML_LOCATION (object)->priv->parent_uri);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	}
}

static void
sml_location_set_property (GObject      *object,
                           guint         property_id,
                           const GValue *value,
                           GParamSpec   *pspec)
{
	g_mutex_lock(SML_LOCATION (object)->priv->lock);
	switch (property_id) {
	case PROP_URI:
		g_free (SML_LOCATION (object)->priv->uri);
		SML_LOCATION (object)->priv->uri = g_strdup (g_value_get_string (value));
		if (SML_LOCATION (object)->priv->full_uri) {
			g_free(SML_LOCATION (object)->priv->full_uri);
			SML_LOCATION (object)->priv->full_uri = NULL;
		}
		break;
	case PROP_NAME:
		g_free (SML_LOCATION (object)->priv->name);
		SML_LOCATION (object)->priv->name = g_strdup (g_value_get_string (value));
		break;
	case PROP_PARENT_URI:
		g_free (SML_LOCATION (object)->priv->parent_uri);
		SML_LOCATION (object)->priv->parent_uri = g_strdup (g_value_get_string (value));
		if (SML_LOCATION (object)->priv->full_uri) {
			g_free(SML_LOCATION (object)->priv->full_uri);
			SML_LOCATION (object)->priv->full_uri = NULL;
		}
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	}
	g_mutex_unlock(SML_LOCATION (object)->priv->lock);
}

static void
sml_location_finalize (GObject *object)
{
	SmlLocation *self = SML_LOCATION (object);
	g_mutex_lock(self->priv->lock);
	g_mutex_unlock(self->priv->lock);
	g_free(self->priv->uri);
	g_free(self->priv->name);
	g_free(self->priv->parent_uri);
	g_free(self->priv->full_uri);
	g_mutex_free(self->priv->lock);
	/* all pointers must be NULL */
	self->priv->uri = NULL;
	self->priv->name = NULL;
	self->priv->parent_uri = NULL;
	self->priv->full_uri = NULL;
	self->priv->lock = NULL;
	G_OBJECT_CLASS (sml_location_parent_class)->finalize (object);
}

static void
sml_location_class_init (SmlLocationClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	g_type_class_add_private (klass, sizeof (SmlLocationPrivate));

	object_class->get_property = sml_location_get_property;
	object_class->set_property = sml_location_set_property;
	object_class->finalize     = sml_location_finalize;

	/**
	 * SmlLocation:uri:
	 *
	 * The URI property.
	 */
	g_object_class_install_property (object_class,
	                                 PROP_URI,
	                                 g_param_spec_string ("URI",
	                                                      "URI",
	                                                      "URI",
	                                                      NULL,
	                                                      G_PARAM_READWRITE));
	/**
	 * SmlLocation:name:
	 *
	 * The name property.
	 */
	g_object_class_install_property (object_class,
	                                 PROP_NAME,
	                                 g_param_spec_string ("name",
	                                                      "name",
	                                                      "name",
	                                                      NULL,
	                                                      G_PARAM_READWRITE));
	/**
	 * SmlLocation:parent_uri:
	 *
	 * The The URI of the parent property.
	 */
	g_object_class_install_property (object_class,
	                                 PROP_PARENT_URI,
	                                 g_param_spec_string ("parentURI",
	                                                      "parentURI",
	                                                      "The URI of the parent",
	                                                      NULL,
	                                                      G_PARAM_READWRITE));

}

static void
sml_location_init (SmlLocation *self)
{
	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
	                                          SML_TYPE_LOCATION,
	                                          SmlLocationPrivate);
}

/**
 * sml_location_new:
 *
 * Creates a new instance of #SmlLocation.
 *
 * Return value: the newly created #SmlLocation instance
 */
SmlLocation*
sml_location_new (void)
{
	SmlLocation *self = g_object_new (SML_TYPE_LOCATION, NULL);
	self->priv->full_uri = NULL;
	self->priv->lock = g_mutex_new();
	return self;
}

/**
 * sml_location_get_uri:
 * @self: A #SmlLocation
 *
 * Gets the URI property.
 *
 * Return value: 
 */
G_CONST_RETURN gchar*
sml_location_get_uri (SmlLocation *self)
{
	g_return_val_if_fail (SML_IS_LOCATION (self), NULL);
	return self->priv->uri;
}

/**
 * sml_location_set_uri:
 * @self: A #SmlLocation
 * @uri:
 *
 * Sets the URI property.
 */
void
sml_location_set_uri (SmlLocation *self,
                      const gchar* uri)
{
	g_assert(SML_IS_LOCATION (self));
	g_mutex_lock(self->priv->lock);

	/* reset full uri cache */
	if (self->priv->full_uri) {
		g_free (self->priv->full_uri);
		self->priv->full_uri = NULL;
	}

	/* normalize the URI */
	if (uri && strlen(uri) == 0)
		uri = NULL;

	g_free (self->priv->uri);
	self->priv->uri = g_strdup (uri);
	if (uri && !self->priv->uri)
		g_error("Cannot copy the URI - out of memory.");
	g_mutex_unlock(self->priv->lock);
}

/**
 * sml_location_get_name:
 * @self: A #SmlLocation
 *
 * Gets the name property.
 *
 * Return value: 
 */
G_CONST_RETURN gchar*
sml_location_get_name (SmlLocation *self)
{
	g_return_val_if_fail (SML_IS_LOCATION (self), NULL);
	return self->priv->name;
}

/**
 * sml_location_set_name:
 * @self: A #SmlLocation
 * @name:
 *
 * Sets the name property.
 */
void
sml_location_set_name (SmlLocation *self,
                       const gchar* name)
{
	g_assert(SML_IS_LOCATION (self));
	g_mutex_lock(self->priv->lock);

	/* normalize the name */
	if (name && strlen(name) == 0)
		name = NULL;

	g_free (self->priv->name);
	self->priv->name = g_strdup (name);
	if (name && !self->priv->name)
		g_error("Cannot copy the name - out of memory.");
	g_mutex_unlock(self->priv->lock);
}

/**
 * sml_location_get_parent_uri:
 * @self: A #SmlLocation
 *
 * Gets the parentURI property.
 *
 * Return value: 
 */
G_CONST_RETURN gchar*
sml_location_get_parent_uri (SmlLocation *self)
{
	g_return_val_if_fail (SML_IS_LOCATION (self), NULL);
	return self->priv->parent_uri;
}

/**
 * sml_location_set_parent_uri:
 * @self: A #SmlLocation
 * @parent_uri:
 *
 * Sets the parentURI property.
 */
void
sml_location_set_parent_uri (SmlLocation *self,
                             const gchar* parent_uri)
{
	g_assert(SML_IS_LOCATION (self));
	g_mutex_lock(self->priv->lock);

	/* reset full uri cache */
	if (self->priv->full_uri) {
		g_free (self->priv->full_uri);
		self->priv->full_uri = NULL;
	}

	/* normalize the parent URI */
	if (parent_uri && strlen(parent_uri) == 0)
		parent_uri = NULL;

	g_free (self->priv->parent_uri);
	self->priv->parent_uri = g_strdup (parent_uri);
	if (parent_uri && !self->priv->parent_uri)
		g_error("Cannot copy the parent URI - out of memory.");
	g_mutex_unlock(self->priv->lock);
}

/**
 * sml_location_clone:
 * @self: A #SmlLocation
 *
 * 
 */
SmlLocation*
sml_location_clone (SmlLocation *self)
{
	g_assert(self);
	g_mutex_lock(self->priv->lock);

	SmlLocation *loc = sml_location_new();
	if (!loc)
		g_error("Cannot create new SmlLocation object - out of memory.");
	if (self->priv->uri)
		sml_location_set_uri(loc, self->priv->uri);
	if (self->priv->name)
		sml_location_set_name(loc, self->priv->name);
	if (self->priv->parent_uri)
		sml_location_set_parent_uri(loc, self->priv->parent_uri);

	g_mutex_unlock(self->priv->lock);
	return loc;
}

/**
 *
 * This function replaces all occurences of @needle with @replacement.
 * Actually we must still support glib 2.12. Therefore we cannot use
 * GRegex.
 *
 */
static gchar*
_sml_location_strreplace (const gchar *input,
                          const gchar *needle,
                          const gchar *replacement)
{
	g_assert(input);
	g_assert(strlen(input) > 0);
	g_assert(needle);
	g_assert(strlen(needle) > 0);

	/* normalize the replacement string */
	if (replacement != NULL && strlen(replacement) == 0)
		replacement = NULL;

	gchar *output = g_strdup(input);
	if (!output)
		g_error("Cannot duplicate input string - out of memory.");

	/* loop until there is no needle */
	while (g_strrstr(output, needle)) {

		/* copy the string until the position of the needle */
		gchar *prefix = g_strndup(output, g_strrstr(output, needle) - output);
		if (!prefix)
			g_error("Cannot copy string before the needle - out of memory.");

		/* concatenate prefix of needle, replacement and suffix of needle */
		gchar *buffer2 = g_strconcat(prefix, replacement ? replacement : "", g_strrstr(output, needle) + strlen(needle), NULL);
		if (!buffer2)
			g_error("Cannot create new string with replacement - out of memory.");

		/* cleanup */
		g_free(prefix);
		g_free(output);
		output = buffer2;
	}
	
	return output;
}

static gchar*
_sml_location_get_normalized_uri (const gchar *uri)
{
	g_assert(uri);
	g_assert(strlen(uri) > 0);

	/* remove ./ (e.g. "./Contacts" will be "Contacts") */
	gchar *buffer = _sml_location_strreplace(uri, "./", "");

	/* replace // from Windows-like systems (e.g. "//Contacts" will be "/Contacts") */
	gchar *buffer2 = _sml_location_strreplace(buffer, "//", "/");

	/* cleanup */
	g_free(buffer);
	buffer = NULL;
	
	/* remove a trailing slash "/" (e.g. "Contacts/" will be "Contacts")
	 * Don't do this if the URI is "/" which has a special meaning.
	 */
	if (strlen(buffer2) > 1 && buffer2[strlen(buffer2) - 1] == '/')
		buffer2[strlen(buffer2) - 1] = 0;
	
	return buffer2;
}

/**
 * sml_location_get_full_uri:
 * @self: A #SmlLocation
 *
 * Gets the name property.
 *
 * Return value: 
 */
G_CONST_RETURN gchar*
sml_location_get_full_uri (SmlLocation *self)
{
	g_assert(SML_IS_LOCATION (self));
	g_mutex_lock(self->priv->lock);

	/* use cache */
	if (self->priv->full_uri) {
		g_mutex_unlock(self->priv->lock);
		return self->priv->full_uri;
	}

	/* normalize URI */
	gchar *path = NULL;
	if (self->priv->uri) {
		path = _sml_location_get_normalized_uri(self->priv->uri);
	}

	/* add parent (e.g. TargetRef or SourceRef from hierarchical sync) */
	if (self->priv->parent_uri) {

		/* normalize parent */
		gchar *parent_uri = _sml_location_get_normalized_uri(self->priv->parent_uri);

		/* ensure absolute URI */
		if (path && !g_path_is_absolute(path)) {
			gchar *absolute = g_strconcat("/", path, NULL);
			g_free(path);
			path = absolute;
			if (!path)
				g_error("Cannot add '/' as prefix - out of memory.");
		}

		/* prepend the parent */
		gchar *parent = g_strconcat(parent_uri, path, NULL);
		g_free(path);
		g_free(parent_uri);
		path = parent;
		if (!path)
			g_error("Cannot add parent as prefix - out of memory.");
	}

	/* cache the result */
	self->priv->full_uri = _sml_location_get_normalized_uri(path);
	g_free(path);

	g_mutex_unlock(self->priv->lock);
	return self->priv->full_uri;
}

/**
 * sml_location_is_equal:
 * @self: A #SmlLocation
 *
 * 
 */
gboolean
sml_location_is_equal (SmlLocation *self,
                       SmlLocation *loc)
{
	g_assert(self);

	gboolean ret = FALSE;
	
	if (!loc)
		return FALSE;

	/* normalization */

	const char *loc_path = sml_location_get_full_uri(loc);
	const char *self_path = sml_location_get_full_uri(self);

	/* comparison */

	if (strcmp(self_path, loc_path) != 0)
		ret = FALSE;
	else
		ret = TRUE;

	return ret;
}
