/*
 * 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 <string.h>
#include <stdlib.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <linux/input.h>
#include <linux/matroxfb.h>


#define SPLIT 12

#define DO_RGB(type,d,after,r,g,b) { \
	int i, u, c, l, div = (w << 8) / SPLIT; \
	char lines[8][dotsz * w]; \
	type *dest; \
	for (dest = (void *)lines[0], i = 0; i < w; i++) { \
		d = (i * (1 << r) / w) << (g+b); \
		after; \
	} \
	for (dest = (void *)lines[1], i = 0; i < w; i++) { \
		d = (i * (1 << g) / w) << b; \
		after; \
	} \
	for (dest = (void *)lines[2], i = 0; i < w; i++) { \
		d = (i * (1 << b) / w); \
		after; \
	} \
	for (dest = (void *)lines[3], i = 0; i < w; i++) { \
		d = ((i * (1 << r) / w) << (g+b)) | \
		    ((i * (1 << g) / w) << b) | \
		    (i * (1 << b) / w); \
		after; \
	} \
\
	for (dest = (void *)lines[4], i = 0; i < w; i++) { \
		c = (((i << 8) / div + 1) * div) >> 8; \
		d = ((c * (1 << r) / w) << (g+b)) | \
		    ((c * (1 << g) / w) << b) | \
		    (c * (1 << b) / w); \
		after; \
	} \
	for (dest = (void *)lines[5], i = 0; i < w; i++) { \
		c = (((i << 8) / div + 1) * div) >> 8; \
		d = (c * (1 << b) / w); \
		after; \
	} \
	for (dest = (void *)lines[6], i = 0; i < w; i++) { \
		c = (((i << 8) / div + 1) * div) >> 8; \
		d = (c * (1 << g) / w) << b; \
		after; \
	} \
	for (dest = (void *)lines[7], i = 0; i < w; i++) { \
		c = (((i << 8) / div + 1) * div) >> 8; \
		d = (c * (1 << r) / w) << (g+b); \
		after; \
	} \
\
	for (u = 0; u < h; u++) { \
		l = u / (h / 8); \
		memcpy((char *)ptr + u * dotsz * w, lines[l], dotsz * w); \
	} \
}

#include "font.c"

static void text_out(void *fbi, int x, int y, int dotsz, int stride, const char *text)
{
	int i, l, v = -1;
	char *fb;

	fb = (char *)fbi + x * dotsz + y * stride;

	for (i = 0; i < strlen(text); i++)
	{
		for (l=0;l<8;l++)
		{
			#define pix(fdmask,add) \
				if (fontdata8x8[((text[i])*8)+l]&fdmask) \
					memcpy(fb + l*stride + add*dotsz, &v, dotsz)
			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 += dotsz * 8;
	}
}

static void get_config(char *buff, size_t size, int just_dir)
{
	char *home;

	home = getenv("HOME");
	if (home == NULL)
		home = "";
	if (just_dir)
		snprintf(buff, size, "%s/.config", home);
	else
		snprintf(buff, size, "%s/.config/op_gammatool", home);
}

static int do_load(int *gamma)
{
	int ret, *g = gamma;
	char buff[128];
	FILE *f;

	get_config(buff, sizeof(buff), 0);
	f = fopen(buff, "r");
	if (f == NULL)
		return -1;
	ret = fscanf(f, "%d %d %d %d %d %d %d %d %d %d %d %d\n",
		&g[0], &g[1], &g[2], &g[3], &g[4],  &g[5],
		&g[6], &g[7], &g[8], &g[9], &g[10], &g[11]);
	fclose(f);

	return (ret == 12) ? 0 : -1;
}

static int do_save(const int *gamma)
{
	const int *g = gamma;
	char buff[128];
	FILE *f;
	int ret;

	get_config(buff, sizeof(buff), 1);
	mkdir(buff, 0644);
	get_config(buff, sizeof(buff), 0);
	f = fopen(buff, "w");
	if (f == NULL)
		return -1;
	ret = fprintf(f, "%d %d %d %d %d %d %d %d %d %d %d %d\n",
		g[0], g[1], g[2], g[3], g[4],  g[5],
		g[6], g[7], g[8], g[9], g[10], g[11]);
	fclose(f);

	return (ret > 0) ? 0 : -1;
}

int main(int argc, char *argv[])
{
	const char *msg_once = "Start - exit, Y - change bpp, A/B - save/load, "
				"hold R for faster tuning";
	int pressed_up = 0, pressed_down = 0, pressed_r = 0;
	struct fb_var_screeninfo fbvar;
	int fbdev, ifd, ret, sel = 0;
	int i, w, h, dotsz = 0, stride;
	int gamma[12] = { 0, };
	char buff[64];
	FILE *f;
	void *ptr;

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

	ret = ioctl(fbdev, FBIOGET_VSCREENINFO, &fbvar);
	if (ret == -1) {
		perror("ioctl(FBIOGET_FSCREENINFO) failed");
		return 1;
	}

	if (argv[1] != NULL) {
		fbvar.bits_per_pixel = atoi(argv[1]);
		ret = ioctl(fbdev, FBIOPUT_VSCREENINFO, &fbvar);
		if (ret == -1)
		{
			perror("ioctl FBIOPUT_VSCREENINFO");
		}
	}

	printf("visible resolution: %i %i\n", fbvar.xres, fbvar.yres);
	printf("virtual resolution: %i %i\n", fbvar.xres_virtual, fbvar.yres_virtual);
	printf("offset from virtual to visible: %i %i\n", fbvar.xoffset, fbvar.yoffset);
	printf("bits_per_pixel: %i\n", fbvar.bits_per_pixel);

	ptr = mmap(0, fbvar.xres * fbvar.yres * 4,
			PROT_WRITE|PROT_READ, MAP_SHARED, fbdev, 0);
	if (ptr == MAP_FAILED)
		ptr = mmap(0, fbvar.xres * fbvar.yres * fbvar.bits_per_pixel / 8,
				PROT_WRITE|PROT_READ, MAP_SHARED, fbdev, 0);
	if (ptr == MAP_FAILED)
	{
		perror("mmap(fbptr) failed");
		return 1;
	}

	w = fbvar.xres;
	h = fbvar.yres;

	// search for gpio keys
	for (ifd = -1, i = 0; ; i++) {
		snprintf(buff, sizeof(buff), "/dev/input/event%i", i);
		ifd = open(buff, O_RDONLY | O_NONBLOCK);
		if (ifd == -1)
			break;

		ioctl(ifd, EVIOCGNAME(sizeof(buff)), buff);

		if (strcasestr(buff, "gpio") != NULL)
			break;
		close(ifd);
	}

	ret = 0;
	f = fopen("/sys/devices/platform/omap2_mcspi.1/spi1.1/gamma", "r+");
	if (f != NULL) {
		int *g = gamma;
		rewind(f);
		ret = fscanf(f, "%d %d %d %d %d %d %d %d %d %d %d %d\n",
			&g[0], &g[1], &g[2], &g[3], &g[4],  &g[5],
			&g[6], &g[7], &g[8], &g[9], &g[10], &g[11]);
	}

	if (ret != 12) {
		fprintf(stderr, "gamma file missing or bad\n");
		goto out;
	}

	while (1)
	{
		if (f != NULL) {
			int *g = gamma;
			rewind(f);
			fscanf(f, "%d %d %d %d %d %d %d %d %d %d %d %d\n",
				&g[0], &g[1], &g[2], &g[3], &g[4],  &g[5],
				&g[6], &g[7], &g[8], &g[9], &g[10], &g[11]);
		}

		//ioctl(fbdev, FBIO_WAITFORVSYNC, &ret);
		if (fbvar.bits_per_pixel == 16)
		{
			dotsz = 2;
			DO_RGB(unsigned short, dest[i],, 5, 6, 5);
		}
		else if (fbvar.bits_per_pixel == 24)
		{
			// BGR?
			unsigned char *p;
			unsigned int d = 0;
			dotsz = 3;
			DO_RGB(unsigned char, d, p = &dest[i*3]; p[0] = d; p[1] = d >> 8; p[2] = d >> 16, 8, 8, 8);
		}
		else if (fbvar.bits_per_pixel == 32)
		{
			dotsz = 4;
			DO_RGB(unsigned int, dest[i],, 8, 8, 8);
		}
		else
		{
			fprintf(stderr, "unhandled bpp: %d\n", fbvar.bits_per_pixel);
			goto out;
		}
		stride = dotsz * w;

		snprintf(buff, sizeof(buff), "%dbpp", fbvar.bits_per_pixel);
		text_out(ptr, 8, 10, dotsz, stride, buff);
		if (msg_once != NULL) {
			text_out(ptr, 80, 10, dotsz, stride, msg_once);
			msg_once = NULL;
		}
		for (i = 0; i < 12; i++) {
			int x = i * w / 12 + 8;
			if (i == sel)
				text_out(ptr, x, h - 18, dotsz, stride, "____");
			snprintf(buff, sizeof(buff), "%d", gamma[i]);
			text_out(ptr, x, h - 20, dotsz, stride, buff);
		}

		// do input
		if (ifd == -1)
			break;

		while (1)
		{
			struct input_event ev;
			ret = read(ifd, &ev, sizeof(ev));
			if (ret < (int) sizeof(ev)) {
				if (errno == EAGAIN || errno == EWOULDBLOCK)
					goto input_no_change;

				perror("evtest: read error");
				goto out;
			}

			if (ev.type != EV_KEY)
				continue;

			switch (ev.code) {
			case KEY_LEFT:
				if (ev.value)
					sel--;
				if (sel < 0)
					sel = 11;
				goto input_done;
			case KEY_RIGHT:
				if (ev.value)
					sel++;
				if (sel > 11)
					sel = 0;
				goto input_done;
			case KEY_UP:
				pressed_up = ev.value;
				break;
			case KEY_DOWN:
				pressed_down = ev.value;
				break;
			case KEY_RIGHTCTRL:
			case BTN_TR:
				pressed_r = ev.value;
				break;
			case KEY_PAGEUP:
			case BTN_BASE:
				if (ev.value) {
					fbvar.bits_per_pixel += 8;
					if (fbvar.bits_per_pixel > 32)
						fbvar.bits_per_pixel = 16;
					ret = ioctl(fbdev, FBIOPUT_VSCREENINFO, &fbvar);
					if (ret == -1)
						perror("ioctl FBIOPUT_VSCREENINFO");
				}
				goto input_done;
			case KEY_END:
			case BTN_BASE2:
				if (!ev.value)
					break;
				if (do_load(gamma) == 0)
					msg_once = "loaded";
				else
					msg_once = "load failed";
				goto input_done_gamma;
			case KEY_HOME:
			case BTN_BASE4:
				if (!ev.value)
					break;
				if (do_save(gamma) == 0)
					msg_once = "saved";
				else
					msg_once = "save failed";
				goto input_done;
			case KEY_LEFTALT:
			case BTN_START:
				goto out;
			case KEY_LEFTCTRL:
			case BTN_SELECT:
				goto out;
			}

input_no_change:
			if (pressed_up) {
				gamma[sel]++;
				if (gamma[sel] >= 1023)
					gamma[sel] = 1023;
				if (!pressed_r)
					usleep(30000);
				goto input_done_gamma;
			}
			if (pressed_down) {
				gamma[sel]--;
				if (gamma[sel] < 0)
					gamma[sel] = 0;
				if (!pressed_r)
					usleep(30000);
				goto input_done_gamma;
			}

			usleep(10000);
		}

input_done_gamma:
		if (f != NULL) {
			int *g = gamma;
			rewind(f);
			fprintf(f, "%d %d %d %d %d %d %d %d %d %d %d %d\n",
				g[0], g[1], g[2], g[3], g[4],  g[5],
				g[6], g[7], g[8], g[9], g[10], g[11]);
		}
input_done:;
	}

out:
	munmap(ptr, fbvar.xres * fbvar.yres * fbvar.bits_per_pixel / 8);
	close(fbdev);
	if (ifd != -1)
		close(ifd);
	return 0;
}

