/*
 * Copyright (c) 2010, Gražvydas Ignotas
 * 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.
 *     * Neither the name of the organization nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <linux/input.h>
#include <tslib.h>

#include "font.c"

#define array_size(x) (sizeof(x) / sizeof(x[0]))

typedef struct {
	int x, y;
	int code;
	int active;
	const char *name;
} key_item;

static key_item key_items[] =
{
	{  66,  26, KEY_1, 0, "1" },
	{ 133,  26, KEY_2, 0, "2" },
	{ 200,  26, KEY_3, 0, "3" },
	{ 266,  26, KEY_4, 0, "4" },
	{ 333,  26, KEY_5, 0, "5" },
	{ 400,  26, KEY_6, 0, "6" },
	{ 466,  26, KEY_7, 0, "7" },
	{ 533,  26, KEY_8, 0, "8" },
	{ 600,  26, KEY_9, 0, "9" },
	{ 666,  26, KEY_0, 0, "0" },
	{ 733,  26, KEY_BACKSPACE, 0, "DEL" },
	//
	{  10,  55, KEY_RIGHTSHIFT,0, "L" },
	{  10,  75, KEY_KPPLUS,    0, "L2" },
	{ 758,  55, KEY_RIGHTCTRL, 0, "R" },
	{ 758,  75, KEY_KPMINUS,   0, "R2" },
	{  10, 220, KEY_POWER,     0, "POWER" },
	{  10, 240, KEY_COFFEE,    0, "HOLD" },
	//
	{ 130,  94, KEY_UP,     0, "U" },
	{  80, 163, KEY_LEFT,   0, "L" },
	{ 180, 163, KEY_RIGHT,  0, "R" },
	{ 130, 232, KEY_DOWN,   0, "D" },
	{ 376,  94, KEY_LEFTALT,0, "ALT" },
	{ 368, 163, KEY_LEFTCTRL,0,"CTRL" },
	{ 368, 232, KEY_MENU,   0, "MENU" },
	{ 700,  94, KEY_PAGEUP, 0, "1" },
	{ 650, 163, KEY_HOME,   0, "4" },
	{ 750, 163, KEY_END,    0, "2" },
	{ 700, 232, KEY_PAGEDOWN,0, "3" },
	//
	{  92, 300, KEY_Q,      0, "Q" },
	{ 158, 300, KEY_W,      0, "W" },
	{ 225, 300, KEY_E,      0, "E" },
	{ 292, 300, KEY_R,      0, "R" },
	{ 358, 300, KEY_T,      0, "T" },
	{ 425, 300, KEY_Y,      0, "Y" },
	{ 492, 300, KEY_U,      0, "U" },
	{ 558, 300, KEY_I,      0, "I" },
	{ 625, 300, KEY_O,      0, "O" },
	{ 692, 300, KEY_P,      0, "P" },
	//
	{  30, 369, KEY_LEFTSHIFT, 0, "SHIFT" },
	{ 133, 369, KEY_A,      0, "A" },
	{ 200, 369, KEY_S,      0, "S" },
	{ 266, 369, KEY_D,      0, "D" },
	{ 333, 369, KEY_F,      0, "F" },
	{ 400, 369, KEY_G,      0, "G" },
	{ 466, 369, KEY_H,      0, "H" },
	{ 533, 369, KEY_J,      0, "J" },
	{ 600, 369, KEY_K,      0, "K" },
	{ 666, 369, KEY_L,      0, "L" },
	{ 710, 369, KEY_ENTER,  0, "ENTER" },
	//
	{  25, 437, KEY_COMMA,  0, "," },
	{  92, 437, KEY_DOT,    0, "." },
	{ 158, 437, KEY_Z,      0, "Z" },
	{ 225, 437, KEY_X,      0, "X" },
	{ 292, 437, KEY_C,      0, "C" },
	{ 358, 437, KEY_V,      0, "V" },
	{ 425, 437, KEY_B,      0, "B" },
	{ 492, 437, KEY_N,      0, "N" },
	{ 558, 437, KEY_M,      0, "M" },
	{ 625, 437, KEY_SPACE,  0, "SPACE" },
	{ 758, 437, KEY_FN,     0, "Fn" },
};

#define KI_COUNT (sizeof(key_items) / sizeof(key_items[0]))

static int ts_old_x = 0, ts_old_y = 0;
static int lid_closed = 0;

static void draw_ts_cross(unsigned short *fb, unsigned short col, int x, int y)
{
	int i, x1, y1;

	if (y >= 0 && y < 480)
		for (x1 = x - 5, i = 0; i < 11; i++, x1++)
			if (x1 >= 0 && x1 < 800) fb[y * 800 + x1] = col;
	
	if (x >= 0 && x < 800)
		for (y1 = y - 5, i = 0; i < 11; i++, y1++)
			if (y1 >= 0 && y1 < 480) fb[y1 * 800 + x] = col;
	
	ts_old_x = x;
	ts_old_y = y;
}

static void text_out16(unsigned short *fb, int x, int y, unsigned short col, const char *text)
{
	int i,l;

	fb = fb + x + y*800;

	for (i = 0; i < strlen(text); i++)
	{
		for (l=0;l<8;l++)
		{
			#define pix(fdmask,add) \
				if (fontdata8x8[((text[i])*8)+l]&fdmask) \
					fb[l*2*800+add]=fb[l*2*800+add+1]=fb[l*2*800+800+add]=fb[l*2*800+800+add+1]=col
			pix(0x80,  0);
			pix(0x40,  2);
			pix(0x20,  4);
			pix(0x10,  6);
			pix(0x08,  8);
			pix(0x04, 10);
			pix(0x02, 12);
			pix(0x01, 14);
			#undef pix
		}
		fb += 8*2;
	}
}

static void text_out16_small(unsigned short *fb, int x, int y, unsigned short col, const char *text)
{
	int i,l;

	fb = fb + x + y*800;

	for (i = 0; i < strlen(text); i++)
	{
		for (l=0;l<8;l++)
		{
			#define pix(fdmask,add) \
				if (fontdata8x8[((text[i])*8)+l]&fdmask) \
					fb[l*800+add]=fb[l*800+800+add]=col
			pix(0x80,  0);
			pix(0x40,  1);
			pix(0x20,  2);
			pix(0x10,  3);
			pix(0x08,  4);
			pix(0x04,  5);
			pix(0x02,  6);
			pix(0x01,  7);
			#undef pix
		}
		fb += 8;
	}
}

static void redraw_nubs(unsigned short *fb, int x1, int y1, int x2, int y2)
{
	char buff[32];
	int y;

	fb += 90*800;

	// clear areas
	for (y = 0; y < 140; y++) {
		memset(fb + 800*y + 200, 0, (64+8)*2*2);
		memset(fb + 800*y + 450, 0, (64+8)*2*2);
	}

	text_out16(fb + 200, 32*2 + x1/8, 32*2 + y1/8, 0x001f, "@");
	text_out16(fb + 450, 32*2 + x2/8, 32*2 + y2/8, 0x001f, "@");

	snprintf(buff, sizeof(buff), "%2i, %2i", x1/8, y1/8);
	text_out16_small(fb, 245, 130, 0x7bef, buff);
	snprintf(buff, sizeof(buff), "%2i, %2i", x2/8, y2/8);
	text_out16_small(fb, 495, 130, 0x7bef, buff);
}

static void redraw_keys_lid(unsigned short *fb)
{
	const key_item *key;
	int i;

	for (i = 0; i < KI_COUNT; i++)
	{
		key = &key_items[i];

		text_out16(fb, key->x, key->y,
			key->active ? 0x07e0 : 0x7bef, key->name);
	}

	text_out16(fb, 10, 260, lid_closed ? 0x07e0 : 0x7bef, "LID");
}

static void set_key(int code, int val)
{
	key_item *key = NULL;
	int i;

	for (i = 0; i < KI_COUNT; i++)
	{
		if (key_items[i].code == code)
		{
			key = &key_items[i];
			break;
		}
	}

	if (key == NULL)
	{
		printf("%c unexpected key? (%i)\n", val ? '+' : '-', code);
	}
	else
	{
		key->active = val;
		printf("%c %s\n", val ? '+' : '-', key->name);
	}
}

/* sound test */
#include <sys/soundcard.h>
#include <pthread.h>

