/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2003 Takuro Ashie
 *
 *  This program 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 2, or (at your option)
 *  any later version.
 *
 *  This program 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "kz-gesture.h"

#include <stdlib.h>
#include <string.h>
#include <ctype.h>

enum {
   START_SIGNAL,
   CANCEL_SIGNAL,
   STACK_MOTION_SIGNAL,
   PERFORM_SIGNAL,
   LAST_SIGNAL
};

static void dispose      (GObject *object);

/* KzGesture class signals */
static void kz_gesture_stack_motion (KzGesture *gesture,
				     KzGestureMotion motion);
static void kz_gesture_real_perform (KzGesture *gesture);

static void kz_gesture_reset        (KzGesture *gesture);
static KzGestureItem *kz_gesture_search_matched_item (KzGesture *gesture);
static void kz_gesture_item_destroy (KzGestureItem *item);

static gint kz_gesture_signals[LAST_SIGNAL] = {0};

G_DEFINE_TYPE(KzGesture, kz_gesture, G_TYPE_OBJECT)

static void
kz_gesture_class_init (KzGestureClass *klass)
{
	GObjectClass *object_class;

	object_class = (GObjectClass *) klass;

	object_class->dispose = dispose;

	klass->start        = NULL;
	klass->cancel       = NULL;
	klass->stack_motion = kz_gesture_stack_motion;
	klass->perform      = kz_gesture_real_perform;

	kz_gesture_signals[START_SIGNAL]
		= g_signal_new ("start",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzGestureClass, start),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);

	kz_gesture_signals[CANCEL_SIGNAL]
		= g_signal_new ("cancel",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzGestureClass, cancel),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);

	kz_gesture_signals[STACK_MOTION_SIGNAL]
		= g_signal_new ("stack-motion",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzGestureClass, stack_motion),
				NULL, NULL,
				g_cclosure_marshal_VOID__CHAR,
				G_TYPE_NONE, 1, G_TYPE_CHAR);

	kz_gesture_signals[PERFORM_SIGNAL]
		= g_signal_new ("perform",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzGestureClass, perform),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);
}

static void
kz_gesture_init (KzGesture *gesture)
{
	gesture->max_sequence_len
		= G_N_ELEMENTS(gesture->sequence) - 1;
	gesture->threshold = 16;

	kz_gesture_reset(gesture); /* init other members */

	gesture->items = NULL;
}

static void
dispose (GObject *object)
{
	KzGesture *gesture;

	gesture = KZ_GESTURE(object);

	if (gesture->items)
		kz_gesture_items_unref(gesture->items);
	gesture->items = NULL;

	if (G_OBJECT_CLASS (kz_gesture_parent_class)->dispose)
		G_OBJECT_CLASS (kz_gesture_parent_class)->dispose(object);
}

KzGesture *
kz_gesture_new (void)
{
	KzGesture *gesture = g_object_new(KZ_TYPE_GESTURE, NULL);
	return gesture;
}

void
kz_gesture_set_items (KzGesture *gesture, KzGestureItems *items)
{
	g_return_if_fail(KZ_IS_GESTURE(gesture));

	if (gesture->items)
		kz_gesture_items_unref(gesture->items);
	if (items)
		gesture->items = kz_gesture_items_ref(items);
	else
		gesture->items = NULL;
}

static void
kz_gesture_real_perform (KzGesture *gesture)
{
	KzGestureItem *item;

	item = kz_gesture_search_matched_item(gesture);
	if (item)
		gtk_action_activate(item->action);

	kz_gesture_reset(gesture);
}

static void
kz_gesture_stack_motion (KzGesture *gesture, KzGestureMotion motion)
{
	gint len, max_len;
	KzGestureMotion *seq;

	g_return_if_fail(KZ_IS_GESTURE(gesture));
	/*
	g_return_if_fail(motion > KZ_GESTURE_MOTION_TERMINATOR &&
			 motion < KZ_GESTURE_N_MOTIONS);
	*/

	len = gesture->sequence_len;
	max_len = gesture->max_sequence_len;
	seq = gesture->sequence;

	g_return_if_fail(len >= 0 && len < max_len);

	seq[len] = motion;
	len = ++gesture->sequence_len;
	seq[len] = '\0';
}

void
kz_gesture_start (KzGesture *gesture, gint mode, gint x, gint y)
{
	g_return_if_fail(KZ_IS_GESTURE(gesture));

	gesture->current_mode = mode;
	gesture->prev_x = gesture->x = x;
	gesture->prev_y = gesture->y = y;
	gesture->started = TRUE;

	g_signal_emit(gesture, kz_gesture_signals[START_SIGNAL], 0);
}

