/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * pluma-source-style-manager.c
 *
 * Copyright (C) 2007 - Paolo Borelli and Paolo Maggi
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/*
 * Modified by the pluma Team, 2007. See the AUTHORS file for a
 * list of people on the pluma Team.
 * See the ChangeLog files for a list of changes.
 *
 * $Id$
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <errno.h>

#include <glib/gi18n.h>
#include <glib/gstdio.h>

#include <gtk/gtk.h>
#if GTK_CHECK_VERSION (3, 0, 0)
#include <gtksourceview/gtksource.h>
#endif

#include "pluma-style-scheme-manager.h"
#include "pluma-prefs-manager.h"
#include "pluma-dirs.h"

static GtkSourceStyleSchemeManager *style_scheme_manager = NULL;

static gchar *
get_pluma_styles_path (void)
{
	gchar *config_dir;
	gchar *dir = NULL;

	config_dir = pluma_dirs_get_user_config_dir ();

	if (config_dir != NULL)
	{
		dir = g_build_filename (config_dir,
					"styles",
					NULL);
		g_free (config_dir);
	}
	
	return dir;
}

static void
add_pluma_styles_path (GtkSourceStyleSchemeManager *mgr)
{
	gchar *dir;

	dir = get_pluma_styles_path();

	if (dir != NULL)
	{
		gtk_source_style_scheme_manager_append_search_path (mgr, dir);
		g_free (dir);
	}	
}

GtkSourceStyleSchemeManager *
pluma_get_style_scheme_manager (void)
{
	if (style_scheme_manager == NULL)
	{
		style_scheme_manager = gtk_source_style_scheme_manager_new ();
		add_pluma_styles_path (style_scheme_manager);
	}

	return style_scheme_manager;
}

static gint
schemes_compare (gconstpointer a, gconstpointer b)
{
	GtkSourceStyleScheme *scheme_a = (GtkSourceStyleScheme *)a;
	GtkSourceStyleScheme *scheme_b = (GtkSourceStyleScheme *)b;

	const gchar *name_a = gtk_source_style_scheme_get_name (scheme_a);
	const gchar *name_b = gtk_source_style_scheme_get_name (scheme_b);

	return g_utf8_collate (name_a, name_b);
}

GSList *
pluma_style_scheme_manager_list_schemes_sorted (GtkSourceStyleSchemeManager *manager)
{
	const gchar * const * scheme_ids;
	GSList *schemes = NULL;

#if GTK_CHECK_VERSION (3, 0, 0)
	g_return_val_if_fail (GTK_SOURCE_IS_STYLE_SCHEME_MANAGER (manager), NULL);
#else
	g_return_val_if_fail (GTK_IS_SOURCE_STYLE_SCHEME_MANAGER (manager), NULL);
#endif

	scheme_ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);
	
	while (*scheme_ids != NULL)
	{
		GtkSourceStyleScheme *scheme;

		scheme = gtk_source_style_scheme_manager_get_scheme (manager, 
								     *scheme_ids);

		schemes = g_slist_prepend (schemes, scheme);

		++scheme_ids;
	}

	if (schemes != NULL)
		schemes = g_slist_sort (schemes, (GCompareFunc)schemes_compare);

	return schemes;
}

gboolean
_pluma_style_scheme_manager_scheme_is_pluma_user_scheme (GtkSourceStyleSchemeManager *manager,
							 const gchar                 *scheme_id)
{
	GtkSourceStyleScheme *scheme;
	const gchar *filename;
	gchar *dir;
	gboolean res = FALSE;

	scheme = gtk_source_style_scheme_manager_get_scheme (manager, scheme_id);
	if (scheme == NULL)
		return FALSE;

	filename = gtk_source_style_scheme_get_filename (scheme);
	if (filename == NULL)
		return FALSE;

	dir = get_pluma_styles_path ();

	res = g_str_has_prefix (filename, dir);

	g_free (dir);

	return res;
}

/**
 * file_copy:
 * @name: a pointer to a %NULL-terminated string, that names
 * the file to be copied, in the GLib file name encoding
 * @dest_name: a pointer to a %NULL-terminated string, that is the
 * name for the destination file, in the GLib file name encoding
 * @error: return location for a #GError, or %NULL
 *
 * Copies file @name to @dest_name.
 *
 * If the call was successful, it returns %TRUE. If the call was not
 * successful, it returns %FALSE and sets @error. The error domain
 * is #G_FILE_ERROR. Possible error
 * codes are those in the #GFileError enumeration.
 *
 * Return value: %TRUE on success, %FALSE otherwise.
 */