static int snd_test_l, snd_test_r, snd_test_quit;
static pthread_cond_t snd_cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t snd_mutex = PTHREAD_MUTEX_INITIALIZER;

static void *sound_thread(void *arg)
{
	int i, ret, randfd, dspfd;
	int frag, bits, stereo, rate;
	short buf[8*1024];//2*22050 / 10];

	randfd = open("/dev/urandom", O_RDONLY);
	if (randfd == -1) {
		perror("open(\"/dev/urandom\")");
		return NULL;
	}

	dspfd = open("/dev/dsp", O_WRONLY);
	if (dspfd == -1) {
		perror("open(\"/dev/dsp\")");
		close(randfd);
		return NULL;
	}

	frag = (2 << 16) | 13;
	ret = ioctl(dspfd, SNDCTL_DSP_SETFRAGMENT, &frag);
	if (ret < 0)
		perror("SNDCTL_DSP_SETFRAGMENT");

	stereo = 1; bits = 16; rate = 22050;
	ret = ioctl(dspfd, SNDCTL_DSP_STEREO, &stereo);
	if (ret == 0)
		ret = ioctl(dspfd, SNDCTL_DSP_SETFMT, &bits);
	if (ret == 0)
		ret = ioctl(dspfd, SNDCTL_DSP_SPEED, &rate);
	if (ret < 0)
		perror("failed to set audio format");

	while (1) {
		pthread_mutex_lock(&snd_mutex);
		if (!snd_test_l && !snd_test_r)
			pthread_cond_wait(&snd_cond, &snd_mutex);
		pthread_mutex_unlock(&snd_mutex);

		if (snd_test_quit)
			break;

		ret = read(randfd, buf, sizeof(buf));
		if (ret == -1) {
			perror("urandom read");
			break;
		}

		if (!snd_test_l)
			for (i = 0; i < array_size(buf) / 2; i++)
				buf[i * 2] = 0;
		if (!snd_test_r)
			for (i = 0; i < array_size(buf) / 2; i++)
				buf[i * 2 + 1] = 0;

		ret = write(dspfd, buf, sizeof(buf));
		if (ret == -1) {
			perror("dsp write");
			break;
		}
	}

	close(randfd);
	close(dspfd);

	return NULL;
}

