/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2003-2008  Marcel Holtmann <marcel@holtmann.org>
 *
 *
 *  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 of the License, 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/hidp.h>

#include "hidd.h"
#include "uinput.h"

#include <math.h>

#ifdef NEED_PPOLL
#include "ppoll.h"
#endif

static volatile sig_atomic_t __io_canceled = 0;

static void sig_hup(int sig)
{
}

static void sig_term(int sig)
{
	__io_canceled = 1;
}

static void send_event(int fd, uint16_t type, uint16_t code, int32_t value)
{
	struct uinput_event event;
	int len;

	if (fd <= fileno(stderr))
		return;

	memset(&event, 0, sizeof(event));
	event.type = type;
	event.code = code;
	event.value = value;

	len = write(fd, &event, sizeof(event));
}

static int uinput_create(char *name, int keyboard, int mouse)
{
	struct uinput_dev dev;
	int fd, aux;

	fd = open("/dev/uinput", O_RDWR);
	if (fd < 0) {
		fd = open("/dev/input/uinput", O_RDWR);
		if (fd < 0) {
			fd = open("/dev/misc/uinput", O_RDWR);
			if (fd < 0) {
				fprintf(stderr, "Can't open input device: %s (%d)\n",
							strerror(errno), errno);
				return -1;
			}
		}
	}

	memset(&dev, 0, sizeof(dev));

	if (name)
		strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);

	dev.id.bustype = BUS_BLUETOOTH;
	dev.id.vendor  = 0x0000;
	dev.id.product = 0x0000;
	dev.id.version = 0x0000;

	if (write(fd, &dev, sizeof(dev)) < 0) {
		fprintf(stderr, "Can't write device information: %s (%d)\n",
							strerror(errno), errno);
		close(fd);
		return -1;
	}

	if (mouse) {
		ioctl(fd, UI_SET_EVBIT, EV_REL);

		for (aux = REL_X; aux <= REL_MISC; aux++)
			ioctl(fd, UI_SET_RELBIT, aux);
	}

	if (keyboard) {
		ioctl(fd, UI_SET_EVBIT, EV_KEY);
		ioctl(fd, UI_SET_EVBIT, EV_LED);
		ioctl(fd, UI_SET_EVBIT, EV_REP);

		for (aux = KEY_RESERVED; aux <= KEY_UNKNOWN; aux++)
			ioctl(fd, UI_SET_KEYBIT, aux);

		//for (aux = LED_NUML; aux <= LED_MISC; aux++)
		//	ioctl(fd, UI_SET_LEDBIT, aux);
	}

	if (mouse) {
		ioctl(fd, UI_SET_EVBIT, EV_KEY);

		for (aux = BTN_LEFT; aux <= BTN_BACK; aux++)
			ioctl(fd, UI_SET_KEYBIT, aux);
	}

	ioctl(fd, UI_DEV_CREATE);

	return fd;
}

static int rfcomm_connect(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel)
{
	struct sockaddr_rc addr;
	int sk;

	sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
	if (sk < 0) {
		fprintf(stderr, "Can't create socket: %s (%d)\n",
							strerror(errno), errno);
		return -1;
	}

	memset(&addr, 0, sizeof(addr));
	addr.rc_family = AF_BLUETOOTH;
	bacpy(&addr.rc_bdaddr, src);

	if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		fprintf(stderr, "Can't bind socket: %s (%d)\n",
							strerror(errno), errno);
		close(sk);
		return -1;
	}

	memset(&addr, 0, sizeof(addr));
	addr.rc_family = AF_BLUETOOTH;
	bacpy(&addr.rc_bdaddr, dst);
	addr.rc_channel = channel;

	if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		fprintf(stderr, "Can't connect: %s (%d)\n",
							strerror(errno), errno);
		close(sk);
		return -1;
	}

	return sk;
}

static void func(int fd)
{
}

static void back(int fd)
{
}

static void next(int fd)
{
}

static void button(int fd, unsigned int button, int is_press)
{
	switch (button) {
	case 1:
		send_event(fd, EV_KEY, BTN_LEFT, is_press);
		break;
	case 3:
		send_event(fd, EV_KEY, BTN_RIGHT, is_press);
		break;
	}

	send_event(fd, EV_SYN, SYN_REPORT, 0);
}

