/*
 * This file is part of GNOME LaTeX.
 *
 * Copyright (C) 2015-2020 - Sébastien Wilmet <swilmet@gnome.org>
 *
 * GNOME LaTeX 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 3 of the License, or
 * (at your option) any later version.
 *
 * GNOME LaTeX 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 GNOME LaTeX.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * SECTION:templates-personal
 * @title: LatexilaTemplatesPersonal
 * @short_description: Personal templates
 * @see_also: #LatexilaTemplatesDefault
 *
 * #LatexilaTemplatesPersonal is a singleton class that stores information about
 * pesonal templates.
 *
 * Personal templates are stored in the `~/.local/share/gnome-latex/` directory.
 * There is a templatesrc file that stores the list of names, icons and files.
 */

#include "config.h"
#include "latexila-templates-personal.h"
#include <string.h>
#include <stdlib.h>
#include <tepl/tepl.h>
#include "latexila-templates-common.h"
#include "latexila-utils.h"

struct _LatexilaTemplatesPersonal
{
	GtkListStore parent;
};

#define RC_FILE_GROUP_NAME "Personal templates"

G_DEFINE_TYPE (LatexilaTemplatesPersonal, latexila_templates_personal, GTK_TYPE_LIST_STORE)

static void
latexila_templates_personal_class_init (LatexilaTemplatesPersonalClass *klass)
{
}

static GFile *
get_rc_file (void)
{
	return g_file_new_build_filename (g_get_user_data_dir (),
					  "gnome-latex",
					  "templatesrc",
					  NULL);
}

static GFile *
get_personal_template_file_by_filename (const gchar *filename)
{
	return g_file_new_build_filename (g_get_user_data_dir (),
					  "gnome-latex",
					  filename,
					  NULL);
}

static GFile *
get_personal_template_file_by_index (gint template_num)
{
	gchar *filename;
	GFile *template_file;

	filename = g_strdup_printf ("%d.tex", template_num);
	template_file = get_personal_template_file_by_filename (filename);

	g_free (filename);
	return template_file;
}

static void
load_rc_file (LatexilaTemplatesPersonal *templates)
{
	GFile *rc_file;
	gchar *contents = NULL;
	gsize length;
	GKeyFile *key_file = NULL;
	gchar **names = NULL;
	gchar **icons = NULL;
	gchar **files = NULL;
	gsize n_names;
	gsize n_icons;
	gsize n_files;
	gboolean has_files;
	guint i;
	GError *error = NULL;

	rc_file = get_rc_file ();

	g_file_load_contents (rc_file, NULL, &contents, &length, NULL, &error);

	if (error != NULL)
	{
		/* If the rc file doesn't exist, it means that there is no personal
		 * templates.
		 */
		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
		{
			g_error_free (error);
			error = NULL;
		}

		goto out;
	}

	key_file = g_key_file_new ();
	g_key_file_load_from_data (key_file, contents, length, G_KEY_FILE_NONE, &error);

	if (error != NULL)
	{
		goto out;
	}

	names = g_key_file_get_string_list (key_file, RC_FILE_GROUP_NAME, "names", &n_names, &error);

	if (error != NULL)
	{
		goto out;
	}

	icons = g_key_file_get_string_list (key_file, RC_FILE_GROUP_NAME, "icons", &n_icons, &error);

	if (error != NULL)
	{
		goto out;
	}

	g_return_if_fail (n_names == n_icons);

	has_files = g_key_file_has_key (key_file, RC_FILE_GROUP_NAME, "files", &error);

	if (error != NULL)
	{
		goto out;
	}

	if (has_files)
	{
		files = g_key_file_get_string_list (key_file, RC_FILE_GROUP_NAME, "files", &n_files, &error);

		if (error != NULL)
		{
			goto out;
		}

		g_return_if_fail (n_names == n_files);
	}

	for (i = 0; i < n_names; i++)
	{
		GFile *template_file;

		if (has_files)
		{
			template_file = get_personal_template_file_by_filename (files[i]);
		}
		else
		{
			template_file = get_personal_template_file_by_index (i);
		}

		latexila_templates_add_template (GTK_LIST_STORE (templates),
						 names[i],
						 icons[i],
						 template_file);

		g_object_unref (template_file);
	}

out:

	if (error != NULL)
	{
		g_warning ("The loading of personal templates failed: %s", error->message);
		g_error_free (error);
	}

	g_object_unref (rc_file);
	g_free (contents);
	g_strfreev (names);
	g_strfreev (icons);

	if (key_file != NULL)
	{
		g_key_file_unref (key_file);
	}
}

