/*
 * OMAP3 ISP snapshot test application
 *
 * Copyright (C) 2010-2011 Ideas on board SPRL
 *
 * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
 *
 * 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
 */

#define _BSD_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>

#include <linux/spi/spidev.h>

#if USE_LIBJPEG
#include <jpeglib.h>
#include <setjmp.h>
#endif

#include "isp/list.h"
#include "isp/omap3isp.h"
#include "isp/tools.h"
#include "isp/v4l2.h"
#include "isp/v4l2-pool.h"

#include "events.h"
#include "iq.h"
#include "jpeg.h"

#define MEDIA_DEVICE		"/dev/media0"

#define VIEWFINDER_WIDTH	1024
#define VIEWFINDER_HEIGHT	768
#define SNAPSHOT_WIDTH		2048
#define SNAPSHOT_HEIGHT		1536

struct viewfinder {
	unsigned int count;
	bool enabled;
	struct v4l2_buffers_pool *pool;
};

static struct viewfinder vf = { 0, false, NULL };

enum snapshot_trigger_type {
	SNAPSHOT_TRIGGER_AUTO = 0,
	SNAPSHOT_TRIGGER_MANUAL,
};

struct snapshot {
	struct v4l2_mbus_framefmt format;
	struct v4l2_rect crop;

	unsigned int cycle;
	unsigned int ncycles;
	unsigned int frame;
	unsigned int nframes;

	unsigned int interval;
	unsigned int skip;

	struct timespec time;
	enum snapshot_trigger_type trigger;
	bool save;

	bool jpeg_enable;
	struct jpeg *jpeg;
};

static struct snapshot snap = {
	.crop = { -1, -1, -1, -1 },
	.ncycles = 1,
	.nframes = 3,
	.interval = 20,
	.skip = 2,
};

static struct iq_tuning *iq = NULL;

static struct events events;

/* -----------------------------------------------------------------------------
 * Image quality
 */

static void __iq_aewb_process(struct omap3_isp_device *isp __attribute__((__unused__)),
			      const struct omap3_isp_aewb_stats *stats)
{
	iq_aewb_process(iq, stats);
}

static void __iq_histogram_process(struct omap3_isp_device *isp __attribute__((__unused__)),
				   const struct omap3_isp_histogram_stats *stats)
{
	iq_histogram_process(iq, stats);
}

/* -----------------------------------------------------------------------------
 * Events
 */

static void __events_watch_fd(int fd, enum omap3_isp_event_type type,
			      void(*callback)(void *), void *priv)
{
	events_watch_fd(&events, fd, type, callback, priv);
}

static void __events_unwatch_fd(int fd)
{
	events_unwatch_fd(&events, fd);
}

static void sigint_handler(int signal __attribute__((__unused__)))
{
	/* Stop the main loop when the user presses CTRL-C. */
	events_stop(&events);
}

/* -----------------------------------------------------------------------------
 * Snapshots
 */

static int snapshot_capture(struct omap3_isp_device *isp)
{
	printf("- snapshot %u\n", snap.cycle);

	snap.cycle++;
	snap.frame = 0;
	clock_gettime(CLOCK_MONOTONIC, &snap.time);

	return omap3_isp_snapshot_capture(isp);
}

static void snapshot_process(struct omap3_isp_device *isp,
			     struct v4l2_video_buffer *buffer)
{
	static struct timespec ts;
	char name[33];
	FILE *file;
	int ret;

	if (buffer->error) {
		printf("warning: error in dequeued buffer, skipping\n");
		goto requeue;
	}

	ts.tv_sec = buffer->timestamp.tv_sec - snap.time.tv_sec;
	ts.tv_nsec = (buffer->timestamp.tv_usec * 1000) - snap.time.tv_nsec;
	if (ts.tv_nsec < 0) {
		ts.tv_sec--;
		ts.tv_nsec += 1000000000;
	}

	snap.time.tv_sec = buffer->timestamp.tv_sec;
	snap.time.tv_nsec = buffer->timestamp.tv_usec * 1000;

	printf("frame captured in %lu.%06lus.\n", ts.tv_sec, ts.tv_nsec / 1000);

