/*
 * Copyright (c) 2012 Martin Sucha
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <errno.h>
#include <fibril.h>
#include <io/chardev.h>
#include <mem.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

#include "isdv4.h"

#define BUF_SIZE 64

#define START_OF_PACKET 128
#define CONTROL_PACKET 64
#define TOUCH_EVENT 16
#define FINGER1 1
#define FINGER2 2
#define TIP 1
#define BUTTON1 2
#define BUTTON2 4
#define PROXIMITY 32

#define CMD_START '1'
#define CMD_STOP '0'
#define CMD_QUERY_STYLUS '*'
#define CMD_QUERY_TOUCH '%'

/** Packet consumer function
 *
 * @return true if reading of packets should continue
 */
typedef bool (*packet_consumer_fn)(uint8_t *packet, size_t size,
    isdv4_state_t *state);

static void isdv4_event_init(isdv4_event_t *event)
{
	memset(event, 0, sizeof(isdv4_event_t));
}

/**
 * Parse event packet and emit events
 * @return true if reading of packets should continue
 */
static bool parse_event(uint8_t *packet, size_t size, isdv4_state_t *state)
{
	if (size < 1)
		return false;

	bool control_packet = ((packet[0] & CONTROL_PACKET) > 0);
	if (control_packet)
		return true;

	/* This is an event initiated by the device */
	isdv4_event_t event;
	isdv4_event_init(&event);

	if (packet[0] & TOUCH_EVENT) {
		if (size != 5)
			return true;

		/* This is a touch event */
		bool finger1 = (packet[0] & FINGER1) > 0;
		event.x = ((packet[1] & 127) << 7) | (packet[2] & 127);
		event.y = ((packet[3] & 127) << 7) | (packet[4] & 127);
		event.source = TOUCH;

		if (!state->stylus_in_proximity) {
			if (!finger1 && state->finger1_pressed) {
				state->finger1_pressed = false;

				event.type = RELEASE;
				event.button = 1;
				state->emit_event_fn(&event);
			} else if (finger1 && !state->finger1_pressed) {
				state->finger1_pressed = true;

				event.type = PRESS;
				event.button = 1;
				state->emit_event_fn(&event);
			} else {
				event.type = MOVE;
				event.button = 1;
				state->emit_event_fn(&event);
			}
		}
	} else {
		if (size != 9)
			return true;

		/* This is a stylus event */
		bool tip = packet[0] & TIP;
		bool button1 = packet[0] & BUTTON1;
		bool button2 = packet[0] & BUTTON2;
		bool proximity = packet[0] & PROXIMITY;
		event.x = ((packet[1] & 127) << 7) | (packet[2] & 124) | ((packet[6] >> 5) & 3);
		event.y = ((packet[3] & 127) << 7) | (packet[4] & 124) | ((packet[6] >> 3) & 3);
		event.pressure = (packet[5] & 127) | ((packet[6] & 7) << 7);

		if (proximity && !state->stylus_in_proximity) {
			/* Stylus came into proximity */
			state->stylus_in_proximity = true;
			state->stylus_is_eraser = !tip && button2;
			event.source = (state->stylus_is_eraser ? STYLUS_ERASER : STYLUS_TIP);
			event.type = PROXIMITY_IN;
			state->emit_event_fn(&event);
		} else if (!proximity && state->stylus_in_proximity) {
			/* Stylus came out of proximity */
			state->stylus_in_proximity = false;
			event.source = (state->stylus_is_eraser ? STYLUS_ERASER : STYLUS_TIP);
			event.type = PROXIMITY_OUT;
			state->emit_event_fn(&event);
		} else {
			/* Proximity state didn't change, but we need to check if it is still eraser */
			if (state->stylus_is_eraser && !button2) {
				event.type = PROXIMITY_OUT;
				event.source = STYLUS_ERASER;
				state->emit_event_fn(&event);
				event.type = PROXIMITY_IN;
				event.source = STYLUS_TIP;
				state->emit_event_fn(&event);
				state->stylus_is_eraser = false;
			} else if (!state->stylus_is_eraser && !tip && button2) {
				event.type = PROXIMITY_OUT;
				event.source = STYLUS_TIP;
				state->emit_event_fn(&event);
				event.type = PROXIMITY_IN;
				event.source = STYLUS_ERASER;
				state->emit_event_fn(&event);
				state->stylus_is_eraser = true;
			}
		}

		if (!state->stylus_is_eraser) {
			if (tip && !state->tip_pressed) {
				state->tip_pressed = true;
				event.type = PRESS;
				event.source = STYLUS_TIP;
				event.button = 1;
				state->emit_event_fn(&event);
			} else if (!tip && state->tip_pressed) {
				state->tip_pressed = false;
				event.type = RELEASE;
				event.source = STYLUS_TIP;
				event.button = 1;
				state->emit_event_fn(&event);
			}
			if (button1 && !state->button1_pressed) {
				state->button1_pressed = true;
				event.type = PRESS;
				event.source = STYLUS_TIP;
				event.button = 2;
				state->emit_event_fn(&event);
			} else if (!button1 && state->button1_pressed) {
				state->button1_pressed = false;
				event.type = RELEASE;
				event.source = STYLUS_TIP;
				event.button = 2;
				state->emit_event_fn(&event);
			}
			if (button2 && !state->button2_pressed) {
				state->button2_pressed = true;
				event.type = PRESS;
				event.source = STYLUS_TIP;
				event.button = 3;
				state->emit_event_fn(&event);
			} else if (!button2 && state->button2_pressed) {
				state->button2_pressed = false;
				event.type = RELEASE;
				event.source = STYLUS_TIP;
				event.button = 3;
				state->emit_event_fn(&event);
			}
			event.type = MOVE;
			event.source = STYLUS_TIP;
			event.button = 0;
			state->emit_event_fn(&event);
		} else {
			if (tip && !state->tip_pressed) {
				state->tip_pressed = true;
				event.type = PRESS;
				event.source = STYLUS_ERASER;
				event.button = 1;
				state->emit_event_fn(&event);
			} else if (!tip && state->tip_pressed) {
				state->tip_pressed = false;
				event.type = RELEASE;
				event.source = STYLUS_ERASER;
				event.button = 1;
				state->emit_event_fn(&event);
			}
			event.type = MOVE;
			event.source = STYLUS_ERASER;
			event.button = 0;
			state->emit_event_fn(&event);
		}
	}

	return true;
}

