#include "Ecore_Fb.h"
#include "ecore_fb_private.h"


/* Copyright (C) Brad Hards (1999-2002).
 * this macro is used to tell if "bit" is set in "array"
 * it selects a byte from the array, and does a boolean AND 
 * operation with a byte that only has the relevant bit set. 
 * eg. to check for the 12th bit, we do (array[1] & 1<<4)
 */
#define TEST_BIT(bit, array)    (array[bit/8] & (1<<(bit%8)))

#define CLICK_THRESHOLD_DEFAULT 0.25

static Ecore_List *_ecore_fb_li_devices = NULL;

static const char *_ecore_fb_li_kbd_syms[128 * 6] =
{
#include "ecore_fb_keytable.h"
};

static void 
_ecore_fb_li_event_free_key_down(void *data, void *ev)
{

	Ecore_Fb_Event_Key_Up *e;

	e = ev;
	free(e->keyname);
	if (e->keysymbol) free(e->keysymbol);
	if (e->key_compose) free(e->key_compose);
	free(e);
}

static void 
_ecore_fb_li_event_free_key_up(void *data, void *ev)
{

	Ecore_Fb_Event_Key_Up *e;

	e = ev;
	free(e->keyname);
	if (e->keysymbol) free(e->keysymbol);
	if (e->key_compose) free(e->key_compose);
	free(e);
}

static void
_ecore_fb_li_device_event_key(Ecore_Fb_Input_Device *dev, struct input_event *iev)
{
	if(!dev->listen)
		return;

	/* check for basic keyboard keys */
	if(iev->code >= KEY_ESC && iev->code <= KEY_COMPOSE)
	{
		/* check the key table */
		if(iev->value)
		{
			int offset = 0;
			Ecore_Fb_Event_Key_Down *ev;

			ev = calloc(1, sizeof(Ecore_Fb_Event_Key_Down));
			if(dev->keyboard.shift) offset = 1;
			else if(dev->keyboard.lock) offset = 2;
			ev->keyname = strdup(_ecore_fb_li_kbd_syms[iev->code * 6]);

			ev->keysymbol = strdup(_ecore_fb_li_kbd_syms[(iev->code * 6) + offset]);
			ev->key_compose = strdup(_ecore_fb_li_kbd_syms[(iev->code * 6) + 3 + offset]);
			ev->dev = dev;
			ecore_event_add(ECORE_FB_EVENT_KEY_DOWN, ev, _ecore_fb_li_event_free_key_down, NULL);
			/* its a repeated key, dont increment */
			if(iev->value == 2)
				return;
			if (!strcmp(ev->keyname, "Control_L"))
				dev->keyboard.ctrl++;
			else if (!strcmp(ev->keyname, "Control_R"))
				dev->keyboard.ctrl++;
			else if (!strcmp(ev->keyname, "Alt_L"))
				dev->keyboard.alt++;
			else if (!strcmp(ev->keyname, "Alt_R"))
				dev->keyboard.alt++;
			else if (!strcmp(ev->keyname, "Shift_L"))
				dev->keyboard.shift++;
			else if (!strcmp(ev->keyname, "Shift_R"))
				dev->keyboard.shift++;
			else if (!strcmp(ev->keyname, "Caps_Lock"))
				dev->keyboard.lock++;
			if (dev->keyboard.ctrl > 2) dev->keyboard.ctrl = 2;
			if (dev->keyboard.alt > 2) dev->keyboard.alt = 2;
			if (dev->keyboard.shift > 2) dev->keyboard.shift = 2;
			if (dev->keyboard.lock > 1) dev->keyboard.lock = 1;
		}
		else
		{
			int offset = 0;
			Ecore_Fb_Event_Key_Up *ev;

			ev = calloc(1, sizeof(Ecore_Fb_Event_Key_Up));
			if(dev->keyboard.shift) offset = 1;
			else if(dev->keyboard.lock) offset = 2;
			ev->keyname = strdup(_ecore_fb_li_kbd_syms[iev->code * 6]);

			ev->keysymbol = strdup(_ecore_fb_li_kbd_syms[(iev->code * 6) + offset]);
			ev->key_compose = strdup(_ecore_fb_li_kbd_syms[(iev->code * 6) + 3 + offset]);
			ev->dev = dev;
			ecore_event_add(ECORE_FB_EVENT_KEY_UP, ev, _ecore_fb_li_event_free_key_up, NULL);
			if (!strcmp(ev->keyname, "Control_L"))
				dev->keyboard.ctrl--;
			else if (!strcmp(ev->keyname, "Control_R"))
				dev->keyboard.ctrl--;
			else if (!strcmp(ev->keyname, "Alt_L"))
				dev->keyboard.alt--;
			else if (!strcmp(ev->keyname, "Alt_R"))
				dev->keyboard.alt--;
			else if (!strcmp(ev->keyname, "Shift_L"))
				dev->keyboard.shift--;
			else if (!strcmp(ev->keyname, "Shift_R"))
				dev->keyboard.shift--;
			else if (!strcmp(ev->keyname, "Caps_Lock"))
				dev->keyboard.lock--;
			if (dev->keyboard.ctrl < 0) dev->keyboard.ctrl = 0;
			if (dev->keyboard.alt < 0) dev->keyboard.alt = 0;
			if (dev->keyboard.shift < 0) dev->keyboard.shift = 0;
			if (dev->keyboard.lock < 0) dev->keyboard.lock = 0;
		}
	}
	/* check for mouse button events */
	else if(iev->code >= BTN_MOUSE && iev->code < BTN_JOYSTICK)
	{
		int button;

		button = ((iev->code & 0x00F) + 1);
		if(iev->value)
		{
			Ecore_Fb_Event_Mouse_Button_Down *ev;
			double current;

			ev = calloc(1, sizeof(Ecore_Fb_Event_Mouse_Button_Down));
			ev->dev = dev;
			ev->button = button;
			ev->x = dev->mouse.x;
			ev->y = dev->mouse.y;

			current = ecore_time_get();
			if((current - dev->mouse.prev) <= dev->mouse.threshold)
			{
				ev->double_click = 1;
			}
			if((current - dev->mouse.last) <= (2 * dev->mouse.threshold))
			{
				ev->triple_click = 1;
				/* reset */
				dev->mouse.prev = 0;
				dev->mouse.last = 0;
				current = 0;
			}
			else
			{
				/* update values */
				dev->mouse.last = dev->mouse.prev;
				dev->mouse.prev = current;
			}
			ecore_event_add(ECORE_FB_EVENT_MOUSE_BUTTON_DOWN, ev, NULL ,NULL);
		}
		else
		{
			Ecore_Fb_Event_Mouse_Button_Up *ev;

			ev = calloc(1,sizeof(Ecore_Fb_Event_Mouse_Button_Up));
			ev->dev = dev;
			ev->button = button;
			ev->x = dev->mouse.x;
			ev->y = dev->mouse.y;

			ecore_event_add(ECORE_FB_EVENT_MOUSE_BUTTON_UP, ev, NULL ,NULL);
		}
	}
}