static void
latexila_templates_personal_init (LatexilaTemplatesPersonal *templates)
{
	latexila_templates_init_store (GTK_LIST_STORE (templates));
	load_rc_file (templates);
}

/**
 * latexila_templates_personal_get_instance:
 *
 * Gets the instance of the #LatexilaTemplatesPersonal singleton.
 *
 * Returns: (transfer none): the instance of #LatexilaTemplatesPersonal.
 */
LatexilaTemplatesPersonal *
latexila_templates_personal_get_instance (void)
{
	static LatexilaTemplatesPersonal *instance = NULL;

	if (instance == NULL)
	{
		instance = g_object_new (LATEXILA_TYPE_TEMPLATES_PERSONAL, NULL);
	}

	return instance;
}

/**
 * latexila_templates_personal_get_contents:
 * @templates: the #LatexilaTemplatesPersonal instance.
 * @path: the #GtkTreePath of a personal template.
 *
 * Gets the contents of a personal template.
 *
 * TODO load contents asynchronously.
 *
 * Returns: the personal template's contents. Free with g_free().
 */
gchar *
latexila_templates_personal_get_contents (LatexilaTemplatesPersonal *templates,
					  GtkTreePath               *path)
{
	GtkTreeIter iter;
	GFile *file;
	gchar *contents = NULL;
	GError *error = NULL;

	g_return_val_if_fail (LATEXILA_IS_TEMPLATES_PERSONAL (templates), NULL);
	g_return_val_if_fail (path != NULL, NULL);

	gtk_tree_model_get_iter (GTK_TREE_MODEL (templates),
				 &iter,
				 path);

	gtk_tree_model_get (GTK_TREE_MODEL (templates),
			    &iter,
			    LATEXILA_TEMPLATES_COLUMN_FILE, &file,
			    -1);

	g_return_val_if_fail (G_IS_FILE (file), NULL);

	g_file_load_contents (file, NULL, &contents, NULL, NULL, &error);

	if (error != NULL)
	{
		g_warning ("Error when loading personal template contents: %s", error->message);
		g_error_free (error);
	}

	g_object_unref (file);
	return contents;
}

static gboolean
save_rc_file (LatexilaTemplatesPersonal  *templates,
	      GError                    **error)
{
	GFile *rc_file;
	gchar *rc_path = NULL;
	gint personal_templates_count;
	gchar **names = NULL;
	gchar **icons = NULL;
	gchar **files = NULL;
	GtkTreeIter iter;
	gint template_num;
	GKeyFile *key_file = NULL;
	gboolean ret = TRUE;

	rc_file = get_rc_file ();
	personal_templates_count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (templates), NULL);

	if (personal_templates_count == 0)
	{
		GError *my_error = NULL;

		g_file_delete (rc_file, NULL, &my_error);

		if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
		{
			g_error_free (my_error);
			my_error = NULL;
		}
		else if (my_error != NULL)
		{
			ret = FALSE;
			g_propagate_error (error, my_error);
		}

		goto out;
	}

	names = g_new0 (gchar *, personal_templates_count + 1);
	icons = g_new0 (gchar *, personal_templates_count + 1);
	files = g_new0 (gchar *, personal_templates_count + 1);

	if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (templates), &iter))
	{
		g_assert_not_reached ();
	}

	template_num = 0;
	do
	{
		gchar *name;
		gchar *icon;
		GFile *file;

		gtk_tree_model_get (GTK_TREE_MODEL (templates), &iter,
				    LATEXILA_TEMPLATES_COLUMN_NAME, &name,
				    LATEXILA_TEMPLATES_COLUMN_CONFIG_ICON_NAME, &icon,
				    LATEXILA_TEMPLATES_COLUMN_FILE, &file,
				    -1);

		g_assert_cmpint (template_num, <, personal_templates_count);
		names[template_num] = name;
		icons[template_num] = icon;
		files[template_num] = g_file_get_basename (file);

		g_object_unref (file);
		template_num++;
	}
	while (gtk_tree_model_iter_next (GTK_TREE_MODEL (templates), &iter));

	g_assert_cmpint (template_num, ==, personal_templates_count);

	key_file = g_key_file_new ();

	g_key_file_set_string_list (key_file,
				    RC_FILE_GROUP_NAME,
				    "names",
				    (const gchar * const *) names,
				    personal_templates_count);

	g_key_file_set_string_list (key_file,
				    RC_FILE_GROUP_NAME,
				    "icons",
				    (const gchar * const *) icons,
				    personal_templates_count);

	g_key_file_set_string_list (key_file,
				    RC_FILE_GROUP_NAME,
				    "files",
				    (const gchar * const *) files,
				    personal_templates_count);

	if (!tepl_utils_create_parent_directories (rc_file, NULL, error))
	{
		ret = FALSE;
		goto out;
	}

	rc_path = g_file_get_path (rc_file);
	if (!g_key_file_save_to_file (key_file, rc_path, error))
	{
		ret = FALSE;
	}