void
kz_gesture_perform (KzGesture *gesture)
{
	g_return_if_fail(KZ_IS_GESTURE(gesture));

	g_signal_emit(gesture, kz_gesture_signals[PERFORM_SIGNAL], 0);
}

void
kz_gesture_cancel (KzGesture *gesture)
{
	kz_gesture_reset (gesture);
	g_signal_emit(gesture, kz_gesture_signals[CANCEL_SIGNAL], 0);
}

static void
kz_gesture_reset (KzGesture *gesture)
{
	gesture->sequence[0]       = '\0';
	gesture->sequence_len      = 0;
	gesture->current_mode      = 0;
	gesture->prev_x            = -1;
	gesture->prev_y            = -1;
	gesture->x                 = -1;
	gesture->y                 = -1;
	gesture->started           = FALSE;
}

gboolean
kz_gesture_is_started (KzGesture *gesture)
{
	g_return_val_if_fail(KZ_IS_GESTURE(gesture), FALSE);
	return gesture->started;
}

void
kz_gesture_create_gesture_string(KzGesture *gesture, gchar buf[], gint len)
{
	gint i, j = 0;

	g_return_if_fail(KZ_IS_GESTURE(gesture));
	g_return_if_fail(buf);

	buf[0] = '\0';

	for (i = 0; i < gesture->sequence_len && j < len - 2; i++)
	{
		switch (toupper(gesture->sequence[i]))
		{
		case 'U':
			buf[j++] = 'U';
			break;
		case 'D':
			buf[j++] = 'D';
			break;
		case 'L':
			buf[j++] = 'L';
			break;
		case 'R':
			buf[j++] = 'R';
			break;
		default:
			buf[j++] = '?';
			break;
		}
		if (gesture->sequence[i] != '\0')
			buf[j++] = ' ';
		buf[j] = '\0';
	}
}

void
kz_gesture_update_position (KzGesture *gesture, gint x, gint y)
{
	KzGestureMotion motion;
	gint mx, my;

	g_return_if_fail(KZ_IS_GESTURE(gesture));
	g_return_if_fail(kz_gesture_is_started(gesture));

	mx = x - gesture->prev_x; 
	my = y - gesture->prev_y;

	if (abs(mx) > gesture->threshold || abs(my) > gesture->threshold)
	{
		gint len = gesture->sequence_len;
		gint max_len = gesture->max_sequence_len;
		KzGestureMotion *seq = gesture->sequence;

		if (abs(mx) > abs(my)) {
			if (mx < 0)
				motion = 'L';
			else
				motion = 'R';
		}
		else
		{
			if (my < 0)
				motion = 'U';
			else
				motion = 'D';
		}

		gesture->prev_x = x;
		gesture->x = x;
		gesture->prev_y = y;
		gesture->y = y;

		if (len == 0 || (len > 0 && len < max_len && seq[len - 1] != motion))
		{
			g_signal_emit (gesture,
				       kz_gesture_signals[STACK_MOTION_SIGNAL],
				       0, motion);
		}
	}
}

static KzGestureItem *
kz_gesture_search_matched_item (KzGesture *gesture)
{
	GSList *node;
	gint j;

	g_return_val_if_fail(KZ_IS_GESTURE(gesture), NULL);
	if (!kz_gesture_is_started(gesture)) return NULL;
	if (gesture->sequence[0] == 0) return NULL;
	if (!gesture->items) return NULL;

	for (node = gesture->items->list; node; node = g_slist_next(node))
	{
		KzGestureItem *item = node->data;

		if (!item) continue;
		if (item->sequence[0] == 0) continue;

		for (j = 0; gesture->sequence[j] == item->sequence[j]; j++)
		{
			if (gesture->sequence[j + 1] == 0 &&
			    item->sequence[j + 1] == 0)
			{
				return item;
			}
			else if (gesture->sequence[j + 1] == 0 ||
				 item->sequence[j + 1] == 0)
			{
				break;
			}
		}
	}

	return NULL;
}

gboolean
kz_gesture_is_matched (KzGesture *gesture)
{
	g_return_val_if_fail(KZ_IS_GESTURE(gesture), FALSE);

	if (kz_gesture_get_matched_label(gesture))
		return TRUE;
	else
		return FALSE;
}

const gchar *
kz_gesture_get_matched_label (KzGesture *gesture)
{
	KzGestureItem *item;

	g_return_val_if_fail(KZ_IS_GESTURE(gesture), NULL);

	item = kz_gesture_search_matched_item (gesture);
	if (!item) return NULL;
	g_return_val_if_fail(item->action, NULL);
	return gtk_action_get_name(item->action);
}

