/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2007 Imendio AB
 *
 * 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 <glib.h>
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <glib/gstdio.h>

#include "giggle-remote.h"
#include "giggle-enums.h"

typedef struct GiggleRemotePriv GiggleRemotePriv;

struct GiggleRemotePriv {
	GiggleRemoteMechanism  mechanism;
	gchar                 *icon_name;
	gchar                 *name;
	gchar                 *url;

	GList                 *branches; // of GiggleRemoteBranch
};

enum {
	PROP_0,
	PROP_BRANCHES,
	PROP_ICON_NAME,
	PROP_MECHANISM,
	PROP_NAME,
	PROP_URL,
	N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES] = { NULL, };

static void     remote_finalize            (GObject           *object);
static void     remote_get_property        (GObject           *object,
					   guint              param_id,
					   GValue            *value,
					   GParamSpec        *pspec);
static void     remote_set_property        (GObject           *object,
					   guint              param_id,
					   const GValue      *value,
					   GParamSpec        *pspec);

G_DEFINE_TYPE (GiggleRemote, giggle_remote, G_TYPE_OBJECT)

#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GIGGLE_TYPE_REMOTE, GiggleRemotePriv))

static void
giggle_remote_class_init (GiggleRemoteClass *class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (class);

	object_class->finalize     = remote_finalize;
	object_class->get_property = remote_get_property;
	object_class->set_property = remote_set_property;

	properties[PROP_BRANCHES] = g_param_spec_pointer ("branches", "Branches",
	                                                  "The list of remote branches",
	                                                  G_PARAM_READABLE);

	properties[PROP_ICON_NAME] = g_param_spec_string ("icon-name", "Icon Name",
	                                                  "This remote's icon",
	                                                  NULL,
	                                                  G_PARAM_READWRITE);

	properties[PROP_MECHANISM] = g_param_spec_enum ("mechanism", "Mechanism",
	                                                "This remote's mechanism",
	                                                GIGGLE_TYPE_REMOTE_MECHANISM,
	                                                GIGGLE_REMOTE_MECHANISM_GIT,
	                                                G_PARAM_READWRITE);

	properties[PROP_NAME] = g_param_spec_string ("name", "Name",
	                                             "This remote's name",
	                                             NULL,
	                                             G_PARAM_READWRITE);

	properties[PROP_URL] = g_param_spec_string ("url", "URL",
	                                            "This remote's URL",
	                                            NULL,
	                                            G_PARAM_READWRITE);

	/* Install all properties */
	g_object_class_install_properties (object_class, N_PROPERTIES, properties);

	g_type_class_add_private (object_class, sizeof (GiggleRemotePriv));
}

static void
giggle_remote_init (GiggleRemote *remote)
{
}