static void
_ecore_fb_li_device_event_rel(Ecore_Fb_Input_Device *dev, struct input_event *iev)
{
	if(!dev->listen)
		return;
	/* dispatch the button events if they are queued */
	switch(iev->code)
	{
		case REL_X:
		case REL_Y:
		{
			Ecore_Fb_Event_Mouse_Move *ev;
			if(iev->code == REL_X)
			{
				dev->mouse.x += iev->value;
				if(dev->mouse.x > dev->mouse.w - 1)
					dev->mouse.x = dev->mouse.w;
				else if(dev->mouse.x < 0)
					dev->mouse.x = 0;
			}
			else
			{
				dev->mouse.y += iev->value;
				if(dev->mouse.y > dev->mouse.h - 1)
					dev->mouse.y = dev->mouse.h;
				else if(dev->mouse.y < 0)
					dev->mouse.y = 0;
			}
			ev = calloc(1,sizeof(Ecore_Fb_Event_Mouse_Move));
			ev->x = dev->mouse.x;
			ev->y = dev->mouse.y;
			ev->dev = dev;

			ecore_event_add(ECORE_FB_EVENT_MOUSE_MOVE,ev,NULL,NULL);
			break;
		}
		case REL_WHEEL:
		case REL_HWHEEL:
		{
			Ecore_Fb_Event_Mouse_Wheel *ev;
			ev = calloc(1, sizeof(Ecore_Fb_Event_Mouse_Wheel));

			ev->x = dev->mouse.x;
			ev->y = dev->mouse.y;
			if(iev->code == REL_HWHEEL)
				ev->direction = 1;
			ev->wheel = iev->value;
			ev->dev = dev;
			ecore_event_add(ECORE_FB_EVENT_MOUSE_WHEEL, ev, NULL, NULL);
			break;
		}
		default:
			break;
	}
}