static gboolean
file_copy (const gchar  *name,
	   const gchar  *dest_name,
	   GError      **error)
{
	gchar *contents;
	gsize length;
	gchar *dest_dir;

	/* FIXME - Paolo (Aug. 13, 2007):
	 * Since the style scheme files are relatively small, we can implement
	 * file copy getting all the content of the source file in a buffer and
	 * then write the content to the destination file. In this way we
	 * can use the g_file_get_contents and g_file_set_contents and avoid to
	 * write custom code to copy the file (with sane error management).
	 * If needed we can improve this code later. */

	g_return_val_if_fail (name != NULL, FALSE);
	g_return_val_if_fail (dest_name != NULL, FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	/* Note: we allow to copy a file to itself since this is not a problem
	 * in our use case */

	/* Ensure the destination directory exists */
	dest_dir = g_path_get_dirname (dest_name);

	errno = 0;
	if (g_mkdir_with_parents (dest_dir, 0755) != 0)
	{
		gint save_errno = errno;
		gchar *display_filename = g_filename_display_name (dest_dir);

		g_set_error (error,
			     G_FILE_ERROR,
			     g_file_error_from_errno (save_errno),
			     _("Directory '%s' could not be created: g_mkdir_with_parents() failed: %s"),
			     display_filename,
			     g_strerror (save_errno));

		g_free (dest_dir);
		g_free (display_filename);

		return FALSE;
	}

	g_free (dest_dir);

	if (!g_file_get_contents (name, &contents, &length, error))
		return FALSE;

	if (!g_file_set_contents (dest_name, contents, length, error))
		return FALSE;

	g_free (contents);

	return TRUE;
}

/**
 * _pluma_style_scheme_manager_install_scheme:
 * @manager: a #GtkSourceStyleSchemeManager
 * @fname: the file name of the style scheme to be installed
 *
 * Install a new user scheme.
 * This function copies @fname in #PLUMA_STYLES_DIR and ask the style manager to
 * recompute the list of available style schemes. It then checks if a style
 * scheme with the right file name exists.
 *
 * If the call was succesful, it returns the id of the installed scheme
 * otherwise %NULL.
 *
 * Return value: the id of the installed scheme, %NULL otherwise.
 */
const gchar *
_pluma_style_scheme_manager_install_scheme (GtkSourceStyleSchemeManager *manager,
					    const gchar                 *fname)
{
	gchar *new_file_name = NULL;
	gchar *dirname;
	gchar *styles_dir;
	GError *error = NULL;
	gboolean copied = FALSE;

	const gchar* const *ids;

#if GTK_CHECK_VERSION (3, 0, 0)
	g_return_val_if_fail (GTK_SOURCE_IS_STYLE_SCHEME_MANAGER (manager), NULL);
#else
	g_return_val_if_fail (GTK_IS_SOURCE_STYLE_SCHEME_MANAGER (manager), NULL);
#endif
	g_return_val_if_fail (fname != NULL, NULL);

	dirname = g_path_get_dirname (fname);
	styles_dir = get_pluma_styles_path();

	if (strcmp (dirname, styles_dir) != 0)
	{
		gchar *basename;

		basename = g_path_get_basename (fname);
		new_file_name = g_build_filename (styles_dir, basename, NULL);
		g_free (basename);

		/* Copy the style scheme file into PLUMA_STYLES_DIR */
		if (!file_copy (fname, new_file_name, &error))
		{
			g_free (new_file_name);

			g_message ("Cannot install style scheme:\n%s",
				   error->message);

			g_free (dirname);
			g_free (styles_dir);
			return NULL;
		}

		copied = TRUE;
	}
	else
	{
		new_file_name = g_strdup (fname);
	}

	g_free (dirname);
	g_free (styles_dir);

	/* Reload the available style schemes */
	gtk_source_style_scheme_manager_force_rescan (manager);

	/* Check the new style scheme has been actually installed */
	ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);

	while (*ids != NULL)
	{
		GtkSourceStyleScheme *scheme;
		const gchar *filename;

		scheme = gtk_source_style_scheme_manager_get_scheme (
				pluma_get_style_scheme_manager (), *ids);

		filename = gtk_source_style_scheme_get_filename (scheme);

		if (filename && (strcmp (filename, new_file_name) == 0))
		{
			/* The style scheme has been correctly installed */
			g_free (new_file_name);

			return gtk_source_style_scheme_get_id (scheme);
		}
		++ids;
	}

	/* The style scheme has not been correctly installed */
	if (copied)
		g_unlink (new_file_name);

	g_free (new_file_name);

	return NULL;
}

/**
 * _pluma_style_scheme_manager_uninstall_scheme:
 * @manager: a #GtkSourceStyleSchemeManager
 * @id: the id of the style scheme to be uninstalled
 *
 * Uninstall a user scheme.
 *
 * If the call was succesful, it returns %TRUE
 * otherwise %FALSE.
 *
 * Return value: %TRUE on success, %FALSE otherwise.
 */
gboolean
_pluma_style_scheme_manager_uninstall_scheme (GtkSourceStyleSchemeManager *manager,
					      const gchar                 *id)
{
	GtkSourceStyleScheme *scheme;
	const gchar *filename;

#if GTK_CHECK_VERSION (3, 0, 0)
	g_return_val_if_fail (GTK_SOURCE_IS_STYLE_SCHEME_MANAGER (manager), FALSE);
#else
	g_return_val_if_fail (GTK_IS_SOURCE_STYLE_SCHEME_MANAGER (manager), FALSE);
#endif
	g_return_val_if_fail (id != NULL, FALSE);

	scheme = gtk_source_style_scheme_manager_get_scheme (manager, id);
	if (scheme == NULL)
		return FALSE;

	filename = gtk_source_style_scheme_get_filename (scheme);
	if (filename == NULL)
		return FALSE;

	if (g_unlink (filename) == -1)
		return FALSE;
		
	/* Reload the available style schemes */
	gtk_source_style_scheme_manager_force_rescan (manager);
	
	return TRUE;	
}