static void
remote_finalize (GObject *object)
{
	GiggleRemotePriv *priv;

	priv = GET_PRIV (object);

	g_free (priv->icon_name);
	g_free (priv->name);
	g_free (priv->url);

	giggle_remote_remove_branches (GIGGLE_REMOTE (object));

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

static const char *
remote_get_icon_name (GiggleRemotePriv *priv)
{
	if (priv->icon_name)
		return priv->icon_name;

	switch (priv->mechanism) {
	case GIGGLE_REMOTE_MECHANISM_GIT:
		return "giggle-scm-git";

	case GIGGLE_REMOTE_MECHANISM_GIT_SVN:
		return "giggle-scm-svn";

	case GIGGLE_REMOTE_MECHANISM_INVALID:
		g_return_val_if_reached (NULL);
		break;

	default:
		g_return_val_if_reached (NULL);
		break;
	}
}

static void
remote_get_property (GObject    *object,
		     guint       param_id,
		     GValue     *value,
		     GParamSpec *pspec)
{
	GiggleRemotePriv *priv;

	priv = GET_PRIV (object);

	switch (param_id) {
	case PROP_BRANCHES:
		g_value_set_pointer (value, priv->branches);
		break;
	case PROP_ICON_NAME:
		g_value_set_string (value, remote_get_icon_name (priv));
		break;
	case PROP_NAME:
		g_value_set_string (value, priv->name);
		break;
	case PROP_URL:
		g_value_set_string (value, priv->url);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
		break;
	}
}

static void
remote_set_property (GObject      *object,
		     guint         param_id,
		     const GValue *value,
		     GParamSpec   *pspec)
{
	switch (param_id) {
	case PROP_ICON_NAME:
		giggle_remote_set_icon_name (GIGGLE_REMOTE (object),
					     g_value_get_string (value));
		break;
	case PROP_NAME:
		giggle_remote_set_name (GIGGLE_REMOTE (object),
					g_value_get_string (value));
		break;
	case PROP_URL:
		giggle_remote_set_url (GIGGLE_REMOTE (object),
				       g_value_get_string (value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
		break;
	}
}

GiggleRemote *
giggle_remote_new (gchar const *name)
{
	return g_object_new (GIGGLE_TYPE_REMOTE, "name", name, NULL);
}

void
giggle_remote_add_branch (GiggleRemote       *remote,
			  GiggleRemoteBranch *branch)
{
	GiggleRemotePriv *priv;

	priv = GET_PRIV (remote);

	priv->branches = g_list_append (priv->branches, g_object_ref (branch));
	g_object_notify_by_pspec (G_OBJECT (remote), properties[PROP_BRANCHES]);
}

GiggleRemote *
giggle_remote_new_from_file (gchar const *filename)
{
	GiggleRemote *remote;
	gchar        *content;

	content = g_path_get_basename (filename);
	remote = giggle_remote_new (content);
	g_free (content);
	content = NULL;

	if(g_file_get_contents (filename, &content, NULL, NULL)) {
		gchar**lines;
		gchar**step;
		lines = g_strsplit (content, "\n", -1);
		for (step = lines; step && *step; step++) {
			GiggleRemoteBranch* branch = NULL;
			if(!**step) {
				/* empty string */
				continue;
			} else if(g_str_has_prefix(*step, "URL: ")) {
				giggle_remote_set_url (remote, *step + strlen ("URL: "));
			} else if(g_str_has_prefix(*step, "Push: ")) {
				branch = giggle_remote_branch_new (GIGGLE_REMOTE_DIRECTION_PUSH,
								   *step + strlen ("Push: "));
			} else if(g_str_has_prefix(*step, "Pull: ")) {
				branch = giggle_remote_branch_new (GIGGLE_REMOTE_DIRECTION_PULL,
								   *step + strlen ("Pull: "));
			} else {
				gchar* escaped = g_strescape (*step, NULL);
				g_warning ("Read unexpected line at %s:%td\n\"%s\"",
					   filename, step - lines, escaped);
				g_free (escaped);
			}

			if(GIGGLE_IS_REMOTE_BRANCH (branch)) {
				giggle_remote_add_branch (remote, branch);
				g_object_unref (branch);
				branch = NULL;
			}
		}
		g_strfreev (lines);
	}
	g_free (content);

	return remote;
}

G_GNUC_PURE static const char *
remote_get_config_prefix (GiggleRemoteMechanism mechanism)
{
	switch (mechanism) {
	case GIGGLE_REMOTE_MECHANISM_GIT:
		return "remote.";

	case GIGGLE_REMOTE_MECHANISM_GIT_SVN:
		return "svn-remote.";

	case GIGGLE_REMOTE_MECHANISM_INVALID:
		g_return_val_if_reached (NULL);
		break;

	default:
		g_return_val_if_reached (NULL);
		break;
	}
}

void
giggle_remote_apply_config (GiggleRemote *remote,
			    GHashTable   *config)
{
	GiggleRemotePriv   *priv;
	GiggleRemoteBranch *branch;
	const char         *url, *pull, *push;
	const char         *prefix = NULL;
	char               *key;

	g_return_if_fail (GIGGLE_IS_REMOTE (remote));
	g_return_if_fail (NULL != config);

	priv = GET_PRIV (remote);

	prefix = remote_get_config_prefix (priv->mechanism);
	g_return_if_fail (NULL != prefix);

	key = g_strconcat (prefix, priv->name, ".url", NULL);
	url = g_hash_table_lookup (config, key);
	g_free (key);

	key = g_strconcat (prefix, priv->name, ".fetch", NULL);
	pull = g_hash_table_lookup (config, key);
	g_free (key);

	key = g_strconcat (prefix, priv->name, ".push", NULL);
	push = g_hash_table_lookup (config, key);
	g_free (key);

	if (url) {
		giggle_remote_set_url (remote, url);
	}

	if (pull) {
		branch = giggle_remote_branch_new (GIGGLE_REMOTE_DIRECTION_PULL, pull);
		giggle_remote_add_branch (remote, branch);
		g_object_unref (branch);
	}

	if (push) {
		branch = giggle_remote_branch_new (GIGGLE_REMOTE_DIRECTION_PUSH, push);
		giggle_remote_add_branch (remote, branch);
		g_object_unref (branch);
	}
}

GList *
giggle_remote_get_branches (GiggleRemote *remote)
{
	g_return_val_if_fail (GIGGLE_IS_REMOTE (remote), NULL);

	return GET_PRIV (remote)->branches;
}

void
giggle_remote_remove_branches (GiggleRemote *self)
{
	GiggleRemotePriv *priv;

	g_return_if_fail (GIGGLE_IS_REMOTE (self));

	priv = GET_PRIV (self);

	g_list_free_full (priv->branches, g_object_unref);
	priv->branches = NULL;

	g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BRANCHES]);
}

const gchar *
giggle_remote_get_icon_name (GiggleRemote *remote)
{
	g_return_val_if_fail (GIGGLE_IS_REMOTE (remote), NULL);

	return remote_get_icon_name (GET_PRIV (remote));
}

void
giggle_remote_set_icon_name (GiggleRemote *self,
			     gchar const  *icon_name)
{
	GiggleRemotePriv *priv;

	g_return_if_fail (GIGGLE_IS_REMOTE (self));
	g_return_if_fail (!icon_name || *icon_name);

	priv = GET_PRIV (self);

	if (icon_name == priv->icon_name) {
		return;
	}

	g_free (priv->icon_name);
	priv->icon_name = g_strdup (icon_name);

	g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ICON_NAME]);
}

