/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Authors: 
 *   Christopher James Lahey <clahey@ximian.com>
 *
 * Copyright (C) 2002 Ximian, Inc.
 */

#include <config.h>
#include "gal-a11y-e-text.h"
#include "gal-a11y-util.h"
#include <atk/atkobject.h>
#include <atk/atktable.h>
#include <atk/atkcomponent.h>
#include <atk/atkobjectfactory.h>
#include <atk/atkregistry.h>
#include <atk/atkgobjectaccessible.h>
#include "gal/e-text/e-text.h"
#include <gtk/gtkmain.h>

#define CS_CLASS(a11y) (G_TYPE_INSTANCE_GET_CLASS ((a11y), C_TYPE_STREAM, GalA11yETextClass))
static GObjectClass *parent_class;
static AtkComponentIface *component_parent_iface;
static GType parent_type;
static gint priv_offset;
static GQuark		quark_accessible_object = 0;
#define GET_PRIVATE(object) ((GalA11yETextPrivate *) (((char *) object) + priv_offset))
#define PARENT_TYPE (parent_type)

struct _GalA11yETextPrivate {
	int dummy;
};

static void
et_dispose (GObject *object)
{
	if (parent_class->dispose)
		parent_class->dispose (object);
}

/* Static functions */

static void
et_get_extents (AtkComponent *component,
		gint *x,
		gint *y,
		gint *width,
		gint *height,
		AtkCoordType coord_type)
{
	EText *item = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (component)));
	double real_width;
	double real_height;
	int fake_width;
	int fake_height;

	if (component_parent_iface &&
	    component_parent_iface->get_extents)
		component_parent_iface->get_extents (component,
						     x,
						     y,
						     &fake_width,
						     &fake_height,
						     coord_type);

	gtk_object_get (GTK_OBJECT (item),
			"text_width", &real_width,
			"text_height", &real_height,
			NULL);

	if (width)
		*width = real_width;
	if (height) 
		*height = real_height;
}

static const gchar *
et_get_full_text (AtkText *text)
{
	EText *etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
	ETextModel *model;
	const char *full_text;

	gtk_object_get (GTK_OBJECT (etext),
			"model", &model,
			NULL);

	full_text = e_text_model_get_text (model);

	return full_text;
}

static void
et_set_full_text (AtkEditableText *text,
		  const char *full_text)
{
	EText *etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
	ETextModel *model;

	gtk_object_get (GTK_OBJECT (etext),
			"model", &model,
			NULL);

	e_text_model_set_text (model, full_text);
}

static gchar *
et_get_text (AtkText *text,
	     gint start_offset,
	     gint end_offset)
{
	const char *full_text = et_get_full_text (text);

	if (end_offset == -1)
		end_offset = strlen (full_text);
	else
		end_offset = g_utf8_offset_to_pointer (full_text, end_offset) - full_text;

	start_offset = g_utf8_offset_to_pointer (full_text, start_offset) - full_text;

	return g_strndup (full_text + start_offset, end_offset - start_offset);
}

static gchar *
et_get_text_after_offset (AtkText *text,
			  gint offset,
			  AtkTextBoundary boundary_type,
			  gint *start_offset,
			  gint *end_offset)
{
	/* Unimplemented */
	return NULL;
}

static gchar *
et_get_text_at_offset (AtkText *text,
		       gint offset,
		       AtkTextBoundary boundary_type,
		       gint *start_offset,
		       gint *end_offset)
{
	/* Unimplemented */
	return NULL;
}

static gunichar
et_get_character_at_offset (AtkText *text,
			    gint offset)
{
	const char *full_text = et_get_full_text (text);
	char *at_offset;

	at_offset = g_utf8_offset_to_pointer (full_text, offset);
	return g_utf8_get_char_validated (at_offset, -1);
}


static gchar*
et_get_text_before_offset (AtkText *text,
			   gint offset,
			   AtkTextBoundary boundary_type,
			   gint *start_offset,
			   gint *end_offset)
{
	/* Unimplemented */
	return NULL;
}


static gint
et_get_caret_offset (AtkText *text)
{
	EText *etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
	const char *full_text = et_get_full_text (text);
	int offset;

	gtk_object_get (GTK_OBJECT (etext),
			"cursor_pos", &offset,
			NULL);
	offset = g_utf8_pointer_to_offset (full_text, full_text + offset);
	return offset;
}


