/* Copyright (C) 2010-2012 by Cristian Henzel <oss@rspwn.com>
 *
 * forked from parcellite, which is
 * Copyright (C) 2007-2008 by Xyhthyx <xyhthyx@gmail.com>
 *
 * This file is part of ClipIt.
 *
 * ClipIt 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.
 *
 * ClipIt 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/>.
 */

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

#include <glib.h>
#include <gtk/gtk.h>
#include <string.h>
#include <stdlib.h>
#include "main.h"
#include "utils.h"
#include "history.h"

GList *history;

/* Deletes duplicate item in history */
static GList *find_duplicate(gchar *item)
{
	GList *element;
	/* Go through each element compare each */
	for (element = history; element != NULL; element = element->next) {
		history_item *elem_data = element->data;
		if (g_strcmp0((gchar*)elem_data->content, item) == 0) {
			return element;
		}
	}
	return NULL;
}

static history_item *initialize_history_item()
{
	history_item *hitem = g_new0(history_item, 1);
	hitem->is_static = 0;
	return hitem;
}

/* Returns TRUE if text should be excluded from history */
static gboolean is_excluded(gchar *text)
{
	/* Open the file for reading */
	gchar *path = g_build_filename(g_get_user_data_dir(), EXCLUDES_FILE, NULL);
	FILE *excludes_file = fopen(path, "rb");
	g_free(path);
	/* Check that it opened and begin read */
	if (excludes_file)
	{
		/* Read the size of the first item */
		gint size;
		size_t fread_return;
		fread_return = fread(&size, 4, 1, excludes_file);
		/* Continue reading until size is 0 */
		while (size != 0)
		{
			/* Read Regex */
			gchar *regex = (gchar*)g_malloc(size + 1);
			fread_return = fread(regex, size, 1, excludes_file);
			regex[size] = '\0';
			fread_return = fread(&size, 4, 1, excludes_file);
			/* Append the read action */
			GRegex *regexp = g_regex_new(regex, G_REGEX_CASELESS, 0, NULL);
			gboolean result = g_regex_match(regexp, text, 0, NULL);
			g_regex_unref(regexp);
			g_free(regex);
			if(result)
				return result;
		}
		fclose(excludes_file);
	}
	return FALSE;
}

/* Reads history from DATADIR/clipit/history */
void read_history()
{
	/* Build file path */
	gchar *history_path = g_build_filename(g_get_user_data_dir(),
						HISTORY_FILE,
						NULL);

	/* Open the file for reading */
	FILE *history_file = fopen(history_path, "rb");
	g_free(history_path);
	/* Check that it opened and begin read */
	if (history_file) {
		/* Read the size of the first item */
		int size;
		size_t fread_return;
		if (fread(&size, 4, 1, history_file) != 1)
			size = 0;
		if (size == -1) {
			/* If size is -1, we are using the new filetype introduced in 1.4.1 */
			/* We currently aren't using the extra data fields */
			char extra_data[64];
			int data_type;
			fread_return = fread(&extra_data, 64, 1, history_file);
			if (fread(&size, 4, 1, history_file) != 1)
				size = 0;
			if (fread(&data_type, 4, 1, history_file) != 1)
				data_type = 0;
			while (size && data_type) {
				switch (data_type) {
					case 1: {
						gchar *item = (gchar*)g_malloc(size + 1);
						fread_return = fread(item, size, 1, history_file);
						item[size] = '\0';
						history_item *hitem = initialize_history_item();
						hitem->content = item;
						history = g_list_prepend(history, hitem);
						break;
					}
					case 2: {
						int read_static;
						history_item *hitem = history->data;
						fread_return = fread(&read_static, size, 1, history_file);
						hitem->is_static = read_static;
						break;
					}
				}
				if (fread(&size, 4, 1, history_file) != 1)
					size = 0;
				if (fread(&data_type, 4, 1, history_file) != 1)
					data_type = 0;
			}
		} else {
			/* Continue reading until size is 0 */
			while (size) {
				/* Malloc according to the size of the item */
				gchar *item = (gchar*)g_malloc(size + 1);
				/* Read item and add ending character */
				fread_return = fread(item, size, 1, history_file);
				item[size] = '\0';
				/* Prepend item and read next size */
				history_item *hitem = initialize_history_item();
				hitem->content = item;
				history = g_list_prepend(history, hitem);
				if (fread(&size, 4, 1, history_file) != 1)
					size = 0;
			}
		}
		/* Close file and reverse the history to normal */
		fclose(history_file);
		history = g_list_reverse(history);
	}
}