static void sound_init(void)
{
	pthread_t tid;
	int ret;

	ret = pthread_create(&tid, NULL, sound_thread, NULL);
	if (ret != 0) {
		fprintf(stderr, "pthread_create sound_thread: %d\n", ret);
		return;
	}
	pthread_detach(tid);
}

static void sound_do(int l, int r)
{
	pthread_mutex_lock(&snd_mutex);
	snd_test_l = l;
	snd_test_r = r;
	if (l || r || snd_test_quit)
		pthread_cond_signal(&snd_cond);
	pthread_mutex_unlock(&snd_mutex);
}

enum {
	DEV_PWRBTN,
	DEV_KEYPAD,
	DEV_BUTTONS,
	DEV_TS,
	DEV_LNUB,
	DEV_RNUB,
	DEVS_TOTAL
};

int main(int argc, char *argv[])
{
	unsigned short *screen;
	int fbdev, ifd[DEVS_TOTAL] = { -1, -1, -1, -1, -1, -1 };
	int i, id, imaxfd = 0, ts_x = 0, ts_y = 0;
	int nubx[2] = {0,0}, nuby[2] = {0,0};
	int pressed_l = 0, pressed_r = 0;
	struct tsdev *ts = NULL;

	fbdev = open("/dev/fb0", O_RDWR);
	if (fbdev == -1)
	{
		perror("open(\"/dev/fb0\") failed");
		return 1;
	}

	screen = mmap(0, 800*480*2, PROT_WRITE|PROT_READ, MAP_SHARED, fbdev, 0);
	if (screen == MAP_FAILED)
	{
		perror("mmap(fbptr) failed");
		return 1;
	}

	memset(screen, 0, 800*480*2);

	sound_init();

	for (id = 0; ; id++)
	{
		char fname[64];
		char name[256] = { 0, };
		int fd;

		snprintf(fname, sizeof(fname), "/dev/input/event%i", id);
		fd = open(fname, O_RDONLY);
		if (fd == -1)
		{
			break;
		}

		ioctl(fd, EVIOCGNAME(sizeof(name)), name);

		if (strcasestr(name, "power") != NULL)
		{
			ifd[DEV_PWRBTN] = fd;
		}
		else if (strcasestr(name, "keypad") != NULL)
		{
			ifd[DEV_KEYPAD] = fd;
		}
		else if (strcmp(name, "gpio-keys") == 0)
		{
			ifd[DEV_BUTTONS] = fd;
		}
		else if (strcasestr(name, "touchscreen") != NULL)
		{
			close(fd);
			ts = ts_open(fname, 0);
			if (ts == NULL)
			{
				perror("ts_open");
				goto end;
			}
			if (ts_config(ts))
			{
				perror("ts_config");
				goto end;
			}
			ifd[DEV_TS] = ts_fd(ts);
		}
		else if (strcmp(name, "nub0") == 0)
		{
			ifd[DEV_LNUB] = fd;
		}
		else if (strcmp(name, "nub1") == 0)
		{
			ifd[DEV_RNUB] = fd;
		}
		else
		{
			printf("skipping \"%s\"\n", name);
			close(fd);
			continue;
		}
		if (imaxfd < fd) imaxfd = fd;
	}

	if (ifd[DEV_PWRBTN]  == -1) printf("Warning: couldn't find pwrbutton device\n");
	if (ifd[DEV_KEYPAD]  == -1) printf("Warning: couldn't find keypad device\n");
	if (ifd[DEV_BUTTONS] == -1) printf("Warning: couldn't find button device\n");
	if (ifd[DEV_TS]      == -1) printf("Warning: couldn't find touchscreen device\n");
	if (ifd[DEV_LNUB]    == -1) printf("Warning: couldn't find nub1 device\n");
	if (ifd[DEV_RNUB]    == -1) printf("Warning: couldn't find nub2 device\n");

#ifdef VALIDATION
	text_out16_small(screen, 220, 3, 0x7bef, "press menu/pandora to shutdown, alt/start to exit");
#else
	text_out16_small(screen, 320, 3, 0x7bef, "Press L+R to exit");
#endif

	while (!pressed_l || !pressed_r)
	{
		struct input_event ev[64];
		int fd = -1, rd, which, ret;
		fd_set fdset;

		draw_ts_cross(screen, 0x0000, ts_old_x, ts_old_y);
		redraw_keys_lid(screen);
		redraw_nubs(screen, nubx[0], nuby[0], nubx[1], nuby[1]);
		draw_ts_cross(screen, 0xf800, ts_x, ts_y);

		FD_ZERO(&fdset);
		for (i = 0; i < DEVS_TOTAL; i++)
			if (ifd[i] != -1)
				FD_SET(ifd[i], &fdset);

		ret = select(imaxfd + 1, &fdset, NULL, NULL, NULL);
		if (ret == -1)
		{
			perror("select");
			break;
		}

		for (i = 0; i < DEVS_TOTAL; i++)
			if (ifd[i] != -1 && FD_ISSET(ifd[i], &fdset))
				fd = ifd[i];

		/* touch event? */
		if (fd == ifd[DEV_TS])
		{
			struct ts_sample samp;

			ret = ts_read(ts, &samp, 1);
			if (ret < 0) {
				perror("ts_read");
				break;
			}

			if (ret != 1)
				continue;

			ts_x = samp.x; ts_y = samp.y;
			//printf("ts: %6d %6d %6d\n", samp.x, samp.y, samp.pressure);
			continue;
		}

		/* buttons or keypad */
		rd = read(fd, ev, sizeof(ev));
		if (rd < (int) sizeof(ev[0])) {
			perror("\nevtest: error reading");
			break;
		}

		for (i = 0; i < rd / sizeof(ev[0]); i++)
		{
			switch (ev[i].type) {
			case EV_SYN:
				break;
			case EV_KEY:
				set_key(ev[i].code, ev[i].value);
#ifndef VALIDATION
				if (ev[i].code == KEY_RIGHTSHIFT)
					pressed_l = !!ev[i].value;
				if (ev[i].code == KEY_RIGHTCTRL)
					pressed_r = !!ev[i].value;
#else
				if (ev[i].code == KEY_LEFTALT && !!ev[i].value)
					goto end;
				if (ev[i].code == KEY_MENU && !!ev[i].value) {
					sync();
					system("poweroff -f");
					goto end;
				}
#endif
				if (ev[i].code == KEY_LEFT)
					sound_do(!!ev[i].value, snd_test_r);
				if (ev[i].code == KEY_RIGHT)
					sound_do(snd_test_l, !!ev[i].value);
				break;
			case EV_ABS:
				which = (fd == ifd[DEV_LNUB]) ? 0 : 1;
				if (ev[i].code == ABS_X)
					nubx[which] = ev[i].value;
				else if (ev[i].code == ABS_Y)
					nuby[which] = ev[i].value;
				else
					printf("unexpected EV_ABS code: %i\n", ev[i].code);
				break;
			case EV_SW:
				if (ev[i].code == SW_LID)
					lid_closed = ev[i].value;
				else
					printf("unexpected EV_SW code: %i\n", ev[i].code);
				break;
			case EV_MSC:
				if (ev[i].code == MSC_SCAN)
					break;
				/* fallthrough */
			default:
				printf("unexpected event: type %i, code %d\n", ev[i].type, ev[i].code);
				break;
			}
		}
	}

	snd_test_quit = 1;
	sound_do(0, 0);

end:
	if (ts != NULL)
		ts_close(ts);
	for (i = 0; i < DEVS_TOTAL; i++)
		if (i != DEV_TS && ifd[i] != -1)
			close(ifd[i]);
	munmap(screen, 800*480*2);
	close(fbdev);
	return 0;
}