	if (snap.save && snap.frame >= snap.skip) {
		sprintf(name, "snapshot-%06u-%02u.%s", snap.cycle, snap.frame,
#if USE_LIBJPEG
			snap.jpeg ? "jpg" :
#endif
			"bin");
		file = fopen(name, "wb");
		if (file == NULL)
			goto requeue;
#if USE_LIBJPEG
		if (snap.jpeg)
			jpeg_save(snap.jpeg, file, buffer);
		else
#endif
			fwrite(buffer->mem, buffer->bytesused, 1, file);
		fclose(file);
	}

requeue:
	/* Requeue the buffer */
	ret = omap3_isp_snapshot_put_buffer(isp, buffer);
	if (ret < 0) {
		printf("error: unable to requeue buffer: %s (%d)\n",
			strerror(-ret), ret);
		return;
	}

	if (buffer->error)
		return;

	snap.frame++;
	if (snap.frame < snap.nframes)
		return;

	if (snap.trigger == SNAPSHOT_TRIGGER_AUTO)
		events_stop(&events);

	omap3_isp_snapshot_done(isp);

	if (vf.enabled) {
		ret = omap3_isp_viewfinder_start(isp, -1);
		if (ret < 0) {
			printf("error: unable to restart viewfinder: %s (%d)\n",
				strerror(-ret), ret);
			return;
		}
	}
}

static int snapshot_init(struct omap3_isp_device *isp)
{
	struct v4l2_rect *crop;
	int ret;

	if (snap.crop.width > 0 && snap.crop.height > 0)
		crop = &snap.crop;
	else
		crop = NULL;

	ret = omap3_isp_snapshot_setup(isp, crop, &snap.format);
	if (ret < 0) {
		printf("error: unable to setup pipeline\n");
		return ret;
	}

#if USE_LIBJPEG
	if (snap.jpeg_enable) {
		snap.jpeg = jpeg_init(&snap.format);
		if (snap.jpeg == NULL) {
			printf("Unable to initialize JPEG compressor.\n");
			return -EIO;
		}
	}
#endif

	printf("snapshot configured for %04x %ux%u\n",
	       snap.format.code, snap.format.width, snap.format.height);

	return 0;
}

static void snapshot_cleanup(void)
{
#if USE_LIBJPEG
	if (snap.jpeg != NULL)
		jpeg_cleanup(snap.jpeg);
#endif
}

/* -----------------------------------------------------------------------------
 * Viewfinder
 */

static void viewfinder_process(struct omap3_isp_device *isp,
			       struct v4l2_video_buffer *buffer)
{
	int ret;

	if (buffer->error) {
		printf("warning: error in dequeued buffer, skipping\n");
		return;
	}

	vf.count++;
	printf("viewfinder frame %u captured.\n", vf.count);

	/* Requeue the buffer */
	ret = omap3_isp_viewfinder_put_buffer(isp, buffer);
	if (ret < 0) {
		printf("error: unable to requeue buffer: %s (%d)\n",
			strerror(-ret), ret);
		return;
	}

	if ((vf.count % snap.interval) == 0) {
		ret = omap3_isp_viewfinder_stop(isp);
		if (ret < 0) {
			printf("error: unable to stop viewfinder: %s (%d)\n",
				strerror(-ret), ret);
			return;
		}

		snapshot_capture(isp);
	}
}

static int viewfinder_init(struct omap3_isp_device *isp)
{
	struct v4l2_mbus_framefmt view_format;
	int ret;

	memset(&view_format, 0, sizeof view_format);
	view_format.code = V4L2_MBUS_FMT_YUYV8_1X16;
	view_format.width = VIEWFINDER_WIDTH;
	view_format.height = VIEWFINDER_HEIGHT;

	ret = omap3_isp_viewfinder_setup(isp, &view_format);
	if (ret < 0) {
		printf("error: unable to setup pipeline\n");
		return ret;
	}

	printf("viewfinder configured for %04x %ux%u\n",
	       view_format.code, view_format.width, view_format.height);

	/* Allocate a buffers pool and use it for the viewfinder. */
	vf.pool = v4l2_buffers_pool_new(4);
	if (vf.pool == NULL) {
		printf("error: unable to create buffers pool\n");
		return -ENOMEM;
	}

	ret = v4l2_buffers_pool_alloc(vf.pool, view_format.width * view_format.height * 2, 4096);
	if (ret < 0) {
		printf("error: unable to allocate buffers memory\n");
		return ret;
	}

	ret = omap3_isp_viewfinder_set_pool(isp, vf.pool);
	if (ret < 0) {
		printf("error: unable to set buffers pool\n");
		return ret;
	}

	return 0;
}