/* Saves history to DATADIR/clipit/history */
void save_history()
{
	if(prefs.save_history) {
		/* Check that the directory is available */
		check_dirs();
		/* Build file path */
		gchar *history_path = g_build_filename(g_get_user_data_dir(),
							HISTORY_FILE,
							NULL);
		/* Open the file for writing */
		FILE *history_file = fopen(history_path, "wb");
		g_free(history_path);
		/* Check that it opened and begin write */
		if (history_file) {
			GList *element;
			/* Write each element to a binary file */
			int i;
			for (i=1; i<=17; i++) {
				int write_val = -1;
				fwrite(&write_val, 4, 1, history_file);
			}
			for (element = history; element != NULL; element = element->next) {
				/* Create new GString from element data, write its
				 * length (size) to file followed by the element
				 * data itself
				 */
				int write_val = 1;
				history_item *elem_data = element->data;
				GString *item = g_string_new((gchar*)elem_data->content);
				int write_static = elem_data->is_static;
				fwrite(&(item->len), 4, 1, history_file);
				fwrite(&write_val, 4, 1, history_file);
				fputs(item->str, history_file);
				write_val = 4;
				fwrite(&write_val, 4, 1, history_file);
				write_val = 2;
				fwrite(&write_val, 4, 1, history_file);
				fwrite(&write_static, 4, 1, history_file);
				g_string_free(item, TRUE);
			}
			/* Write 0 to indicate end of file */
			gint end = 0;
			fwrite(&end, 4, 1, history_file);
			fwrite(&end, 4, 1, history_file);
			fclose(history_file);
		}
	}
	/* Refresh indicator menu. Temporary solution until
	 * getting the visible status of the menu is supported by the API
	 */
#ifdef HAVE_APPINDICATOR
	create_app_indicator(0);
#endif
}

/* Checks if item should be included in history and calls append */
void check_and_append(gchar *item)
{
	if (item) {
		/* if item is too big, we don't include it in the history */
		if(strlen(item) > ENTRY_MAX_SIZE)
			return;
		GtkClipboard *clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
		/* Check if we have URIs */
		if((gtk_clipboard_wait_for_uris(clip) != NULL) && !prefs.save_uris)
			/* We have URIs but the user doesn't want to save them */
			return;
		if(is_excluded(item))
			/* This item is excluded from the history */
			return;
		if (prefs.hyperlinks_only && !is_hyperlink(item))
			/* User only wants hyperlinks, but this isn't one */
			return;

		GList *duplicate_elem = find_duplicate(item);
		if (duplicate_elem) {
			history = g_list_remove_link(history, duplicate_elem);
			history = g_list_concat(duplicate_elem, history);
		} else {
			append_item(item);
		}
	}
}

/* Adds item to the end of history */
void append_item(gchar *item)
{
	history_item *hitem = initialize_history_item();
	hitem->content = g_strdup(item);
	history = g_list_prepend(history, hitem);
	truncate_history();
}

/* Truncates history to history_limit items */
void truncate_history()
{
	if (history) {
		/* Shorten history if necessary */
		GList *last_element = g_list_nth (history, prefs.history_limit);
		while (last_element) {
			history_item *elem_data = last_element->data;
			last_element = last_element->next;
			if (elem_data->is_static == 0)
				history = g_list_remove(history, elem_data);
		}

		last_element = g_list_nth (history, prefs.history_limit - 1);
		/* last_element->next checks if the list is longer than the user
		 * wants it; last_element->prev checks if this isn't the first
		 * item, which we wouldn't want to delete */
		if (last_element) {
			while (last_element->next && last_element->prev) {
				history_item *elem_data = last_element->data;

				while (elem_data->is_static && last_element->prev) {
					if (last_element->prev->prev)
						last_element = last_element->prev;
					elem_data = last_element->data;
				}
				if (elem_data->is_static == 0)
					history = g_list_remove(history, elem_data);
				last_element = g_list_nth (history, prefs.history_limit - 1);
			}
		}
		/* Save changes */
		save_history();
	}
}

/* Returns pointer to last item in history */
gpointer get_last_item()
{
	if (history) {
		history_item *elem_data = history->data;
		if (elem_data->content) {
			/* Return the last element */
			gpointer last_item = elem_data->content;
			return last_item;
		}
		return NULL;
	}
	return NULL;
}

/* Deletes duplicate item in history */
void delete_duplicate(gchar *item)
{
	GList *element;
	/* Go through each element compare each */
	for (element = history; element != NULL; element = element->next) {
		history_item *elem_data = element->data;
		if (g_strcmp0((gchar*)elem_data->content, item) == 0) {
			g_free(elem_data->content);
			history = g_list_delete_link(history, element);
			break;
		}
	}
}
