/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2004-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

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

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

#include <glib.h>
#include <dbus/dbus.h>

#include "logging.h"
#include "device.h"
#include "fakehid.h"
#include "uinput.h"

#define PS3_FLAGS_MASK 0xFFFFFF00

enum ps3remote_special_keys {
	PS3R_BIT_PS = 0,
	PS3R_BIT_ENTER = 3,
	PS3R_BIT_L2 = 8,
	PS3R_BIT_R2 = 9,
	PS3R_BIT_L1 = 10,
	PS3R_BIT_R1 = 11,
	PS3R_BIT_TRIANGLE = 12,
	PS3R_BIT_CIRCLE = 13,
	PS3R_BIT_CROSS = 14,
	PS3R_BIT_SQUARE = 15,
	PS3R_BIT_SELECT = 16,
	PS3R_BIT_L3 = 17,
	PS3R_BIT_R3 = 18,
	PS3R_BIT_START = 19,
	PS3R_BIT_UP = 20,
	PS3R_BIT_RIGHT = 21,
	PS3R_BIT_DOWN = 22,
	PS3R_BIT_LEFT = 23,
};

static unsigned int ps3remote_bits[] = {
	[PS3R_BIT_ENTER] = 0x0b,
	[PS3R_BIT_PS] = 0x43,
	[PS3R_BIT_SQUARE] = 0x5f,
	[PS3R_BIT_CROSS] = 0x5e,
	[PS3R_BIT_CIRCLE] = 0x5d,
	[PS3R_BIT_TRIANGLE] = 0x5c,
	[PS3R_BIT_R1] = 0x5b,
	[PS3R_BIT_L1] = 0x5a,
	[PS3R_BIT_R2] = 0x59,
	[PS3R_BIT_L2] = 0x58,
	[PS3R_BIT_LEFT] = 0x57,
	[PS3R_BIT_DOWN] = 0x56,
	[PS3R_BIT_RIGHT] = 0x55,
	[PS3R_BIT_UP] = 0x54,
	[PS3R_BIT_START] = 0x53,
	[PS3R_BIT_R3] = 0x52,
	[PS3R_BIT_L3] = 0x51,
	[PS3R_BIT_SELECT] = 0x50,
};

static unsigned int ps3remote_keymap[] = {
	[0x16] = KEY_EJECTCD,
	[0x64] = KEY_AUDIO,
	[0x65] = KEY_ANGLE,
	[0x63] = KEY_SUBTITLE,
	[0x0f] = KEY_CLEAR,
	[0x28] = KEY_TIME,
	[0x00] = KEY_1,
	[0x01] = KEY_2,
	[0x02] = KEY_3,
	[0x03] = KEY_4,
	[0x04] = KEY_5,
	[0x05] = KEY_6,
	[0x06] = KEY_7,
	[0x07] = KEY_8,
	[0x08] = KEY_9,
	[0x09] = KEY_0,
	[0x81] = KEY_RED,
	[0x82] = KEY_GREEN,
	[0x80] = KEY_BLUE,
	[0x83] = KEY_YELLOW,
	[0x70] = KEY_INFO,		/* display */
	[0x1a] = KEY_MENU,		/* top menu */
	[0x40] = KEY_CONTEXT_MENU,	/* pop up/menu */
	[0x0e] = KEY_ESC,		/* return */
	[0x5c] = KEY_OPTION,		/* options/triangle */
	[0x5d] = KEY_BACK,		/* back/circle */
	[0x5f] = KEY_SCREEN,		/* view/square */
	[0x5e] = BTN_0,			/* cross */
	[0x54] = KEY_UP,
	[0x56] = KEY_DOWN,
	[0x57] = KEY_LEFT,
	[0x55] = KEY_RIGHT,
	[0x0b] = KEY_ENTER,
	[0x5a] = BTN_TL,		/* L1 */
	[0x58] = BTN_TL2,		/* L2 */
	[0x51] = BTN_THUMBL,		/* L3 */
	[0x5b] = BTN_TR,		/* R1 */
	[0x59] = BTN_TR2,		/* R2 */
	[0x52] = BTN_THUMBR,		/* R3 */
	[0x43] = KEY_HOMEPAGE,		/* PS button */
	[0x50] = KEY_SELECT,
	[0x53] = BTN_START,
	[0x33] = KEY_REWIND,		/* scan back */
	[0x32] = KEY_PLAY,
	[0x34] = KEY_FORWARD,		/* scan forward */
	[0x30] = KEY_PREVIOUS,
	[0x38] = KEY_STOP,
	[0x31] = KEY_NEXT,
	[0x60] = KEY_FRAMEBACK,		/* slow/step back */
	[0x39] = KEY_PAUSE,
	[0x61] = KEY_FRAMEFORWARD,	/* slow/step forward */
	[0xff] = KEY_MAX,
};

static int ps3remote_decode(char *buff, int size, unsigned int *value)
{
	static unsigned int lastkey = 0;
	static unsigned int lastmask = 0;
	int retval, mask, i;
	guint8 key;

	if (size < 12) {
		error("Got a shorter packet! (size %i)\n", size);
		return KEY_RESERVED;
	}

	mask = (buff[2] << 16) + (buff[3] << 8) + buff[4];
	key = buff[5];

	/* first, check flags */
	for (i = 0; i < 24; i++) {
		if ((lastmask & (1 << i)) == (mask & (1 << i)))
			continue;
		if (ps3remote_bits[i] == 0)
			goto error;
		retval = ps3remote_keymap[ps3remote_bits[i]];
		if (mask & (1 << i))
			/* key pressed */
			*value = 1;
		else
			/* key released */
			*value = 0;

		goto out;
	}

	*value = buff[11];
	if (buff[11] == 1) {
		retval = ps3remote_keymap[key];
	} else
		retval = lastkey;

	if (retval == KEY_RESERVED)
		goto error;
	if (retval == KEY_MAX)
		return retval;

	lastkey = retval;

out:
	fflush(stdout);

	lastmask = mask;

	return retval;

error:
	error("ps3remote: unrecognized sequence [%#x][%#x][%#x][%#x] [%#x],"
			"last: [%#x][%#x][%#x][%#x]",
			buff[2], buff[3], buff[4], buff[5], buff[11],
				lastmask >> 16, lastmask >> 8 & 0xff,
						lastmask & 0xff, lastkey);
	return -1;
}

