/*
 * Copyright 2005 IBM
 * Authors:
 * 	Vernon Mauery <vernux@us.ibm.com>
 */

/* Input device for the IBM ASM (RSA I Service Processor)
*/

#include <xf86.h>
#define NEED_XF86_TYPES
#include <xf86_ansic.h>
#include <xf86_OSproc.h>
#include <xf86Xinput.h>
#include <exevents.h>

#ifdef XFree86LOADER
#include <xf86Module.h>
#endif

#define IBMASM_DEBUG
#ifdef IBMASM_DEBUG
#define ibmasm_debug(level, ...) xf86ErrorFVerb(level, __VA_ARGS__)
#else
#define ibmasm_debug(level, ...)
#endif

/* we can get the 'struct input_event' from standard linux include files,
 * but I don't think we can use those here...
 * plus, we don't really care about the timestamp,
 * so we can define our own simple version */
struct input_event {
	struct {
		long _sec;
		long _usec;
	} _unused;
	unsigned short type;
	unsigned short code;
	int value;
};

/* Event types */
#define EV_SYN			0x00
#define EV_KEY			0x01
#define EV_REL			0x02
#define EV_ABS			0x03

/* Event Codes */
#define ABS_X			0x00
#define ABS_Y			0x01
#define BTN_LEFT		0x110
#define BTN_RIGHT		0x111
#define BTN_MIDDLE		0x112

typedef struct {
	int x, y;
	int new_x, new_y;
	int button_code;
	int button_value;
	int swap_x, swap_y;
	LocalDevicePtr local;
} ibmasm_info;

static const char *default_options[] =
{
	NULL
};

static LocalDevicePtr ibmasm_driver_setup(InputDriverPtr drv, IDevPtr dev, int flags);
static void ibmasm_driver_cleanup(InputDriverPtr drv, LocalDevicePtr local, int flags);

InputDriverRec IBMASM = {
	/* .driverVersion = */ 1,
	/* .driverName =    */ "ibmasm",
	/* .Identify =      */ NULL,
	/* .PreInit =       */ ibmasm_driver_setup,
	/* .UnInit =        */ ibmasm_driver_cleanup,
	/* .module =        */ NULL,
	/* .refCount =      */ 0,
};

#ifdef XFree86LOADER
ModuleInfoRec ibmasm_module_info = {
   	/* .moduleVersion =    */ 1,
    /* .moduleName =       */ "IBMASM",
    /* .module =           */ NULL,
    /* .refCount =         */ 0,
    /* .AvailableOptions = */ NULL,
};

static XF86ModuleVersionInfo VersionRec =
{
	"ibmasm", "Vernon Mauery <vernux@us.ibm.com>",
	MODINFOSTRING1, MODINFOSTRING2,
	XF86_VERSION_CURRENT,
	0, 6, 1,
	ABI_CLASS_XINPUT, ABI_XINPUT_VERSION, MOD_CLASS_XINPUT,
	{0, 0, 0, 0}
};

static pointer plug(pointer module, pointer options, int *error_major, int *error_minor)
{
	ibmasm_debug(1,"enter %s:%d\n", __FUNCTION__, __LINE__);
	xf86AddModuleInfo(&ibmasm_module_info, module);
	xf86AddInputDriver(&IBMASM, module, 0);
	ibmasm_debug(1,"leave %s:%d\n", __FUNCTION__, __LINE__);
	return module;
}

static void unplug(pointer p)
{
	ibmasm_debug(1,"%s:%d\n", __FUNCTION__, __LINE__);
}

XF86ModuleData ibmasmModuleData = {&VersionRec, plug, unplug };

#endif /* XFree86LOADER */

static Bool ibmasm_conversion(LocalDevicePtr local,
		int first, int num, int v0,
		int v1, int v2, int v3, int v4,
		int v5, int *x, int *y)
{
	ibmasm_info *private;
	int width = screenInfo.screens[0]->width;
	int height = screenInfo.screens[0]->height;

	if (first != 0 || num == 1)
		return FALSE;

	private = (ibmasm_info*)local->private;
	*x = v0;
	*y = v1;

	if (*x < 0)	*x = 0;
	if (*y < 0)	*y = 0;
	if (*x > width-1)
		*x = width-1;
	if (*y > height-1)
		*y = height-1;

	/* now that we are in bounds, we can do a swap if needed */
	if (private->swap_x)
		*x = width - 1 - *x;
	if (private->swap_y)
		*y = height - 1 - *y;

	ibmasm_debug(1,"%s: (%d, %d) -> (%d, %d)\n", __FUNCTION__, v0, v1, *x, *y);
	return TRUE;
}