static bool parse_response_stylus(uint8_t *packet, size_t size,
    isdv4_state_t *state)
{
	if (size < 1)
		return false;

	bool control_packet = ((packet[0] & CONTROL_PACKET) > 0);
	if (!control_packet)
		return true;

	if (size != 11)
		return false;

	state->stylus_max_x = ((packet[1] & 127) << 7) | (packet[2] & 124) |
	    ((packet[6] >> 5) & 3);
	state->stylus_max_y = ((packet[3] & 127) << 7) | (packet[4] & 124) |
	    ((packet[6] >> 3) & 3);
	state->stylus_max_pressure = (packet[5] & 63) | ((packet[6] & 7) << 7);
	state->stylus_max_xtilt = packet[8] & 127;
	state->stylus_max_ytilt = packet[7] & 127;
	state->stylus_tilt_supported = (state->stylus_max_xtilt &&
	    state->stylus_max_ytilt);

	return false;
}

static bool parse_response_touch(uint8_t *packet, size_t size,
    isdv4_state_t *state)
{
	if (size < 1)
		return false;

	bool control_packet = ((packet[0] & CONTROL_PACKET) > 0);
	if (!control_packet)
		return true;

	if (size != 11)
		return false;

	state->touch_type = (packet[0] & 63);

	unsigned int touch_resolution = packet[1] & 127;
	state->touch_max_x = ((packet[2] >> 5) & 3) | ((packet[3] & 127) << 7) |
	    (packet[4] & 124);
	state->touch_max_y = ((packet[2] >> 3) & 3) | ((packet[5] & 127) << 7) |
	    (packet[6] & 124);

	if (touch_resolution == 0)
		touch_resolution = 10;

	if (state->touch_max_x == 0 || state->touch_max_y == 0) {
		state->touch_max_x = (1 << touch_resolution);
		state->touch_max_y = (1 << touch_resolution);
	}

	return false;
}