void
kz_gesture_get_current_position (KzGesture *gesture,
				 gint *x, gint *y)
{
	g_return_if_fail(KZ_IS_GESTURE(gesture));

	if (x)
		*x = gesture->x;
	if (y)
		*y = gesture->y;
}

const KzGestureMotion *
kz_gesture_get_current_sequence (KzGesture *gesture)
{
	g_return_val_if_fail(KZ_IS_GESTURE(gesture), NULL);

	return gesture->sequence;
}

gint
kz_gesture_get_mode (KzGesture *gesture)
{
	g_return_val_if_fail(KZ_IS_GESTURE(gesture), 0);

	return gesture->current_mode;
}

void
kz_gesture_set_mode (KzGesture *gesture, gint mode)
{
	g_return_if_fail(KZ_IS_GESTURE(gesture));

	gesture->current_mode = mode;
}

gint
kz_gesture_get_threshold (KzGesture *gesture)
{
	g_return_val_if_fail(KZ_IS_GESTURE(gesture), 0);

	return gesture->threshold;
}

void
kz_gesture_set_threshold (KzGesture *gesture, gint threshold)
{
	g_return_if_fail(KZ_IS_GESTURE(gesture));
	g_return_if_fail(threshold >= 0);

	gesture->threshold = threshold;
}



/*****************************************************************************
 *
 *   KzGestureItemTable
 *
 *****************************************************************************/
KzGestureItems *
kz_gesture_items_new ()
{
	KzGestureItems *items = g_new0(KzGestureItems, 1);
	items->list = NULL;
	items->ref_count = 1;
	return items;
}

KzGestureItems *
kz_gesture_items_ref (KzGestureItems *items)
{
	g_return_val_if_fail(items, NULL);
	items->ref_count++;
	return items;
}

void
kz_gesture_items_unref (KzGestureItems *items)
{
	g_return_if_fail(items);
	items->ref_count--;

	if (items->ref_count == 0) {
		GSList *node = items->list;

		for(; node; node = g_slist_next(node))
		{
			KzGestureItem *item = node->data;

			if (!item) continue;
			kz_gesture_item_destroy(item);
		}

		g_slist_free(items->list);
		items->list = NULL;
		g_free(items);
	}
}


static gboolean
validate_gesture_sequence (const KzGestureMotion *sequence)
{
	gint i;

	if (!sequence || !*sequence) return FALSE;

	for (i = 0; sequence[i]; i++)
	{
		gint c = toupper(sequence[i]);
		if (c != 'U' && c != 'D' && c!= 'L' && c != 'R')
			return FALSE;
	}

	return TRUE;
}


static gint
compare_gesture_item (gconstpointer data1, gconstpointer data2)
{
	const KzGestureItem *item = data1;
	const GtkAction *action = GTK_ACTION(data2);

	return item->action - action;
}


static void
kz_gesture_item_destroy (KzGestureItem *item)
{
	g_return_if_fail(item);

	g_object_unref(item->action);
	item->action = NULL;
	g_free(item->sequence);
	item->sequence = NULL;
	g_free(item);
}


void
kz_gesture_items_set_action (KzGestureItems *items,
			     GtkAction *action, gint mode,
			     const KzGestureMotion *sequence)
{
	KzGestureItem *item;
	GSList *node;

	g_return_if_fail(items);
	g_return_if_fail(GTK_IS_ACTION(action));
	g_return_if_fail(sequence && *sequence != '\0');

	if(!validate_gesture_sequence(sequence))
	{
		g_warning("Invalid gesture sequence: %s", sequence);
		return;
	}

	node = g_slist_find_custom(items->list, action,
				   compare_gesture_item);
	if (node)
	{
		item = node->data;
		g_free(item->sequence);
		item->sequence = NULL;
	}
	else
	{
		item = g_new0(KzGestureItem, 1);
		item->action   = g_object_ref(action);
	}

	item->mode     = mode;
	item->sequence = g_strdup(sequence);

	items->list = g_slist_append(items->list, item);
}


void
kz_gesture_items_unset_action (KzGestureItems *items,
			       GtkAction *action)
{
	KzGestureItem *item;
	GSList *node;

	g_return_if_fail(items);
	g_return_if_fail(GTK_IS_ACTION(action));

	node = g_slist_find_custom(items->list, action,
				   compare_gesture_item);
	if (!node) return;

	item = node->data;
	items->list = g_slist_remove(items->list, item);
	kz_gesture_item_destroy(item);
}