out:
	g_object_unref (rc_file);
	g_free (rc_path);
	g_strfreev (names);
	g_strfreev (icons);
	g_strfreev (files);

	if (key_file != NULL)
	{
		g_key_file_unref (key_file);
	}

	return ret;
}

static gboolean
is_template_index_used (LatexilaTemplatesPersonal *templates,
			gint                       template_num)
{
	GtkTreeIter iter;

	if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (templates), &iter))
	{
		return FALSE;
	}

	do
	{
		GFile *file;
		gchar *basename;
		gchar *endptr;
		glong cur_template_num;
		gboolean numeric_basename;

		gtk_tree_model_get (GTK_TREE_MODEL (templates), &iter,
				    LATEXILA_TEMPLATES_COLUMN_FILE, &file,
				    -1);

		basename = g_file_get_basename (file);
		cur_template_num = strtol (basename, &endptr, 10);
		numeric_basename = endptr != basename;

		g_object_unref (file);
		g_free (basename);

		if (numeric_basename && template_num == cur_template_num)
		{
			return TRUE;
		}
	}
	while (gtk_tree_model_iter_next (GTK_TREE_MODEL (templates), &iter));

	return FALSE;
}

static gint
get_first_free_template_index (LatexilaTemplatesPersonal *templates)
{
	gint template_num;

	for (template_num = 0; template_num < G_MAXINT; template_num++)
	{
		if (!is_template_index_used (templates, template_num))
		{
			return template_num;
		}
	}

	g_return_val_if_reached (-1);
}