static errno_t read_packets(isdv4_state_t *state, packet_consumer_fn consumer)
{
	bool reading = true;
	while (reading) {
		size_t nread;
		errno_t rc;

		rc = chardev_read(state->chardev, state->buf + state->buf_end,
		    state->buf_size - state->buf_end, &nread, chardev_f_none);
		if (rc != EOK && nread == 0)
			return EIO;
		state->buf_end += nread;

		size_t i = 0;

		/* Skip data until a start of packet is found */
		while (i < state->buf_end && (state->buf[i] & START_OF_PACKET) == 0)
			i++;

		size_t start = i;
		size_t end = i;
		size_t processed_end = i;

		/* Process packets one by one */
		while (reading && i < state->buf_end) {
			/* Determine the packet length */
			size_t packet_remaining;
			if (state->buf[i] & CONTROL_PACKET) {
				packet_remaining = 11;
			} else if (state->buf[i] & TOUCH_EVENT) {
				packet_remaining = 5;
			} else {
				packet_remaining = 9;
			}

			/* Find the end of the packet */
			i++; /* We need to skip the first byte with START_OF_PACKET set */
			packet_remaining--;
			while (packet_remaining > 0 && i < state->buf_end &&
			    (state->buf[i] & START_OF_PACKET) == 0) {
				i++;
				packet_remaining--;
			}
			end = i;

			/* If we have whole packet, process it */
			if (end > start && packet_remaining == 0) {
				reading = consumer(state->buf + start, end - start, state);
				start = end;
				processed_end = end;
			}
		}

		if (processed_end == 0 && state->buf_end == state->buf_size) {
			/* Packet too large, throw it away */
			state->buf_end = 0;
		}

		/* Shift the buffer contents to the left */
		size_t unprocessed_len = state->buf_end - processed_end;
		memcpy(state->buf, state->buf + processed_end, unprocessed_len);
		state->buf_end = unprocessed_len;
	}
	return EOK;
}

static bool write_command(chardev_t *chardev, uint8_t command)
{
	errno_t rc;
	size_t nwr;

	rc = chardev_write(chardev, &command, 1, &nwr);
	return rc == EOK;
}

errno_t isdv4_init(isdv4_state_t *state, async_sess_t *sess,
    isdv4_event_fn event_fn)
{
	chardev_t *chardev;
	errno_t rc;

	rc = chardev_open(sess, &chardev);
	if (rc != EOK)
		return rc;

	memset(state, 0, sizeof(isdv4_state_t));

	state->sess = sess;
	state->chardev = chardev;

	state->buf = malloc(BUF_SIZE);
	if (state->buf == NULL) {
		chardev_close(chardev);
		return ENOMEM;
	}

	state->buf_size = BUF_SIZE;
	state->emit_event_fn = event_fn;
	return EOK;
}

errno_t isdv4_init_tablet(isdv4_state_t *state)
{
	if (!write_command(state->chardev, CMD_STOP))
		return EIO;

	fibril_usleep(250000); /* 250 ms */

	// FIXME: Read all possible garbage before sending commands
	if (!write_command(state->chardev, CMD_QUERY_STYLUS))
		return EIO;

	errno_t rc = read_packets(state, parse_response_stylus);
	if (rc != EOK)
		return rc;

	if (!write_command(state->chardev, CMD_QUERY_TOUCH))
		return EIO;

	rc = read_packets(state, parse_response_touch);
	if (rc != EOK)
		return rc;

	if (!write_command(state->chardev, CMD_START))
		return EIO;

	return EOK;
}

errno_t isdv4_read_events(isdv4_state_t *state)
{
	return read_packets(state, parse_event);
}

void isdv4_fini(isdv4_state_t *state)
{
	free(state->buf);
}
