/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* This file is part of the GtkHTML library
 *
 * Copyright (C) 2000 Helix Code, Inc.
 * Authors:           Radek Doulik (rodo@helixcode.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include <config.h>
#include "htmlcursor.h"
#include "htmlengine.h"
#include "htmlinterval.h"
#include "htmlobject.h"
#include "htmlselection.h"

inline void
html_point_construct (HTMLPoint *p,
                      HTMLObject *o,
                      guint off)
{
	p->object = o;
	p->offset = off;
}

HTMLPoint *
html_point_new (HTMLObject *o,
                guint off)
{
	HTMLPoint *np = g_new (HTMLPoint, 1);
	html_point_construct (np, o, off);

	return np;
}

inline void
html_point_destroy (HTMLPoint *p)
{
	g_free (p);
}

gboolean
html_point_cursor_object_eq (HTMLPoint *p,
                             HTMLPoint *c)
{
	return p->object == c->object && (!html_object_is_container (p->object) || p->offset == c->offset);
}

inline void
html_point_next_cursor (HTMLPoint *p)
{
	p->object = html_object_next_cursor (p->object, (gint *) &p->offset);
}

HTMLInterval *
html_interval_new (HTMLObject *from,
                   HTMLObject *to,
                   guint from_offset,
                   guint to_offset)
{
	HTMLInterval *i = g_new (HTMLInterval, 1);

	html_point_construct (&i->from, from, from_offset);
	html_point_construct (&i->to,   to,   to_offset);

	return i;
}

inline HTMLInterval *
html_interval_new_from_cursor (HTMLCursor *a,
                               HTMLCursor *b)
{
	HTMLCursor *begin, *end;

	if (html_cursor_get_position (a) < html_cursor_get_position (b)) {
		begin = a;
		end   = b;
	} else {
		begin = b;
		end   = a;
	}

	return html_interval_new (begin->object, end->object, begin->offset, end->offset);
}

inline HTMLInterval *
html_interval_new_from_points (HTMLPoint *from,
                               HTMLPoint *to)
{
	return html_interval_new (from->object, to->object, from->offset, to->offset);
}

void
html_interval_destroy (HTMLInterval *i)
{
	g_free (i);
}

guint
html_interval_get_length (HTMLInterval *i,
                          HTMLObject *obj)
{
	if (obj != i->from.object && obj != i->to.object)
		return html_object_get_length (obj);
	if (obj == i->from.object) {
		if (obj == i->to.object)
			return i->to.offset - i->from.offset;
		else
			return html_object_get_length (obj) - i->from.offset;
	} else
		return i->to.offset;
}

guint
html_interval_get_bytes (HTMLInterval *i,
                         HTMLObject *obj)
{
	if (obj != i->from.object && obj != i->to.object)
		return html_object_get_bytes (obj);
	if (obj == i->from.object) {
		if (obj == i->to.object)
			return html_interval_get_to_index (i) - html_interval_get_from_index (i);
		else
			return html_object_get_bytes (obj) - html_interval_get_from_index (i);
	} else
		return html_interval_get_to_index (i);
}

guint
html_interval_get_start (HTMLInterval *i,
                         HTMLObject *obj)
{
	return (obj != i->from.object) ? 0 : i->from.offset;
}

guint
html_interval_get_start_index (HTMLInterval *i,
                               HTMLObject *obj)
{
	return (obj != i->from.object) ? 0 : html_interval_get_from_index (i);
}

static void
select_object (HTMLObject *o,
               HTMLEngine *e,
               gpointer data)
{
	HTMLInterval *i = (HTMLInterval *) data;
	HTMLEngine *etop = html_engine_get_top_html_engine (e);

	if (o == i->from.object)
		etop->selected_in = TRUE;
	if (etop->selected_in) {
		gint len;

		len = html_interval_get_length (i, o);
		if (len || html_object_is_container (o))
			html_object_select_range (o, e,
						  html_interval_get_start (i, o), len,
						  !html_engine_frozen (e));
	}

	if (o == i->to.object)
		etop->selected_in = FALSE;
}

void
html_interval_select (HTMLInterval *i,
                      HTMLEngine *e)
{
	html_engine_get_top_html_engine (e)->selected_in = FALSE;
	i = html_interval_flat (i);
	html_interval_forall (i, e, select_object, i);
	html_interval_destroy (i);
}

static void
unselect_object (HTMLObject *o,
                 HTMLEngine *e,
                 gpointer data)
{
	if (html_interval_get_length ((HTMLInterval *) data, o))
		html_object_select_range (o, e, 0, 0, !html_engine_frozen (e));
}

void
html_interval_unselect (HTMLInterval *i,
                        HTMLEngine *e)
{
	i = html_interval_flat (i);
	html_interval_forall (i, e, unselect_object, i);
	html_interval_destroy (i);
}

gint
html_interval_get_from_index (HTMLInterval *i)
{
	g_assert (i);

	return html_object_get_index (i->from.object, i->from.offset);
}

gint
html_interval_get_to_index (HTMLInterval *i)
{
	g_assert (i);

	return html_object_get_index (i->to.object, i->to.offset);
}

static GSList *
get_downtree_line (HTMLObject *o)
{
	GSList *list = NULL;

	while (o) {
		list = g_slist_prepend (list, o);
		o = o->parent;
	}

	return list;
}

static HTMLEngine *
do_downtree_lines_intersection (GSList **l1,
                                GSList **l2,
                                HTMLEngine *e)
{
	GSList *link;

	g_assert ((*l1)->data == (*l2)->data);

	while (*l1 && *l2 && (*l1)->data == (*l2)->data) {
		e = html_object_get_engine (HTML_OBJECT ((*l1)->data), e);
		link = *l1;
		*l1 = g_slist_remove_link (*l1, link);
		g_slist_free (link);

		link = *l2;
		*l2 = g_slist_remove_link (*l2, link);
		g_slist_free (link);
	}

	return e;
}

static void
interval_forall (HTMLObject *parent,
                 GSList *from_down,
                 GSList *to_down,
                 HTMLEngine *e,
                 HTMLObjectForallFunc f,
                 gpointer data)
{
	HTMLObject *o, *from, *to;

	from = from_down ? HTML_OBJECT (from_down->data) : html_object_head (parent);
	to   = to_down   ? HTML_OBJECT (to_down->data)   : NULL;

	for (o = from; o; o = html_object_next_not_slave (o)) {
		interval_forall (o,
				 (from_down && o == HTML_OBJECT (from_down->data)) ? from_down->next : NULL,
				 (to_down   && o == HTML_OBJECT (to_down->data))   ? to_down->next   : NULL,
				 html_object_get_engine (o, e), f, data);
		if (o == to)
			break;
	}
	(*f) (parent, e, data);
}

static void
html_point_to_leaf (HTMLPoint *p)
{
	if (html_object_is_container (p->object)) {
		if (p->offset == 0)
			p->object = html_object_get_head_leaf (p->object);
		else if (p->offset == html_object_get_length (p->object)) {
			p->object = html_object_get_tail_leaf (p->object);
			p->offset = html_object_get_length (p->object);
		} else
			g_warning ("Can't transform point to leaf\n");
	}
}

static inline HTMLInterval *
html_interval_dup (HTMLInterval *i)
{
	return html_interval_new_from_points (&i->from, &i->to);
}

HTMLInterval *
html_interval_flat (HTMLInterval *i)
{
	HTMLInterval *ni = html_interval_dup (i);

	html_point_to_leaf (&ni->from);
	html_point_to_leaf (&ni->to);

	return ni;
}

void
html_interval_forall (HTMLInterval *i,
                      HTMLEngine *e,
                      HTMLObjectForallFunc f,
                      gpointer data)
{
	GSList *from_downline, *to_downline;
	HTMLEngine *engine;

	g_return_if_fail (i->from.object);
	g_return_if_fail (i->to.object);

	i = html_interval_flat (i);

	from_downline = get_downtree_line (i->from.object);
	to_downline   = get_downtree_line (i->to.object);
	engine = do_downtree_lines_intersection  (&from_downline, &to_downline, e);

	if (from_downline)
		interval_forall    (HTML_OBJECT (from_downline->data)->parent, from_downline, to_downline,
				    html_object_get_engine (HTML_OBJECT (from_downline->data)->parent, engine), f, data);
	else {
		g_assert (i->from.object == i->to.object);
		html_object_forall (i->from.object, html_object_get_engine (i->from.object, engine), f, data);
	}

	g_slist_free (from_downline);
	g_slist_free (to_downline);
	html_interval_destroy (i);
}

static HTMLObject *
html_object_children_max (HTMLObject *a,
                          HTMLObject *b)
{
	HTMLObject *o;

	g_return_val_if_fail (a->parent, NULL);
	g_return_val_if_fail (b->parent, NULL);
	g_return_val_if_fail (a->parent == b->parent, NULL);

	for (o = a; o; o = html_object_next_not_slave (o))
		if (o == b)
			return b;
	return a;
}

HTMLPoint *
html_point_max (HTMLPoint *a,
                HTMLPoint *b)
{
	GSList *a_downline, *b_downline;
	HTMLPoint *rv = NULL;

	if (a->object == b->object)
		return a->offset < b->offset ? b : a;

	a_downline = get_downtree_line (a->object);
	b_downline = get_downtree_line (b->object);
	do_downtree_lines_intersection (&a_downline, &b_downline, NULL);

	if (a_downline == NULL)
		/* it means that a is parent (container) of b */
		rv = a->offset ? a : b;
	else if (b_downline == NULL)
		/* it means that b is parent (container) of a */
		rv = b->offset ? b : a;
	else
		rv = html_object_children_max (HTML_OBJECT (a_downline->data), HTML_OBJECT (b_downline->data))
			== HTML_OBJECT (a_downline->data) ? a : b;
	g_slist_free (a_downline);
	g_slist_free (b_downline);

	return rv;
}

inline HTMLPoint *
html_point_min (HTMLPoint *a,
                HTMLPoint *b)
{
	return a == html_point_max (a, b) ? b : a;
}

static HTMLPoint *
max_from (HTMLInterval *a,
          HTMLInterval *b)
{
	if (!a->from.object)
		return &b->from;
	if (!b->from.object)
		return &a->from;

	return html_point_max (&a->from, &b->from);
}

static HTMLPoint *
min_to (HTMLInterval *a,
        HTMLInterval *b)
{
	if (!a->to.object)
		return &b->to;
	if (!b->to.object)
		return &a->to;

	return html_point_min (&a->to, &b->to);
}

HTMLInterval *
html_interval_intersection (HTMLInterval *a,
                            HTMLInterval *b)
{
	HTMLPoint *from, *to;

	from = max_from (a, b);
	to   = min_to   (a, b);

	return to == html_point_max (from, to) ?
		html_interval_new_from_points (from, to) : NULL;
}

gpointer
html_interval_substract (HTMLInterval *a,
                         HTMLInterval *b,
                         HTMLInterval **s1,
                         HTMLInterval **s2)
{
	return NULL;
}

void
html_interval_validate (HTMLInterval *i)
{
	if (&i->from == html_point_max (&i->from, &i->to)) {
		HTMLPoint tmp;

		tmp     = i->from;
		i->from = i->to;
		i->to   = tmp;
	}
}

gboolean
html_point_eq (const HTMLPoint *a,
               const HTMLPoint *b)
{
	return a->object == b->object && a->offset == b->offset;
}

gboolean
html_interval_eq (const HTMLInterval *a,
                  const HTMLInterval *b)
{
	return html_point_eq (&a->from, &b->from) && html_point_eq (&a->to, &b->to);
}

HTMLObject *
html_interval_get_head (HTMLInterval *i,
                        HTMLObject *o)
{
	return o == i->from.object->parent ? i->from.object : html_object_head (o);
}