static void
_ecore_fb_li_device_event_abs(Ecore_Fb_Input_Device *dev, struct input_event *iev)
{
	if(!dev->listen)
		return;
	switch(iev->code)
	{
		case ABS_X:
		case ABS_Y:
		{
			Ecore_Fb_Event_Mouse_Move *ev;
			if((iev->code == ABS_X) && (dev->mouse.w != 0))
			{
				int tmp;

				tmp = (int)((double)(iev->value - dev->mouse.min_w) / dev->mouse.rel_w);
				if(tmp < 0)
					dev->mouse.x = 0;
				else if(tmp > dev->mouse.w)
					dev->mouse.x = dev->mouse.w;
				else
					dev->mouse.x = tmp;
			}
			else if((iev->code == ABS_Y) && (dev->mouse.h != 0))
			{
				int tmp;

				tmp = (int)((double)(iev->value - dev->mouse.min_h) / dev->mouse.rel_h);
				if(tmp < 0)
					dev->mouse.y = 0;
				else if(tmp > dev->mouse.h)
					dev->mouse.y = dev->mouse.h;
				else
					dev->mouse.y = tmp;
			}
			ev = calloc(1,sizeof(Ecore_Fb_Event_Mouse_Move));
			ev->x = dev->mouse.x;
			ev->y = dev->mouse.y;
			ev->dev = dev;

			ecore_event_add(ECORE_FB_EVENT_MOUSE_MOVE, ev, NULL, NULL);
			break;
		}
		case ABS_PRESSURE:
			/* TODO emulate a button press */
			break;
	}
}

static int
_ecore_fb_li_device_fd_callback(void *data, Ecore_Fd_Handler *fdh)
{
	Ecore_Fb_Input_Device *dev;
	struct input_event ev[64];
	int len;
	int i;

	dev = (Ecore_Fb_Input_Device*)data;
	/* read up to 64 events at once */
	len = read(dev->fd, &ev, sizeof(ev));
	// printf("[ecore_fb_li_device:fd_callback] received %d data\n", len);
	for(i = 0; i < len/sizeof(ev[0]); i++)
	{
		switch(ev[i].type)
		{
			case EV_ABS:
				_ecore_fb_li_device_event_abs(dev, &ev[i]);
				break;
			case EV_REL:
				_ecore_fb_li_device_event_rel(dev, &ev[i]);
				break;
			case EV_KEY:
				_ecore_fb_li_device_event_key(dev, &ev[i]);
				break;
			default:
				break;
		}
	}
	return 1;
}

/* 
 * Starts getting events from the input device 
 *
 */
EAPI void 
ecore_fb_input_device_listen(Ecore_Fb_Input_Device *dev, int listen)
{
	if(!dev) return;
	if((listen && dev->listen) || (!listen && !dev->listen)) return;
	if(listen)
	{
		/* if the device already had a handler */
		if(!dev->handler)
			dev->handler = ecore_main_fd_handler_add(dev->fd, ECORE_FD_READ, _ecore_fb_li_device_fd_callback, dev, NULL, NULL);

	}
	dev->listen = listen;
}

/*
 * Opens an input device
 */