static void move(int fd, unsigned int direction)
{
	double angle;
	int32_t x, y;

	angle = (direction * 22.5) * 3.1415926 / 180;
	x = (int) (sin(angle) * 8);
	y = (int) (cos(angle) * -8);

	send_event(fd, EV_REL, REL_X, x);
	send_event(fd, EV_REL, REL_Y, y);

	send_event(fd, EV_SYN, SYN_REPORT, 0);
}

static inline void epox_decode(int fd, unsigned char event)
{
	switch (event) {
	case 48:
		func(fd); break;
	case 55:
		back(fd); break;
	case 56:
		next(fd); break;
	case 53:
		button(fd, 1, 1); break;
	case 121:
		button(fd, 1, 0); break;
	case 113:
		break;
	case 54:
		button(fd, 3, 1); break;
	case 120:
		button(fd, 3, 0); break;
	case 112:
		break;
	case 51:
		move(fd, 0); break;
	case 97:
		move(fd, 1); break;
	case 65:
		move(fd, 2); break;
	case 98:
		move(fd, 3); break;
	case 50:
		move(fd, 4); break;
	case 99:
		move(fd, 5); break;
	case 67:
		move(fd, 6); break;
	case 101:
		move(fd, 7); break;
	case 52:
		move(fd, 8); break;
	case 100:
		move(fd, 9); break;
	case 66:
		move(fd, 10); break;
	case 102:
		move(fd, 11); break;
	case 49:
		move(fd, 12); break;
	case 103:
		move(fd, 13); break;
	case 57:
		move(fd, 14); break;
	case 104:
		move(fd, 15); break;
	case 69:
		break;
	default:
		printf("Unknown event code %d\n", event);
		break;
	}
}

int epox_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel)
{
	unsigned char buf[16];
	struct sigaction sa;
	struct pollfd p;
	sigset_t sigs;
	char addr[18];
	int i, fd, sk, len;

	sk = rfcomm_connect(src, dst, channel);
	if (sk < 0)
		return -1;

	fd = uinput_create("Bluetooth Presenter", 0, 1);
	if (fd < 0) {
		close(sk);
		return -1;
	}

	ba2str(dst, addr);

	printf("Connected to %s on channel %d\n", addr, channel);
	printf("Press CTRL-C for hangup\n");

	memset(&sa, 0, sizeof(sa));
	sa.sa_flags   = SA_NOCLDSTOP;
	sa.sa_handler = SIG_IGN;
	sigaction(SIGCHLD, &sa, NULL);
	sigaction(SIGPIPE, &sa, NULL);

	sa.sa_handler = sig_term;
	sigaction(SIGTERM, &sa, NULL);
	sigaction(SIGINT,  &sa, NULL);

	sa.sa_handler = sig_hup;
	sigaction(SIGHUP, &sa, NULL);

	sigfillset(&sigs);
	sigdelset(&sigs, SIGCHLD);
	sigdelset(&sigs, SIGPIPE);
	sigdelset(&sigs, SIGTERM);
	sigdelset(&sigs, SIGINT);
	sigdelset(&sigs, SIGHUP);

	p.fd = sk;
	p.events = POLLIN | POLLERR | POLLHUP;

	while (!__io_canceled) {
		p.revents = 0;
		if (ppoll(&p, 1, NULL, &sigs) < 1)
			continue;

		len = read(sk, buf, sizeof(buf));
		if (len < 0)
			break;

		for (i = 0; i < len; i++)
			epox_decode(fd, buf[i]);
	}

	printf("Disconnected\n");

	ioctl(fd, UI_DEV_DESTROY);

	close(fd);
	close(sk);

	return 0;
}

int headset_presenter(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel)
{
	printf("Not implemented\n");
	return -1;
}

/* The strange meta key close to Ctrl has been assigned to Esc,
   Fn key to CtrlR and the left space to Alt*/