static AtkAttributeSet*
et_get_run_attributes (AtkText *text,
		       gint offset,
		       gint *start_offset,
		       gint *end_offset)
{
	/* Unimplemented */
	return NULL;
}


static AtkAttributeSet*
et_get_default_attributes (AtkText *text)
{
	/* Unimplemented */
	return NULL;
}


static void
et_get_character_extents (AtkText *text,
			  gint offset,
			  gint *x,
			  gint *y,
			  gint *width,
			  gint *height,
			  AtkCoordType coords)
{
	/* Unimplemented */
}


static gint
et_get_character_count (AtkText *text)
{
	const char *full_text = et_get_full_text (text);

	return g_utf8_strlen (full_text, -1);
}


static gint
et_get_offset_at_point (AtkText *text,
			gint x,
			gint y,
			AtkCoordType coords)
{
	/* Unimplemented */
	return 0;
}


static gint 
et_get_n_selections (AtkText *text)
{
	EText *etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
	if (etext->selection_start !=
	    etext->selection_end)
		return 1;
	return 0;
}


static gchar*
et_get_selection (AtkText *text,
		  gint selection_num,
		  gint *start_offset,
		  gint *end_offset)
{
	EText *etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
	if (selection_num == 0 &&
	    etext->selection_start != etext->selection_end) {
		const char *full_text = et_get_full_text (text);

		if (start_offset)
			*start_offset = g_utf8_pointer_to_offset (full_text, full_text + etext->selection_start);
		if (end_offset)
			*end_offset = g_utf8_pointer_to_offset (full_text, full_text + etext->selection_end);

		return g_strndup (full_text + etext->selection_start, etext->selection_end - etext->selection_start);
	}
	return NULL;
}


static gboolean
et_add_selection (AtkText *text,
		  gint start_offset,
		  gint end_offset)
{
	EText *etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
	if (etext->selection_start == etext->selection_end &&
	    start_offset != end_offset) {
		ETextEventProcessorCommand command;
		const char *full_text = et_get_full_text (text);
		ETextEventProcessor *tep;

		start_offset = g_utf8_offset_to_pointer (full_text, start_offset) - full_text;
		end_offset = g_utf8_offset_to_pointer (full_text, end_offset) - full_text;

		gtk_object_get (GTK_OBJECT (etext),
				"tep", &tep,
				NULL);

		command.time = gtk_get_current_event_time ();

		command.action = E_TEP_MOVE;
		command.position = E_TEP_VALUE;
		command.value = start_offset;
		g_signal_emit_by_name (tep, "command", 0, &command);

		command.action = E_TEP_SELECT;
		command.value = end_offset;
		g_signal_emit_by_name (tep, "command", 0, &command);
		return TRUE;
	}
	return FALSE;
}


static gboolean
et_remove_selection (AtkText *text,
		     gint selection_num)
{
	/* Unimplemented */
	return FALSE;
}


static gboolean
et_set_selection (AtkText *text,
		  gint selection_num,
		  gint start_offset,
		  gint end_offset)
{
	/* Unimplemented */
	return FALSE;
}


static gboolean
et_set_caret_offset (AtkText *text,
		     gint offset)
{
	EText *etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)));
	const char *full_text = et_get_full_text (text);

	offset = g_utf8_offset_to_pointer (full_text, offset) - full_text;
	gtk_object_set (GTK_OBJECT (etext),
			"cursor_pos", &offset,
			NULL);
	return TRUE;
}

static gboolean
et_set_run_attributes (AtkEditableText *text,
		       AtkAttributeSet *attrib_set,
		       gint start_offset,
		       gint end_offset)
{
	/* Unimplemented */
	return FALSE;
}

static void
et_set_text_contents (AtkEditableText *text,
		      const gchar *string)
{
	et_set_full_text (text, string);
}

static void
et_insert_text (AtkEditableText *text,
		const gchar *string,
		gint length,
		gint *position)
{
	/* Utf8 unimplemented */

	const char *full_text = et_get_full_text (ATK_TEXT (text));
	char *result = g_strdup_printf ("%.*s%.*s%s", *position, full_text, length, string, full_text + *position);

	et_set_full_text (text, result);

	*position += length;

	g_free (result);
}

static void
et_copy_text (AtkEditableText *text,
	      gint start_pos,
	      gint end_pos)
{
	/* Unimplemented */
}