static void ibmasm_read_input(LocalDevicePtr local)
{
	struct input_event event;
	ibmasm_info *private = (ibmasm_info *)local->private;
	ibmasm_debug(1, "enter %s:%d\n", __FUNCTION__, __LINE__);

	xf86memset(&event, 0, sizeof(struct input_event));
	while (xf86ReadSerial(local->fd, &event, sizeof(struct input_event)) == 
			sizeof(struct input_event)) {

		switch (event.type) {
			case EV_ABS:
				ibmasm_debug(1, "abs event: %s = %d\n",
						(event.code == ABS_X ? "ABS_X":"ABS_Y"), event.value);
				switch (event.code) {
					case ABS_X:
						private->new_x = event.value;
						break;
					case ABS_Y:
						private->new_y = event.value;
						break;
				}
				break;
			case EV_KEY:
				ibmasm_debug(1, "key event: %d %s\n", event.code,
						(event.value ? "pressed":"released"));
				private->button_code = event.code;
				private->button_value = event.value;
				break;
			case EV_SYN:
				/* do something with the data we have */
				ibmasm_debug(1, "sync event\n");
				private->x = private->new_x;
				private->y = private->new_y;
				ibmasm_debug(1, "xf86PostMotionEvent(local->dev = 0x%x, TRUE, 0, 2, %d, %d);\n",
						(int)local->dev, private->x, private->y);
				xf86PostMotionEvent(local->dev, TRUE, 0, 2, private->x, private->y);
				if (private->button_code) {
					xf86PostButtonEvent(local->dev, TRUE,
							private->button_code, private->button_value,
							0, 2, private->x, private->y);
					private->button_code = 0;
				}
				break;
		}
		xf86memset(&event, 0, sizeof(struct input_event));
	}
	ibmasm_debug(1, "%s:%d\n", __FUNCTION__, __LINE__);
}

static Bool ibmasm_device_control(DeviceIntPtr dev, int what)
{
	Bool ret = BadValue;
	LocalDevicePtr local = (LocalDevicePtr) dev->public.devicePrivate;
	ibmasm_debug(1, "%s:%d\n", __FUNCTION__, __LINE__);

	switch (what) {
		case DEVICE_ON:
			ibmasm_debug(1, "%s:%d -- DEVICE_ON\n", __FUNCTION__, __LINE__);
			ret = Success;
			local->fd = xf86OpenSerial(local->options);
			if (local->fd < 0) {
				ret = !Success;
				break;
			}
			xf86FlushInput(local->fd);
			xf86AddEnabledDevice(local);
			break;
		case DEVICE_INIT:
			ibmasm_debug(1, "%s:%d -- DEVICE_INIT\n", __FUNCTION__, __LINE__);
			{
				unsigned char map[] = {0, 1, 2, 3};

				if (!InitButtonClassDeviceStruct(dev, 3, map)) {
					ErrorF("ibmasm: failed to initialize ButtonClassDeviceStruct\n");
					return !Success;
				}
				if (!InitFocusClassDeviceStruct(dev)) {
					ErrorF("ibmasm: failed to initialize FocusClassDeviceStruct\n");
					return !Success;
				}
				if (!InitValuatorClassDeviceStruct(dev, 2, xf86GetMotionEvents,
							local->history_size, Absolute)) {
					ErrorF ("ibmasm: failed to initialize ValuatorClassDeviceStruct\n");
					return !Success;
				}
				xf86MotionHistoryAllocate (local);

				InitValuatorAxisStruct (dev, 0, 0, 1600, 1, 1, 1);
				InitValuatorAxisStruct (dev, 1, 0, 1200, 1, 1, 1);

			}
			ret = Success;
			break;
		case DEVICE_OFF:
		case DEVICE_CLOSE:
			ibmasm_debug(1, "%s:%d -- DEVICE_OFF\n", __FUNCTION__, __LINE__);
			xf86RemoveEnabledDevice(local);
			xf86CloseSerial(local->fd);
			ret = Success;
			break;
	}
	ibmasm_debug(1, "%s:%d\n", __FUNCTION__, __LINE__);
	return ret;
}

static LocalDevicePtr ibmasm_driver_setup(InputDriverPtr drv, IDevPtr dev, int flags)
{
	LocalDevicePtr local;
	ibmasm_info *priv;

	ibmasm_debug(1, "%s:%d\n", __FUNCTION__, __LINE__);
	priv = xcalloc(1, sizeof(ibmasm_info));
	if (!priv)
		return NULL;

	local = xf86AllocateInput(drv, flags);
	if (!local) {
		xfree(priv);
		return NULL;
	}

	priv->local = local;
	local->name = dev->identifier;
	local->device_control = ibmasm_device_control;
	local->read_input = ibmasm_read_input;
	local->control_proc = NULL;
	local->close_proc = NULL;
	local->conversion_proc = ibmasm_conversion;
	local->reverse_conversion_proc = NULL;
	local->switch_mode = NULL;
	local->flags = XI86_POINTER_CAPABLE;
	local->fd = -1;
	local->private = priv;
	local->private_flags = 0;
	local->history_size = 0;
	local->type_name = XI_TOUCHSCREEN;
	local->conf_idev = dev;
	local->dev = NULL;

	xf86CollectInputOptions(local, default_options, NULL);
	xf86OptionListReport(local->options);
	priv->swap_y = xf86SetBoolOption(local->options, "SwapY", FALSE);
	priv->swap_x = xf86SetBoolOption(local->options, "SwapX", FALSE);
	xf86ProcessCommonOptions(local, local->options);

	local->flags |= XI86_CONFIGURED;
	ibmasm_debug(1, "%s:%d\n", __FUNCTION__, __LINE__);
	return local;
}

static void ibmasm_driver_cleanup(InputDriverPtr drv, LocalDevicePtr local, int flags)
{
	ibmasm_debug(1, "%s:%d\n", __FUNCTION__, __LINE__);
	if (local->private)
		xfree(local->private);

	xf86DeleteInput(local, flags);

	ibmasm_debug(1, "%s:%d\n", __FUNCTION__, __LINE__);
}