static gboolean ps3remote_event(GIOChannel *chan, GIOCondition cond,
				gpointer data)
{
	struct fake_input *fake = data;
	struct uinput_event event;
	unsigned int key, value = 0;
	gsize size;
	char buff[50];

	if (cond & G_IO_NVAL)
		return FALSE;

	if (cond & (G_IO_HUP | G_IO_ERR)) {
		error("Hangup or error on rfcomm server socket");
		goto failed;
	}

	memset(buff, 0, sizeof(buff));

	if (g_io_channel_read(chan, buff, sizeof(buff), &size) !=
							G_IO_ERROR_NONE) {
		error("IO Channel read error");
		goto failed;
	}

	key = ps3remote_decode(buff, size, &value);
	if (key == KEY_RESERVED) {
		error("Got invalid key from decode");
		goto failed;
	} else if (key == KEY_MAX)
		return TRUE;

	memset(&event, 0, sizeof(event));
	gettimeofday(&event.time, NULL);
	event.type = EV_KEY;
	event.code = key;
	event.value = value;
	if (write(fake->uinput, &event, sizeof(event)) != sizeof(event)) {
		error("Error writing to uinput device");
		goto failed;
	}

	memset(&event, 0, sizeof(event));
	gettimeofday(&event.time, NULL);
	event.type = EV_SYN;
	event.code = SYN_REPORT;
	if (write(fake->uinput, &event, sizeof(event)) != sizeof(event)) {
		error("Error writing to uinput device");
		goto failed;
	}

	return TRUE;

failed:
	ioctl(fake->uinput, UI_DEV_DESTROY);
	close(fake->uinput);
	fake->uinput = -1;
	g_io_channel_unref(fake->io);

	return FALSE;
}

static int ps3remote_setup_uinput(struct fake_input *fake,
				  struct fake_hid *fake_hid)
{
	struct uinput_dev dev;
	int i;

	fake->uinput = open("/dev/input/uinput", O_RDWR);
	if (fake->uinput < 0) {
		fake->uinput = open("/dev/uinput", O_RDWR);
		if (fake->uinput < 0) {
			fake->uinput = open("/dev/misc/uinput", O_RDWR);
			if (fake->uinput < 0) {
				error("Error opening uinput device file");
				return 1;
			}
		}
	}

	memset(&dev, 0, sizeof(dev));
	snprintf(dev.name, sizeof(dev.name), "%s", "PS3 Remote Controller");
	dev.id.bustype = BUS_BLUETOOTH;
	dev.id.vendor = fake_hid->vendor;
	dev.id.product = fake_hid->product;

	if (write(fake->uinput, &dev, sizeof(dev)) != sizeof(dev)) {
		error("Error creating uinput device");
		goto err;
	}

	/* enabling key events */
	if (ioctl(fake->uinput, UI_SET_EVBIT, EV_KEY) < 0) {
		error("Error enabling uinput device key events");
		goto err;
	}

	/* enabling keys */
	for (i = 0; i < 256; i++)
		if (ps3remote_keymap[i] != KEY_RESERVED)
			if (ioctl(fake->uinput, UI_SET_KEYBIT,
						ps3remote_keymap[i]) < 0) {
				error("Error enabling uinput key %i",
							ps3remote_keymap[i]);
				goto err;
			}

	/* creating the device */
	if (ioctl(fake->uinput, UI_DEV_CREATE) < 0) {
		error("Error creating uinput device");
		goto err;
	}

	return 0;

err:
	close(fake->uinput);
	return 1;
}

static gboolean fake_hid_common_connect(struct fake_input *fake)
{
	return TRUE;
}

static int fake_hid_common_disconnect(struct fake_input *fake)
{
	return 0;
}

static struct fake_hid fake_hid_table[] = {
	/* Sony PS3 remote device */
	{
		.vendor		= 0x054c,
		.product	= 0x0306,
		.connect	= fake_hid_common_connect,
		.disconnect	= fake_hid_common_disconnect,
		.event		= ps3remote_event,
		.setup_uinput	= ps3remote_setup_uinput,
	},

	{ },
};

static inline int fake_hid_match_device(uint16_t vendor, uint16_t product,
							struct fake_hid *fhid)
{
	return vendor == fhid->vendor && product == fhid->product;
}

struct fake_hid *get_fake_hid(uint16_t vendor, uint16_t product)
{
	int i;

	for (i = 0; fake_hid_table[i].vendor != 0; i++)
		if (fake_hid_match_device(vendor, product, &fake_hid_table[i]))
			return &fake_hid_table[i];

	return NULL;
}

int fake_hid_connadd(struct fake_input *fake, int intr_sk,
						struct fake_hid *fake_hid)
{
	if (fake_hid->setup_uinput(fake, fake_hid)) {
		error("Error setting up uinput");
		return ENOMEM;
	}

	fake->io = g_io_channel_unix_new(intr_sk);
	g_io_channel_set_close_on_unref(fake->io, TRUE);
	g_io_add_watch(fake->io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
					(GIOFunc) fake_hid->event, fake);

	return 0;
}