static void
et_cut_text (AtkEditableText *text,
	     gint start_pos,
	     gint end_pos)
{
	/* Unimplemented */
}

static void
et_delete_text (AtkEditableText *text,
		gint start_pos,
		gint end_pos)
{
	/* Unimplemented */
}

static void
et_paste_text (AtkEditableText *text,
	       gint position)
{
	/* Unimplemented */
}


static void
et_atk_component_iface_init (AtkComponentIface *iface)
{
	iface->get_extents = et_get_extents;
}

static void
et_atk_text_iface_init (AtkTextIface *iface)
{
	iface->get_text                = et_get_text;
	iface->get_text_after_offset   = et_get_text_after_offset;
	iface->get_text_at_offset      = et_get_text_at_offset;
	iface->get_character_at_offset = et_get_character_at_offset;
	iface->get_text_before_offset  = et_get_text_before_offset;
	iface->get_caret_offset        = et_get_caret_offset;
	iface->get_run_attributes      = et_get_run_attributes;
	iface->get_default_attributes  = et_get_default_attributes;
	iface->get_character_extents   = et_get_character_extents;
	iface->get_character_count     = et_get_character_count;
	iface->get_offset_at_point     = et_get_offset_at_point;
	iface->get_n_selections        = et_get_n_selections;
	iface->get_selection           = et_get_selection;
	iface->add_selection           = et_add_selection;
	iface->remove_selection        = et_remove_selection;
	iface->set_selection           = et_set_selection;
	iface->set_caret_offset        = et_set_caret_offset;
}

static void
et_atk_editable_text_iface_init (AtkEditableTextIface *iface)
{
	iface->set_run_attributes = et_set_run_attributes;
	iface->set_text_contents  = et_set_text_contents;
	iface->insert_text        = et_insert_text;
	iface->copy_text          = et_copy_text;
	iface->cut_text           = et_cut_text;
	iface->delete_text        = et_delete_text;
	iface->paste_text         = et_paste_text;
}

static void
et_class_init (GalA11yETextClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	quark_accessible_object               = g_quark_from_static_string ("gtk-accessible-object");

	parent_class                          = g_type_class_ref (PARENT_TYPE);

	object_class->dispose                 = et_dispose;
}

static void
et_init (GalA11yEText *a11y)
{
#if 0
	GalA11yETextPrivate *priv;

	priv = GET_PRIVATE (a11y);
#endif
}

/**
 * gal_a11y_e_text_get_type:
 * @void: 
 * 
 * Registers the &GalA11yEText class if necessary, and returns the type ID
 * associated to it.
 * 
 * Return value: The type ID of the &GalA11yEText class.
 **/
GType
gal_a11y_e_text_get_type (void)
{
	static GType type = 0;

	if (!type) {
		AtkObjectFactory *factory;

		GTypeInfo info = {
			sizeof (GalA11yETextClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) et_class_init,
			(GClassFinalizeFunc) NULL,
			NULL, /* class_data */
			sizeof (GalA11yEText),
			0,
			(GInstanceInitFunc) et_init,
			NULL /* value_text */
		};

		static const GInterfaceInfo atk_component_info = {
			(GInterfaceInitFunc) et_atk_component_iface_init,
			(GInterfaceFinalizeFunc) NULL,
			NULL
		};
		static const GInterfaceInfo atk_text_info = {
			(GInterfaceInitFunc) et_atk_text_iface_init,
			(GInterfaceFinalizeFunc) NULL,
			NULL
		};
		static const GInterfaceInfo atk_editable_text_info = {
			(GInterfaceInitFunc) et_atk_editable_text_iface_init,
			(GInterfaceFinalizeFunc) NULL,
			NULL
		};

		factory = atk_registry_get_factory (atk_get_default_registry (), GNOME_TYPE_CANVAS_ITEM);
		parent_type = atk_object_factory_get_accessible_type (factory);

		type = gal_a11y_type_register_static_with_private (PARENT_TYPE, "GalA11yEText", &info, 0,
								   sizeof (GalA11yETextPrivate), &priv_offset);

		g_type_add_interface_static (type, ATK_TYPE_COMPONENT, &atk_component_info);
		g_type_add_interface_static (type, ATK_TYPE_TEXT, &atk_text_info);
		g_type_add_interface_static (type, ATK_TYPE_EDITABLE_TEXT, &atk_editable_text_info);
	}

	return type;
}