#include <endian.h>

#include "v4l2-ctl.h"

static struct v4l2_format vfmt;	/* set_format/get_format */
static unsigned mbus_code;
static unsigned mbus_code_out;

void meta_usage()
{
	printf("\nMetadata Formats options:\n"
	       "  --list-formats-meta [<mbus_code>] display supported metadata capture formats.\n"
	       "		     <mbus_code> is an optional media bus code, if the device has\n"
	       "		     capability V4L2_CAP_IO_MC then only formats that support this\n"
	       "		     media bus code are listed [VIDIOC_ENUM_FMT]\n"
	       "  --get-fmt-meta     query the metadata capture format [VIDIOC_G_FMT]\n"
	       "  --set-fmt-meta <f> set the metadata capture format [VIDIOC_S_FMT]\n"
	       "                     parameter is either the format index as reported by\n"
	       "                     --list-formats-meta, or the fourcc value as a string\n"
	       "  --try-fmt-meta <f> try the metadata capture format [VIDIOC_TRY_FMT]\n"
	       "                     parameter is either the format index as reported by\n"
	       "                     --list-formats-meta, or the fourcc value as a string\n"
	       "  --list-formats-meta-out [<mbus_code>] display supported metadata output formats.\n"
	       "		     <mbus_code> is an optional media bus code, if the device has\n"
	       "		     capability V4L2_CAP_IO_MC then only formats that support this\n"
	       "		     media bus code are listed [VIDIOC_ENUM_FMT]\n"
	       "  --get-fmt-meta-out query the metadata output format [VIDIOC_G_FMT]\n"
	       "  --set-fmt-meta-out <f> set the metadata output format [VIDIOC_S_FMT]\n"
	       "                     parameter is either the format index as reported by\n"
	       "                     --list-formats-meta-out, or the fourcc value as a string\n"
	       "  --try-fmt-meta-out <f> try the metadata output format [VIDIOC_TRY_FMT]\n"
	       "                     parameter is either the format index as reported by\n"
	       "                     --list-formats-meta-out, or the fourcc value as a string\n"
	       );
}

void meta_cmd(int ch, char *optarg)
{
	switch (ch) {
	case OptSetMetaFormat:
	case OptTryMetaFormat:
	case OptSetMetaOutFormat:
	case OptTryMetaOutFormat:
		if (strlen(optarg) == 0) {
			meta_usage();
			std::exit(EXIT_FAILURE);
		} else if (strlen(optarg) == 4) {
			vfmt.fmt.meta.dataformat = v4l2_fourcc(optarg[0],
					optarg[1], optarg[2], optarg[3]);
		} else {
			vfmt.fmt.meta.dataformat = strtol(optarg, nullptr, 0);
		}
		break;
	case OptListMetaFormats:
		if (optarg)
			mbus_code = strtoul(optarg, nullptr, 0);
		break;
	case OptListMetaOutFormats:
		if (optarg)
			mbus_code_out = strtoul(optarg, nullptr, 0);
		break;
	}
}

static void __meta_set(cv4l_fd &_fd, bool set, bool _try, __u32 type)
{
	struct v4l2_format in_vfmt;
	int fd = _fd.g_fd();
	int ret;

	if (!set && !_try)
		return;

	in_vfmt.type = type;
	in_vfmt.fmt.meta.dataformat = vfmt.fmt.meta.dataformat;

	if (in_vfmt.fmt.meta.dataformat < 256) {
		struct v4l2_fmtdesc fmt = {};

		fmt.index = in_vfmt.fmt.meta.dataformat;
		fmt.type = in_vfmt.type;

		if (doioctl(fd, VIDIOC_ENUM_FMT, &fmt))
			fmt.pixelformat = 0;

		in_vfmt.fmt.meta.dataformat = fmt.pixelformat;
	}

	if (set)
		ret = doioctl(fd, VIDIOC_S_FMT, &in_vfmt);
	else
		ret = doioctl(fd, VIDIOC_TRY_FMT, &in_vfmt);
	if (ret == 0 && (verbose || _try))
		printfmt(fd, in_vfmt);
}