static unsigned char jthree_keycodes[63] = {
	KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6,
	KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T,
	KEY_A, KEY_S, KEY_D, KEY_F, KEY_G,
	KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B,
	KEY_LEFTALT, KEY_TAB, KEY_CAPSLOCK, KEY_ESC,
	KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE,
	KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE,
	KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_ENTER,
	KEY_N, KEY_M, KEY_COMMA, KEY_DOT, KEY_SLASH, KEY_UP,
	KEY_SPACE, KEY_COMPOSE, KEY_LEFT, KEY_DOWN, KEY_RIGHT,
	KEY_LEFTCTRL, KEY_RIGHTSHIFT, KEY_LEFTSHIFT, KEY_DELETE, KEY_RIGHTCTRL, KEY_RIGHTALT,
};

static inline void jthree_decode(int fd, unsigned char event)
{
	if (event > 63)
		send_event(fd, EV_KEY, jthree_keycodes[event & 0x3f], 0);
	else
		send_event(fd, EV_KEY, jthree_keycodes[event - 1], 1);
}

int jthree_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel)
{
	unsigned char buf[16];
	struct sigaction sa;
	struct pollfd p;
	sigset_t sigs;
	char addr[18];
	int i, fd, sk, len;

	sk = rfcomm_connect(src, dst, channel);
	if (sk < 0)
		return -1;

	fd = uinput_create("J-Three Keyboard", 1, 0);
	if (fd < 0) {
		close(sk);
		return -1;
	}

	ba2str(dst, addr);

	printf("Connected to %s on channel %d\n", addr, channel);
	printf("Press CTRL-C for hangup\n");

	memset(&sa, 0, sizeof(sa));
	sa.sa_flags   = SA_NOCLDSTOP;
	sa.sa_handler = SIG_IGN;
	sigaction(SIGCHLD, &sa, NULL);
	sigaction(SIGPIPE, &sa, NULL);

	sa.sa_handler = sig_term;
	sigaction(SIGTERM, &sa, NULL);
	sigaction(SIGINT,  &sa, NULL);

	sa.sa_handler = sig_hup;
	sigaction(SIGHUP, &sa, NULL);

	sigfillset(&sigs);
	sigdelset(&sigs, SIGCHLD);
	sigdelset(&sigs, SIGPIPE);
	sigdelset(&sigs, SIGTERM);
	sigdelset(&sigs, SIGINT);
	sigdelset(&sigs, SIGHUP);

	p.fd = sk;
	p.events = POLLIN | POLLERR | POLLHUP;

	while (!__io_canceled) {
		p.revents = 0;
		if (ppoll(&p, 1, NULL, &sigs) < 1)
			continue;

		len = read(sk, buf, sizeof(buf));
		if (len < 0)
			break;

		for (i = 0; i < len; i++)
			jthree_decode(fd, buf[i]);
	}

	printf("Disconnected\n");

	ioctl(fd, UI_DEV_DESTROY);

	close(fd);
	close(sk);

	return 0;
}

static const int celluon_xlate_num[10] = {
	KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9
};

static const int celluon_xlate_char[26] = {
	KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J,
	KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T,
	KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z
};

static int celluon_xlate(int c)
{
	if (c >= '0' && c <= '9')
		return celluon_xlate_num[c - '0'];

	if (c >= 'A' && c <= 'Z')
		return celluon_xlate_char[c - 'A'];

	switch (c) {
	case 0x08:
		return KEY_BACKSPACE;
	case 0x09:
		return KEY_TAB;
	case 0x0d:
		return KEY_ENTER;
	case 0x11:
		return KEY_LEFTCTRL;
	case 0x14:
		return KEY_CAPSLOCK;
	case 0x20:
		return KEY_SPACE;
	case 0x25:
		return KEY_LEFT;
	case 0x26:
		return KEY_UP;
	case 0x27:
		return KEY_RIGHT;
	case 0x28:
		return KEY_DOWN;
	case 0x2e:
		return KEY_DELETE;
	case 0x5b:
		return KEY_MENU;
	case 0xa1:
		return KEY_RIGHTSHIFT;
	case 0xa0:
		return KEY_LEFTSHIFT;
	case 0xba:
		return KEY_SEMICOLON;
	case 0xbd:
		return KEY_MINUS;
	case 0xbc:
		return KEY_COMMA;
	case 0xbb:
		return KEY_EQUAL;
	case 0xbe:
		return KEY_DOT;
	case 0xbf:
		return KEY_SLASH;
	case 0xc0:
		return KEY_GRAVE;
	case 0xdb:
		return KEY_LEFTBRACE;
	case 0xdc:
		return KEY_BACKSLASH;
	case 0xdd:
		return KEY_RIGHTBRACE;
	case 0xde:
		return KEY_APOSTROPHE;
	case 0xff03:
		return KEY_HOMEPAGE;
	case 0xff04:
		return KEY_TIME;
	case 0xff06:
		return KEY_OPEN;
	case 0xff07:
		return KEY_LIST;
	case 0xff08:
		return KEY_MAIL;
	case 0xff30:
		return KEY_CALC;
	case 0xff1a: /* Map FN to ALT */
		return KEY_LEFTALT;
	case 0xff2f:
		return KEY_INFO;
	default:
		printf("Unknown key %x\n", c);
		return c;
	}
}