static void viewfinder_cleanup(struct omap3_isp_device *isp __attribute__((__unused__)))
{
	if (vf.pool)
		v4l2_buffers_pool_delete(vf.pool);
}

/* -------------------------------------------------------------------------- */

static const struct {
	const char *name;
	unsigned int fourcc;
} pixel_formats[] = {
	{ "Y8", V4L2_MBUS_FMT_Y8_1X8 },
	{ "Y10", V4L2_MBUS_FMT_Y10_1X10 },
	{ "Y12", V4L2_MBUS_FMT_Y12_1X12 },
	{ "YUYV", V4L2_MBUS_FMT_YUYV8_1X16 },
	{ "UYVY", V4L2_MBUS_FMT_UYVY8_1X16 },
	{ "SBGGR8", V4L2_MBUS_FMT_SBGGR8_1X8 },
	{ "SGBRG8", V4L2_MBUS_FMT_SGBRG8_1X8 },
	{ "SGRBG8", V4L2_MBUS_FMT_SGRBG8_1X8 },
	{ "SRGGB8", V4L2_MBUS_FMT_SRGGB8_1X8 },
	{ "SBGGR10", V4L2_MBUS_FMT_SBGGR10_1X10 },
	{ "SGBRG10", V4L2_MBUS_FMT_SGBRG10_1X10 },
	{ "SGRBG10", V4L2_MBUS_FMT_SGRBG10_1X10 },
	{ "SRGGB10", V4L2_MBUS_FMT_SRGGB10_1X10 },
	{ "SBGGR12", V4L2_MBUS_FMT_SBGGR12_1X12 },
	{ "SGBRG12", V4L2_MBUS_FMT_SGBRG12_1X12 },
	{ "SGRBG12", V4L2_MBUS_FMT_SGRBG12_1X12 },
	{ "SRGGB12", V4L2_MBUS_FMT_SRGGB12_1X12 },
};

static unsigned int parse_format(const char *name)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(pixel_formats); ++i) {
		if (strcasecmp(pixel_formats[i].name, name) == 0)
			return pixel_formats[i].fourcc;
	}

	return 0;
}

static int parse_crop(const char *p, struct v4l2_rect *crop)
{
	char *end;

	if (*p++ != '(')
		return -EINVAL;

	crop->left = strtoul(p, &end, 10);
	if (*end != ',')
		return -EINVAL;

	p = end + 1;
	crop->top = strtoul(p, &end, 10);
	if (*end++ != ')')
		return -EINVAL;
	if (*end != '/')
		return -EINVAL;

	p = end + 1;
	crop->width = strtoul(p, &end, 10);
	if (*end != 'x')
		return -EINVAL;

	p = end + 1;
	crop->height = strtoul(p, &end, 10);
	if (*end != '\0')
		return -EINVAL;

	return 0;
}

static int parse_size(const char *p, struct v4l2_mbus_framefmt *format)
{
	char *end;

	format->width = strtoul(p, &end, 10);
	if (*end != 'x')
		return -EINVAL;

	p = end + 1;
	format->height = strtoul(p, &end, 10);
	if (*end != '\0')
		return -EINVAL;

	return 0;
}

