/*
 * This file is part of gspell, a spell-checking library.
 *
 * Copyright 2016 - Sébastien Wilmet
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

#include "gspell-entry-utils.h"
#include <string.h>
#include "gspell-utils.h"

GspellEntryWord *
_gspell_entry_word_new (void)
{
	return g_new0 (GspellEntryWord, 1);
}

void
_gspell_entry_word_free (gpointer data)
{
	GspellEntryWord *word = data;

	if (word != NULL)
	{
		g_free (word->word_str);
		g_free (word);
	}
}

static void
improve_word_boundaries (const gchar  *text,
			 PangoLogAttr *log_attrs,
			 gint          n_attrs)
{
	const gchar *cur_text_pos;
	gint attr_num;

	attr_num = 0;
	cur_text_pos = text;

	while (attr_num < n_attrs)
	{
		PangoLogAttr *log_attr_before;
		gunichar ch;
		PangoLogAttr *log_attr_after;

		if (cur_text_pos == NULL ||
		    *cur_text_pos == '\0')
		{
			if (attr_num != n_attrs - 1)
			{
				g_warning ("%s(): problem in loop iteration, attr_num=%d but should be %d.",
					   G_STRFUNC,
					   attr_num,
					   n_attrs - 1);
			}

			break;
		}

		g_assert_cmpint (attr_num + 1, <, n_attrs);

		/* ch is between log_attr_before and log_attr_after. */
		log_attr_before = log_attrs + attr_num;
		ch = g_utf8_get_char (cur_text_pos);
		log_attr_after = log_attr_before + 1;

		/* Same algo as in gspell-text-iter.c. */
		if (_gspell_utils_is_apostrophe_or_dash (ch) &&
		    log_attr_before->is_word_end &&
		    log_attr_after->is_word_start)
		{
			log_attr_before->is_word_end = FALSE;
			log_attr_after->is_word_start = FALSE;
		}

		attr_num++;
		cur_text_pos = g_utf8_find_next_char (cur_text_pos, NULL);
	}
}

/* Without the preedit string.
 * Free @log_attrs with g_free().
 */
static void
get_pango_log_attrs (GtkEntry      *entry,
		     PangoLogAttr **log_attrs,
		     gint          *n_attrs)
{
	GtkEntryBuffer *buffer;
	const gchar *text;

	g_assert (log_attrs != NULL);
	g_assert (n_attrs != NULL);

	buffer = gtk_entry_get_buffer (entry);
	text = gtk_entry_buffer_get_text (buffer);

	*n_attrs = gtk_entry_buffer_get_length (buffer) + 1;
	*log_attrs = g_new0 (PangoLogAttr, *n_attrs);

	pango_get_log_attrs (text,
			     gtk_entry_buffer_get_bytes (buffer),
			     -1,
			     NULL,
			     *log_attrs,
			     *n_attrs);

	improve_word_boundaries (text, *log_attrs, *n_attrs);
}

/* Returns: (transfer full) (element-type GspellEntryWord): the list of words in
 * @entry, without the preedit string. Free with
 * g_slist_free_full (words, _gspell_entry_word_free);
 *
 * The preedit string is not included, because the current word being typed
 * should not be marked as misspelled, so it doesn't change whether the preedit
 * string is included or not, and the code is simpler without.
 */
GSList *
_gspell_entry_utils_get_words (GtkEntry *entry)
{
	const gchar *text;
	const gchar *cur_text_pos;
	const gchar *word_start;
	gint word_start_char_pos;
	PangoLogAttr *attrs;
	gint n_attrs;
	gint attr_num;
	GSList *list = NULL;

	g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL);

	text = gtk_entry_get_text (entry);
	get_pango_log_attrs (entry, &attrs, &n_attrs);

	attr_num = 0;
	cur_text_pos = text;
	word_start = NULL;
	word_start_char_pos = 0;

	while (attr_num < n_attrs)
	{
		if (word_start != NULL &&
		    attrs[attr_num].is_word_end)
		{
			const gchar *word_end;
			GspellEntryWord *word;

			if (cur_text_pos != NULL)
			{
				word_end = cur_text_pos;
			}
			else
			{
				word_end = word_start + strlen (word_start);
			}

			word = _gspell_entry_word_new ();
			word->byte_start = word_start - text;
			word->byte_end = word_end - text;
			word->char_start = word_start_char_pos;
			word->char_end = attr_num;
			word->word_str = g_strndup (word_start, word_end - word_start);

			list = g_slist_prepend (list, word);

			/* Find next word start. */
			word_start = NULL;
		}

		if (word_start == NULL &&
		    attrs[attr_num].is_word_start)
		{
			word_start = cur_text_pos;
			word_start_char_pos = attr_num;
		}

		if (cur_text_pos == NULL &&
		    attr_num != n_attrs - 1)
		{
			g_warning ("%s(): problem in loop iteration, attr_num=%d but should be %d.",
				   G_STRFUNC,
				   attr_num,
				   n_attrs - 1);
			break;
		}

		attr_num++;
		cur_text_pos = g_utf8_find_next_char (cur_text_pos, NULL);
	}

	g_free (attrs);
	return g_slist_reverse (list);
}

static gint
get_layout_index (GtkEntry *entry,
		  gint      x)
{
	PangoLayout *layout;
	PangoLayoutLine *line;
	gint layout_index; /* in bytes */
	gint trailing_chars;
	const gchar *layout_text;
	const gchar *pos_in_layout_text;
	gint layout_text_byte_length;
	gint max_trailing_chars;

	layout = gtk_entry_get_layout (entry);
	line = pango_layout_get_line_readonly (layout, 0);

	pango_layout_line_x_to_index (line,
				      x * PANGO_SCALE,
				      &layout_index,
				      &trailing_chars);

	layout_text = pango_layout_get_text (layout);

	/* Performance should not be a problem here, it's better too much
	 * security than too few.
	 */
	layout_text_byte_length = strlen (layout_text);
	if (layout_index >= layout_text_byte_length)
	{
		return layout_text_byte_length;
	}

	if (trailing_chars == 0)
	{
		return layout_index;
	}

	pos_in_layout_text = layout_text + layout_index;
	max_trailing_chars = g_utf8_strlen (pos_in_layout_text, -1);
	trailing_chars = MIN (trailing_chars, max_trailing_chars);

	pos_in_layout_text = g_utf8_offset_to_pointer (pos_in_layout_text, trailing_chars);

	return pos_in_layout_text - layout_text;
}

/* The return value is in characters, not bytes. And a position suitable for the
 * text in the GtkEntryBuffer, i.e. without the preedit string.
 */
gint
_gspell_entry_utils_get_char_position_at_event (GtkEntry       *entry,
						GdkEventButton *event)
{
	gint scroll_offset;
	gint x;
	gint layout_index; /* in bytes */
	gint text_index; /* in bytes */
	const gchar *buffer_text;

	g_object_get (entry,
		      "scroll-offset", &scroll_offset,
		      NULL);

	x = event->x + scroll_offset;

	layout_index = get_layout_index (entry, x);
	text_index = gtk_entry_layout_index_to_text_index (entry, layout_index);

	buffer_text = gtk_entry_get_text (entry);
	return g_utf8_pointer_to_offset (buffer_text, buffer_text + text_index);
}

/* ex:set ts=8 noet: */