EAPI Ecore_Fb_Input_Device *
ecore_fb_input_device_open(const char *dev)
{
	Ecore_Fb_Input_Device *device;
	char event_type_bitmask[EV_MAX/8 + 1];
	int event_type;
	int fd;

	if(!dev) return NULL;
	device = calloc(1, sizeof(Ecore_Fb_Input_Device));
	if(!device) return NULL;

	if(!_ecore_fb_li_devices)
		_ecore_fb_li_devices = ecore_list_new();

	if((fd = open(dev, O_RDONLY, O_NONBLOCK)) < 0)
	{
		fprintf(stderr, "[ecore_fb_li:device_open] %s %s", dev, strerror(errno));
		goto error_open;
	}
	/* query capabilities */
	if(ioctl(fd, EVIOCGBIT(0, EV_MAX), event_type_bitmask) < 0)
	{
		fprintf(stderr,"[ecore_fb_li:device_open] query capabilities %s %s", dev, strerror(errno));
		goto error_caps;
	}
	/* query name */
	device->info.name = calloc(256, sizeof(char));
	if(ioctl(fd, EVIOCGNAME(sizeof(char) * 256), device->info.name) < 0)
	{
		fprintf(stderr, "[ecore_fb_li:device_open] get name %s %s", dev, strerror(errno));
		strcpy(device->info.name, "Unknown");
	}
	device->fd = fd;
	device->info.dev = strdup(dev);
	/* common */
	device->mouse.threshold = CLICK_THRESHOLD_DEFAULT;

	/* set info */
	for(event_type = 0; event_type < EV_MAX; event_type++)
	{
		if(!TEST_BIT(event_type, event_type_bitmask))
			continue;
		switch(event_type)
		{
			case EV_SYN :
			break;

			case EV_KEY:
			device->info.cap |= ECORE_FB_INPUT_DEVICE_CAP_KEYS_OR_BUTTONS;
			break;

			case EV_REL:
			device->info.cap |= ECORE_FB_INPUT_DEVICE_CAP_RELATIVE;
			break;

			case EV_ABS:
			device->info.cap |= ECORE_FB_INPUT_DEVICE_CAP_ABSOLUTE;
			break;

			case EV_MSC:
			case EV_LED:
			case EV_SND:
			case EV_REP:
			case EV_FF :
			case EV_FF_STATUS:
			case EV_PWR:
			default:
			break;
		}
	}
	ecore_list_append(_ecore_fb_li_devices, device);
	return device;

	error_caps:
	close(fd);
	error_open:
	free(device);
	return NULL;

}

EAPI void
ecore_fb_input_device_close(Ecore_Fb_Input_Device *dev)
{
	/* close the fd */
	close(dev->fd);
	/* remove the element from the list */
	if(ecore_list_goto(_ecore_fb_li_devices, dev))
		ecore_list_remove(_ecore_fb_li_devices);
	free(dev);
}


/*
 * If the device is a relative input device, 
 * we must set a width and height for it. If its
 * absolute set the ioctl correctly, if not, unsupported
 * device
 */
EAPI void
ecore_fb_input_device_axis_size_set(Ecore_Fb_Input_Device *dev, int w, int h)
{
	if(!dev)
		return;
	if(w < 0 || h < 0)
		return;
	/* FIXME 
	 * this code is for a touchscreen device, 
	 * make it configurable (ABSOLUTE | RELATIVE)
	 */
	if(dev->info.cap & ECORE_FB_INPUT_DEVICE_CAP_ABSOLUTE)
	{
		/* FIXME looks like some kernels dont include this struct */
		struct input_absinfo abs_features;

		ioctl(dev->fd, EVIOCGABS(ABS_X), &abs_features);
		dev->mouse.min_w = abs_features.minimum;
		dev->mouse.rel_w = (double)(abs_features.maximum - abs_features.minimum)/(double)(w);

		ioctl(dev->fd, EVIOCGABS(ABS_Y), &abs_features);
		dev->mouse.min_h = abs_features.minimum;
		dev->mouse.rel_h = (double)(abs_features.maximum - abs_features.minimum)/(double)(h);
	}
	else if(!(dev->info.cap & ECORE_FB_INPUT_DEVICE_CAP_RELATIVE))
		return;

	/* update the local values */
	if(dev->mouse.x > w - 1)
		dev->mouse.x = w -1;
	if(dev->mouse.y > h - 1)
		dev->mouse.y = h -1;
	dev->mouse.w = w;
	dev->mouse.h = h;
}


EAPI const char *
ecore_fb_input_device_name_get(Ecore_Fb_Input_Device *dev)
{
	if(!dev)
		return NULL;
	return dev->info.name;
}
EAPI Ecore_Fb_Input_Device_Cap
ecore_fb_input_device_cap_get(Ecore_Fb_Input_Device *dev)
{
	if(!dev)
		return ECORE_FB_INPUT_DEVICE_CAP_NONE;
	return dev->info.cap;
}
EAPI void
ecore_fb_input_device_threshold_click_set(Ecore_Fb_Input_Device *dev, double threshold)
{
	if(!dev) return;
	if(threshold == dev->mouse.threshold || threshold == 0) return;
	dev->mouse.threshold = threshold;
}
EAPI double
ecore_fb_input_device_threshold_click_get(Ecore_Fb_Input_Device *dev)
{
	if(!dev) return 0;
	return dev->mouse.threshold;
}