static void usage(const char *argv0)
{
	printf("Usage: %s [options]\n", argv0);
	printf("Supported options:\n");
	printf("-c, --crop (x,y)/wxh	Set the snapshot capture crop\n");
	printf("-f, --format fmt	Snapshot format\n");
	printf("-h, --help		Show this help screen\n");
	printf("-i, --interval n	Capture a snapshot every n frames (default 20)\n");
#if USE_LIBJPEG
	printf("-j, --jpeg		Save snapshots in JPEG format (implies YUV mode)\n");
#endif
	printf("-k, --skip n		Skip n frames per snapshot when saving to disk (default 2)\n");
	printf("-N, --snap-frames n	Capture n frames per snapshot (default 3)\n");
	printf("-n, --snap-number n	Capture n snapshots (default 1)\n");
	printf("-S, --save		Save snapshots to disk\n");
	printf("-s, --size wxh		Set the snapshot capture size\n");
	printf("-v, --view		Enable viewfinder\n");
	printf("    --aewb param=value	Set AEWB algorithm parameter 'param' to 'value'\n");
	printf("    --disable-aewb	Disable AEWB\n");
	printf("    --saturation value	Set the saturation value [0.0-2.0]\n");
	printf("\nSupported AEWB parameters are:\n");
 	printf("- ae-delay		Number of frames to skip at stream start before enabling AE\n");
	printf("- ae-interval		Number of frames between AE algorithm runs (>= 1)\n");
	printf("- exposure-def		Exposure time default value\n");
	printf("- exposure-min		Exposure time minimum value\n");
	printf("- exposure-max		Exposure time maximum value\n");
	printf("- gain-def		Sensor gain default value\n");
	printf("- gain-min		Sensor gain minimum value\n");
	printf("- gain-max		Sensor gain maximum value\n");
	printf("- mean-level		Mean luminance target level (float [0-1])\n");
	printf("- window-left		Statistics window left (float [0-1])\n");
	printf("- window-top		Statistics window top (float [0-1])\n");
	printf("- window-width		Statistics window width (float [0-1])\n");
	printf("- window-height		Statistics window height (float [0-1])\n");
}

#define OPT_AEWB_PARAM		256
#define OPT_SATURATION		257
#define OPT_AEWB_DISABLE	258

static struct option opts[] = {
	{ "aewb", 1, 0, OPT_AEWB_PARAM },
	{ "crop", 1, 0, 'c' },
	{ "disable-aewb", 0, 0, OPT_AEWB_DISABLE },
	{ "format", 1, 0, 'f' },
	{ "help", 0, 0, 'h' },
	{ "interval", 1, 0, 'i' },
#if USE_LIBJPEG
	{ "jpeg", 0, 0, 'j' },
#endif
	{ "saturation", 1, 0, OPT_SATURATION },
	{ "save", 0, 0, 'S' },
	{ "size", 1, 0, 's' },
	{ "snap-frames", 1, 0, 'N' },
	{ "snap-number", 1, 0, 'n' },
	{ "skip", 1, 0, 'k' },
	{ "view", 1, 0, 'v' },
	{ 0, 0, 0, 0 }
};