GiggleRemoteMechanism
giggle_remote_get_mechanism (GiggleRemote *remote)
{
	g_return_val_if_fail (GIGGLE_IS_REMOTE (remote),
			      GIGGLE_REMOTE_MECHANISM_INVALID);

	return GET_PRIV (remote)->mechanism;
}

void
giggle_remote_set_mechanism (GiggleRemote          *self,
			     GiggleRemoteMechanism  mechanism)
{
	GiggleRemotePriv *priv;

	g_return_if_fail (GIGGLE_IS_REMOTE (self));
	g_return_if_fail (mechanism < GIGGLE_REMOTE_MECHANISM_INVALID);

	priv = GET_PRIV (self);

	if (mechanism != priv->mechanism) {
		priv->mechanism = mechanism;
		g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MECHANISM]);
	}
}

const gchar *
giggle_remote_get_name (GiggleRemote *remote)
{
	g_return_val_if_fail (GIGGLE_IS_REMOTE (remote), NULL);

	return GET_PRIV (remote)->name;
}

void
giggle_remote_set_name (GiggleRemote *self,
			gchar const  *name)
{
	GiggleRemotePriv *priv;

	g_return_if_fail (GIGGLE_IS_REMOTE (self));
	g_return_if_fail (name && *name);

	priv = GET_PRIV (self);

	if (name == priv->name) {
		return;
	}

	g_free (priv->name);
	priv->name = g_strdup (name);

	g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NAME]);
}

const gchar *
giggle_remote_get_url (GiggleRemote *remote)
{
	g_return_val_if_fail (GIGGLE_IS_REMOTE (remote), NULL);

	return GET_PRIV (remote)->url;
}

void
giggle_remote_set_url (GiggleRemote *remote,
		       const gchar  *url)
{
	GiggleRemotePriv *priv;

	g_return_if_fail (GIGGLE_IS_REMOTE (remote));
	priv = GET_PRIV (remote);

	if(priv->url == url) {
		return;
	}

	g_free (priv->url);
	priv->url = g_strdup (url);

	g_object_notify_by_pspec (G_OBJECT (remote), properties[PROP_URL]);
}

void
giggle_remote_save_to_file (GiggleRemote *self,
			    gchar const  *filename)
{
	GList            *branches;
	FILE             *file;

	g_return_if_fail (GIGGLE_IS_REMOTE (self));

	file = g_fopen (filename, "w");

	g_return_if_fail (file);

	/* "URL: kenny.imendio.com:/var/git/public/sven/giggle.git" */
	fprintf (file, "URL: %s\n", giggle_remote_get_url (self));

	for (branches = giggle_remote_get_branches (self); branches; branches = g_list_next (branches)) {
		/* "Pull: refs/heads/master:refs/heads/origin" */
		gchar const* direction;

		switch(giggle_remote_branch_get_direction (branches->data)) {
		case GIGGLE_REMOTE_DIRECTION_PULL:
			direction = "Pull";
			break;
		case GIGGLE_REMOTE_DIRECTION_PUSH:
			direction = "Push";
			break;
		default:
			g_warning ("Got unexpected remote direction: %d",
				   giggle_remote_branch_get_direction (branches->data));
			direction = "";
			break;
		}

		fprintf (file, "%s: %s\n", direction,
			 giggle_remote_branch_get_refspec (branches->data));
	}
	fclose (file);
}