void meta_set(cv4l_fd &_fd)
{
	__meta_set(_fd, options[OptSetMetaFormat], options[OptTryMetaFormat],
		   V4L2_BUF_TYPE_META_CAPTURE);
	__meta_set(_fd, options[OptSetMetaOutFormat],
		   options[OptTryMetaOutFormat], V4L2_BUF_TYPE_META_OUTPUT);
}

static void __meta_get(cv4l_fd &fd, __u32 type)
{
	vfmt.type = type;
	if (doioctl(fd.g_fd(), VIDIOC_G_FMT, &vfmt) == 0)
		printfmt(fd.g_fd(), vfmt);
}

void meta_get(cv4l_fd &fd)
{
	if (options[OptGetMetaFormat])
		__meta_get(fd, V4L2_BUF_TYPE_META_CAPTURE);
	if (options[OptGetMetaOutFormat])
		__meta_get(fd, V4L2_BUF_TYPE_META_OUTPUT);
}

void meta_list(cv4l_fd &fd)
{
	if (options[OptListMetaFormats]) {
		printf("ioctl: VIDIOC_ENUM_FMT\n");
		print_video_formats(fd, V4L2_BUF_TYPE_META_CAPTURE, mbus_code);
	}

	if (options[OptListMetaOutFormats]) {
		printf("ioctl: VIDIOC_ENUM_FMT\n");
		print_video_formats(fd, V4L2_BUF_TYPE_META_OUTPUT, mbus_code_out);
	}
}

struct uvc_meta_buf {
	__u64 ns;
	__u16 sof;
	__u8 length;
	__u8 flags;
	__u8 buf[10];
};

#define UVC_STREAM_SCR	(1 << 3)
#define UVC_STREAM_PTS	(1 << 2)

struct vivid_meta_out_buf {
        __u16	brightness;
        __u16	contrast;
        __u16	saturation;
        __s16	hue;
};

void print_meta_buffer(FILE *f, cv4l_buffer &buf, cv4l_fmt &fmt, cv4l_queue &q)
{
	struct uvc_meta_buf *vbuf;
	int buf_off = 0;
	struct vivid_meta_out_buf *vbuf_out;

	switch (fmt.g_pixelformat()) {
	case V4L2_META_FMT_UVC:
		fprintf(f, "UVC: ");
		vbuf = static_cast<uvc_meta_buf *>(q.g_dataptr(buf.g_index(), 0));

		fprintf(f, "%.6fs sof: %4d len: %u flags: 0x%02x",
			static_cast<double>(vbuf->ns) / 1000000000.0,
			vbuf->sof,
			vbuf->length,
			vbuf->flags);
		if (vbuf->flags & UVC_STREAM_PTS) {
			fprintf(f, " PTS: %u", le32toh(*(__u32*)(vbuf->buf)));
			buf_off = 4;
		}
		if (vbuf->flags & UVC_STREAM_SCR)
			fprintf(f, " STC: %u SOF counter: %u",
				le32toh(*(__u32*)(vbuf->buf + buf_off)),
				le16toh(*(__u16*)(vbuf->buf + buf_off + 4)));
		fprintf(f, "\n");
		break;
	case V4L2_META_FMT_VIVID:
		fprintf(f, "VIVID:");
		vbuf_out = static_cast<vivid_meta_out_buf *>(q.g_dataptr(buf.g_index(), 0));

		fprintf(f, " brightness: %u contrast: %u saturation: %u  hue: %d\n",
			vbuf_out->brightness, vbuf_out->contrast,
			vbuf_out->saturation, vbuf_out->hue);
		break;
	}
}

void meta_fillbuffer(cv4l_buffer &buf, cv4l_fmt &fmt, cv4l_queue &q)
{
	struct vivid_meta_out_buf *vbuf;

	switch (fmt.g_pixelformat()) {
		case V4L2_META_FMT_VIVID:
			vbuf = static_cast<vivid_meta_out_buf *>(q.g_dataptr(buf.g_index(), 0));
			vbuf->brightness = buf.g_sequence() % 192 + 64;
			vbuf->contrast = (buf.g_sequence() + 10) % 192 + 64;
			vbuf->saturation = (buf.g_sequence() + 20) % 256;
			vbuf->hue = buf.g_sequence() % 257 - 128;
			break;
	}
}