struct celluon_state {
	int len;	/* Expected length of current packet */
	int count;	/* Number of bytes received */
	int action;
	int key;
};

static void celluon_decode(int fd, struct celluon_state *s, uint8_t c)
{
	if (s->count < 2 && c != 0xa5) {
		/* Lost Sync */
		s->count = 0;
		return;
	}

	switch (s->count) {
	case 0:
		/* New packet - Reset state */
		s->len = 30;
		s->key = 0;
		break;
	case 1:
		break;
	case 6:
		s->action = c;
		break;
	case 28:
		s->key = c;
		if (c == 0xff)
			s->len = 31;
		break;
	case 29:
	case 30:
		if (s->count == s->len - 1) {
			/* TODO: Verify checksum */
			if (s->action < 2) {
				send_event(fd, EV_KEY, celluon_xlate(s->key),
								s->action);
			}
			s->count = -1;
		} else {
			s->key = (s->key << 8) | c;
		}
		break;
	}

	s->count++;

	return;
}

int celluon_keyboard(const bdaddr_t *src, const bdaddr_t *dst, uint8_t channel)
{
	unsigned char buf[16];
	struct sigaction sa;
	struct pollfd p;
	sigset_t sigs;
	char addr[18];
	int i, fd, sk, len;
	struct celluon_state s;

	sk = rfcomm_connect(src, dst, channel);
	if (sk < 0)
		return -1;

	fd = uinput_create("Celluon Keyboard", 1, 0);
	if (fd < 0) {
		close(sk);
		return -1;
	}

	ba2str(dst, addr);

	printf("Connected to %s on channel %d\n", addr, channel);
	printf("Press CTRL-C for hangup\n");

	memset(&sa, 0, sizeof(sa));
	sa.sa_flags   = SA_NOCLDSTOP;
	sa.sa_handler = SIG_IGN;
	sigaction(SIGCHLD, &sa, NULL);
	sigaction(SIGPIPE, &sa, NULL);

	sa.sa_handler = sig_term;
	sigaction(SIGTERM, &sa, NULL);
	sigaction(SIGINT,  &sa, NULL);

	sa.sa_handler = sig_hup;
	sigaction(SIGHUP, &sa, NULL);

	sigfillset(&sigs);
	sigdelset(&sigs, SIGCHLD);
	sigdelset(&sigs, SIGPIPE);
	sigdelset(&sigs, SIGTERM);
	sigdelset(&sigs, SIGINT);
	sigdelset(&sigs, SIGHUP);

	p.fd = sk;
	p.events = POLLIN | POLLERR | POLLHUP;

	memset(&s, 0, sizeof(s));

	while (!__io_canceled) {
		p.revents = 0;
		if (ppoll(&p, 1, NULL, &sigs) < 1)
			continue;

		len = read(sk, buf, sizeof(buf));
		if (len < 0)
			break;

		for (i = 0; i < len; i++)
			celluon_decode(fd, &s, buf[i]);
	}

	printf("Disconnected\n");

	ioctl(fd, UI_DEV_DESTROY);

	close(fd);
	close(sk);

	return 0;
}