/**
 * latexila_templates_personal_create:
 * @templates: the #LatexilaTemplatesPersonal instance.
 * @name: the template's name.
 * @config_icon_name: the icon name that will be stored in the config file.
 * @contents: the template's contents.
 * @error: (out) (optional): a location to a %NULL #GError, or %NULL.
 *
 * Creates a new personal template. The new template is added at the end of the
 * list.
 *
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
latexila_templates_personal_create (LatexilaTemplatesPersonal  *templates,
				    const gchar                *name,
				    const gchar                *config_icon_name,
				    const gchar                *contents,
				    GError                    **error)
{
	gint template_num;
	GFile *template_file = NULL;
	GFileOutputStream *stream = NULL;
	gboolean ret = TRUE;

	g_return_val_if_fail (LATEXILA_IS_TEMPLATES_PERSONAL (templates), FALSE);
	g_return_val_if_fail (name != NULL && name[0] != '\0', FALSE);
	g_return_val_if_fail (config_icon_name != NULL && config_icon_name[0] != '\0', FALSE);
	g_return_val_if_fail (contents != NULL, FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	template_num = get_first_free_template_index (templates);
	g_return_val_if_fail (template_num >= 0, FALSE);

	template_file = get_personal_template_file_by_index (template_num);

	if (!tepl_utils_create_parent_directories (template_file, NULL, error))
	{
		ret = FALSE;
		goto out;
	}

	stream = g_file_create (template_file, G_FILE_CREATE_NONE, NULL, error);

	if (stream == NULL)
	{
		ret = FALSE;
		goto out;
	}

	if (!g_output_stream_write_all (G_OUTPUT_STREAM (stream),
					contents,
					strlen (contents),
					NULL,
					NULL,
					error))
	{
		ret = FALSE;
		goto out;
	}

	latexila_templates_add_template (GTK_LIST_STORE (templates),
					 name,
					 config_icon_name,
					 template_file);

	if (!save_rc_file (templates, error))
	{
		ret = FALSE;
	}

out:
	g_clear_object (&template_file);
	g_clear_object (&stream);
	return ret;
}

/**
 * latexila_templates_personal_delete:
 * @templates: the #LatexilaTemplatesPersonal instance.
 * @iter: a valid #GtkTreeIter.
 * @error: (out) (optional): a location to a %NULL #GError, or %NULL.
 *
 * Deletes a personal template.
 *
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
latexila_templates_personal_delete (LatexilaTemplatesPersonal  *templates,
				    GtkTreeIter                *iter,
				    GError                    **error)
{
	GFile *file = NULL;
	gboolean success = FALSE;

	g_return_val_if_fail (LATEXILA_IS_TEMPLATES_PERSONAL (templates), FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	gtk_tree_model_get (GTK_TREE_MODEL (templates),
			    iter,
			    LATEXILA_TEMPLATES_COLUMN_FILE, &file,
			    -1);

	g_return_val_if_fail (G_IS_FILE (file), FALSE);

	gtk_list_store_remove (GTK_LIST_STORE (templates), iter);

	if (!save_rc_file (templates, error))
	{
		goto out;
	}

	if (!g_file_delete (file, NULL, error))
	{
		goto out;
	}

	success = TRUE;

out:
	g_clear_object (&file);
	return success;
}

/**
 * latexila_templates_personal_move_up:
 * @templates: the #LatexilaTemplatesPersonal instance.
 * @iter: a valid #GtkTreeIter.
 * @error: (out) (optional): a location to a %NULL #GError, or %NULL.
 *
 * Moves up a personal template. There must be a previous template before @iter.
 *
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
latexila_templates_personal_move_up (LatexilaTemplatesPersonal  *templates,
				     GtkTreeIter                *iter,
				     GError                    **error)
{
	GtkTreeIter prev_iter;

	g_return_val_if_fail (LATEXILA_IS_TEMPLATES_PERSONAL (templates), FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	prev_iter = *iter;
	if (!gtk_tree_model_iter_previous (GTK_TREE_MODEL (templates), &prev_iter))
	{
		g_return_val_if_reached (FALSE);
	}

	gtk_list_store_move_before (GTK_LIST_STORE (templates),
				    iter,
				    &prev_iter);

	return save_rc_file (templates, error);
}

/**
 * latexila_templates_personal_move_down:
 * @templates: the #LatexilaTemplatesPersonal instance.
 * @iter: a valid #GtkTreeIter.
 * @error: (out) (optional): a location to a %NULL #GError, or %NULL.
 *
 * Moves down a personal template. There must be a template after @iter.
 *
 * Returns: %TRUE on success, %FALSE on error.
 */
gboolean
latexila_templates_personal_move_down (LatexilaTemplatesPersonal  *templates,
				       GtkTreeIter                *iter,
				       GError                    **error)
{
	GtkTreeIter next_iter;

	g_return_val_if_fail (LATEXILA_IS_TEMPLATES_PERSONAL (templates), FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	next_iter = *iter;
	if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (templates), &next_iter))
	{
		g_return_val_if_reached (FALSE);
	}

	gtk_list_store_move_after (GTK_LIST_STORE (templates),
				   iter,
				   &next_iter);

	return save_rc_file (templates, error);
}