int main(int argc __attribute__((__unused__)), char *argv[] __attribute__((__unused__)))
{
	struct omap3_isp_device *isp = NULL;
	struct omap3_isp_operations ops;
	struct timespec start, end;
	struct iq_params iq_params;
	int exit_code = EXIT_FAILURE;
	bool enable_aewb = true;
	float fps;
	int ret;
	int c;

	snap.format.code = V4L2_MBUS_FMT_SGRBG10_1X10;
	snap.format.width = SNAPSHOT_WIDTH;
	snap.format.height = SNAPSHOT_HEIGHT;

	iq_params_init(&iq_params);

	while ((c = getopt_long(argc, argv, "c:f:hi:"
#if USE_LIBJPEG
	"j"
#endif
	"k:N:n:Ss:v", opts, NULL)) != -1) {
		switch (c) {
		case 'c':
			if (parse_crop(optarg, &snap.crop)) {
				printf("invalid crop rectangle %s.\n", optarg);
				return 1;
			}
			break;
		case 'f':
			snap.format.code = parse_format(optarg);
			if (snap.format.code == 0) {
				printf("invalid format %s.\n", optarg);
				return 1;
			}
			break;
		case 'h':
			usage(argv[0]);
			return 0;
		case 'i':
			snap.interval = strtoul(optarg, NULL, 10);
			if (snap.interval == 0) {
				printf("snapshot interval value must be >0.\n");
				return 1;
			}
			break;
#if USE_LIBJPEG
		case 'j':
			snap.jpeg_enable = true;
			snap.format.code = V4L2_MBUS_FMT_YUYV8_1X16;
			break;
#endif
		case 'k':
			snap.skip = strtoul(optarg, NULL, 10);
			break;
		case 'N':
			snap.nframes = strtoul(optarg, NULL, 10);
			if (snap.nframes == 0) {
				printf("number of frames must be >0.\n");
				return 1;
			}
			break;
		case 'n':
			snap.ncycles = strtoul(optarg, NULL, 10);
			if (snap.ncycles == 0) {
				printf("number of snapshots must be >0.\n");
				return 1;
			}
			break;
		case 'S':
			snap.save = true;
			break;
		case 's':
			if (parse_size(optarg, &snap.format)) {
				printf("invalid size %s.\n", optarg);
				return 1;
			}
			break;
		case 'v':
			vf.enabled = true;
			snap.trigger = SNAPSHOT_TRIGGER_MANUAL;
			break;
		case OPT_AEWB_DISABLE:
			enable_aewb = false;
			break;
		case OPT_AEWB_PARAM:
			ret = iq_params_parse(&iq_params, optarg);
			if (ret < 0) {
				printf("%s AEWB argument %s.\n",
				       ret == -ERANGE ? "out-of-range" : "invalid",
				       optarg);
				return 1;
			}
			break;
		case OPT_SATURATION:
			iq_params.saturation = strtof(optarg, NULL);
			break;
		default:
			printf("Invalid option -%c\n", c);
			printf("Run %s -h for help.\n", argv[0]);
			return 1;
		}
	}

	events_init(&events);

	/* Register a signal handler for SIGINT, received when the user presses
	 * CTRL-C. This will allow the main loop to be interrupted, and resources
	 * to be freed cleanly.
	 */
	signal(SIGINT, sigint_handler);

	/* Open the OMAP3 ISP device and setup the capture pipeline. */
	memset(&ops, 0, sizeof ops);
	ops.viewfinder_ready = viewfinder_process;
	ops.snapshot_ready = snapshot_process;
	ops.watch_fd = __events_watch_fd;
	ops.unwatch_fd = __events_unwatch_fd;
	ops.aewb_ready = __iq_aewb_process;
	ops.histogram_ready = __iq_histogram_process,

	isp = omap3_isp_open(MEDIA_DEVICE, &ops);
	if (isp == NULL) {
		printf("error: unable to open media device %s\n", MEDIA_DEVICE);
		goto cleanup;
	}

	if (vf.enabled) {
		ret = viewfinder_init(isp);
		if (ret < 0)
			goto cleanup;
	}

	ret = snapshot_init(isp);
	if (ret < 0)
		goto cleanup;

	if (enable_aewb) {
		iq = iq_init(isp, &iq_params);
		if (iq == NULL) {
			printf("error: unable to initialize image quality tuning\n");
			goto cleanup;
		}
	}

	/* Start the viewfinder if needed. */
	if (vf.enabled) {
		ret = omap3_isp_viewfinder_start(isp, -1);
		if (ret < 0)
			goto cleanup;
	}

	/* Main capture loop. */
	clock_gettime(CLOCK_MONOTONIC, &start);

	for (snap.cycle = 0; snap.cycle < snap.ncycles; ) {
		if (!vf.enabled) {
			ret = snapshot_capture(isp);
			if (ret < 0)
				goto cleanup;
		}

		if (events_loop(&events))
			goto cleanup;
	}

	clock_gettime(CLOCK_MONOTONIC, &end);

	/* Stop the ISP. */
	if (vf.enabled)
		omap3_isp_viewfinder_stop(isp);

	/* Print some statistics. */
	end.tv_sec -= start.tv_sec;
	end.tv_nsec -= start.tv_nsec;
	if (end.tv_nsec < 0) {
		end.tv_sec--;
		end.tv_nsec += 1000000000;
	}

	fps = (vf.count + snap.cycle * snap.frame)
	    / (end.tv_sec + end.tv_nsec / 1000000000.0);

	printf("%u images processed in %lu.%06lu seconds (%f fps)\n",
	       vf.count + snap.cycle * snap.frame,
	       end.tv_sec, end.tv_nsec / 1000, fps);

	exit_code = EXIT_SUCCESS;

cleanup:
	/* Cleanup the ISP resources. */
	if (isp)
		omap3_isp_close(isp);
	if (vf.enabled)
		viewfinder_cleanup(isp);
	if (iq)
		iq_cleanup(iq);
	snapshot_cleanup();

	return exit_code;
}