/*
 * Media controller test application
 *
 * Copyright (C) 2010-2014 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 Lesser General Public License as published
 * by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <ctype.h>
#include <dirent.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <v4l2subdev.h>

#include <linux/media.h>
#include <linux/videodev2.h>

#include "options.h"
#include "v4l2subdev.h"

#define MEDIA_DEVNAME_DEFAULT		"/dev/media0"

struct media_options media_opts = {
	.devname = MEDIA_DEVNAME_DEFAULT,
};

static void print_version()
{
#define STR(x) #x
#define STRING(x) STR(x)
	printf("media-ctl %s%s\n", PACKAGE_VERSION, STRING(GIT_COMMIT_CNT));
}

static void usage(const char *argv0)
{
	unsigned int i;

	printf("%s [options]\n", argv0);
	printf("-d, --device dev	Media device name (default: %s)\n", MEDIA_DEVNAME_DEFAULT);
	printf("			If <dev> starts with a digit, then /dev/media<dev> is used.\n");
	printf("			If <dev> doesn't exist, then find a media device that\n");
	printf("			reports a bus info string equal to <dev>.\n");
	printf("-e, --entity name	Print the device name associated with the given entity\n");
	printf("-V, --set-v4l2 v4l2	Comma-separated list of formats to setup\n");
	printf("    --get-v4l2 pad	Print the active format on a given pad\n");
	printf("    --get-dv pad        Print detected and current DV timings on a given pad\n");
	printf("    --set-dv pad	Configure DV timings on a given pad\n");
	printf("-h, --help		Show verbose help and exit\n");
	printf("-i, --interactive	Modify links interactively\n");
	printf("-l, --links links	Comma-separated list of link descriptors to setup\n");
	printf("    --known-mbus-fmts	List known media bus formats and their numeric values\n");
	printf("-p, --print-topology	Print the device topology. If an entity\n");
	printf("			is specified through the -e option, print\n");
	printf("			information for that entity only.\n");
	printf("    --print-dot		Print the device topology as a dot graph\n");
	printf("-r, --reset		Reset all links to inactive\n");
	printf("-v, --verbose		Be verbose\n");
	printf("    --version		Show version information\n");
	printf("\n");
	printf("Links and formats are defined as\n");
	printf("\tlinks           = link { ',' link } ;\n");
	printf("\tlink            = pad '->' pad '[' flags ']' ;\n");
	printf("\tpad             = entity ':' pad-number ;\n");
	printf("\tentity          = entity-number | ( '\"' entity-name '\"' ) ;\n");
	printf("\n");
	printf("\tv4l2            = pad '[' v4l2-properties ']' ;\n");
	printf("\tv4l2-properties = v4l2-property { ',' v4l2-property } ;\n");
	printf("\tv4l2-property   = v4l2-mbusfmt | v4l2-crop | v4l2-interval\n");
	printf("\t                | v4l2-compose | v4l2-interval ;\n");
	printf("\tv4l2-mbusfmt    = 'fmt:' fcc '/' size ; { 'field:' v4l2-field ; } { 'colorspace:' v4l2-colorspace ; }\n");
	printf("\t                   { 'xfer:' v4l2-xfer-func ; } { 'ycbcr:' v4l2-ycbcr-enc ; }\n");
	printf("\t                   { 'quantization:' v4l2-quant ; }\n");
	printf("\tv4l2-crop       = 'crop:' rectangle ;\n");
	printf("\tv4l2-compose    = 'compose:' rectangle ;\n");
	printf("\tv4l2-interval   = '@' numerator '/' denominator ;\n");
	printf("\n");
	printf("\trectangle       = '(' left ',' top, ')' '/' size ;\n");
	printf("\tsize            = width 'x' height ;\n");
	printf("\n");
	printf("where the fields are\n");
	printf("\tentity-number   Entity numeric identifier\n");
	printf("\tentity-name     Entity name (string) \n");
	printf("\tpad-number      Pad numeric identifier\n");
	printf("\tflags           Link flags (0: inactive, 1: active)\n");
	printf("\tfcc             Format FourCC\n");
	printf("\twidth           Image width in pixels\n");
	printf("\theight          Image height in pixels\n");
	printf("\tnumerator       Frame interval numerator\n");
	printf("\tdenominator     Frame interval denominator\n");
	printf("\tv4l2-field      One of the following:\n");

	for (i = V4L2_FIELD_ANY; i <= V4L2_FIELD_INTERLACED_BT; i++)
		printf("\t                %s\n",
		       v4l2_subdev_field_to_string(i));

	printf("\tv4l2-colorspace One of the following:\n");

	for (i = V4L2_COLORSPACE_DEFAULT; i <= V4L2_COLORSPACE_DCI_P3; i++)
		printf("\t                %s\n",
		       v4l2_subdev_colorspace_to_string(i));

	printf("\tv4l2-xfer-func  One of the following:\n");

	for (i = V4L2_XFER_FUNC_DEFAULT; i <= V4L2_XFER_FUNC_SMPTE2084; i++)
		printf("\t                %s\n",
		       v4l2_subdev_xfer_func_to_string(i));

	printf("\tv4l2-quant      One of the following:\n");

	for (i = V4L2_QUANTIZATION_DEFAULT; i <= V4L2_QUANTIZATION_LIM_RANGE; i++)
		printf("\t                %s\n",
		       v4l2_subdev_quantization_to_string(i));

	printf("\tv4l2-ycbcr-enc  One of the following:\n");

	for (i = V4L2_YCBCR_ENC_DEFAULT; i <= V4L2_YCBCR_ENC_SMPTE240M; i++)
		printf("\t                %s\n",
		       v4l2_subdev_ycbcr_encoding_to_string(i));
}

#define OPT_PRINT_DOT			256
#define OPT_GET_FORMAT			257
#define OPT_SET_DV			258
#define OPT_LIST_KNOWN_MBUS_FMTS	259
#define OPT_GET_DV			260
#define OPT_VERSION			261

static struct option opts[] = {
	{"device", 1, 0, 'd'},
	{"entity", 1, 0, 'e'},
	{"set-format", 1, 0, 'f'},
	{"set-v4l2", 1, 0, 'V'},
	{"get-format", 1, 0, OPT_GET_FORMAT},
	{"get-v4l2", 1, 0, OPT_GET_FORMAT},
	{"get-dv", 1, 0, OPT_GET_DV},
	{"set-dv", 1, 0, OPT_SET_DV},
	{"help", 0, 0, 'h'},
	{"interactive", 0, 0, 'i'},
	{"links", 1, 0, 'l'},
	{"known-mbus-fmts", 0, 0, OPT_LIST_KNOWN_MBUS_FMTS},
	{"print-dot", 0, 0, OPT_PRINT_DOT},
	{"print-topology", 0, 0, 'p'},
	{"reset", 0, 0, 'r'},
	{"verbose", 0, 0, 'v'},
	{"version", 0, 0, OPT_VERSION},
	{ },
};

static void list_known_mbus_formats(void)
{
	unsigned int ncodes;
	const unsigned int *code = v4l2_subdev_pixelcode_list(&ncodes);

	while (ncodes--) {
		const char *str = v4l2_subdev_pixelcode_to_string(*code);
		int spaces = 30 - (int)strlen(str);

		if (*code == 0)
			break;

		if (spaces < 0)
			spaces = 0;

		printf("%s %*c 0x%4.4x\n", str, spaces, ' ', *code);

		code++;
	}
}

static const char *make_devname(const char *device)
{
	static char newdev[300];
	struct dirent *ep;
	DIR *dp;

	if (!access(device, F_OK))
		return device;

	if (device[0] >= '0' && device[0] <= '9' && strlen(device) <= 3) {
		snprintf(newdev, sizeof(newdev), "/dev/media%s", device);
		return newdev;
	}

	dp = opendir("/dev");
	if (dp == NULL)
		return device;

	while ((ep = readdir(dp))) {
		const char *name = ep->d_name;

		if (!memcmp(name, "media", 5) && isdigit(name[5])) {
			struct media_device_info mdi;
			int ret;
			int fd;

			snprintf(newdev, sizeof(newdev), "/dev/%s", name);
			fd = open(newdev, O_RDWR);
			if (fd < 0)
				continue;
			ret = ioctl(fd, MEDIA_IOC_DEVICE_INFO, &mdi);
			close(fd);
			if (!ret && !strcmp(device, mdi.bus_info)) {
				closedir(dp);
				return newdev;
			}
		}
	}
	closedir(dp);
	return device;
}

int parse_cmdline(int argc, char **argv)
{
	int opt;

	if (argc == 1) {
		usage(argv[0]);
		return 1;
	}

	/* parse options */
	while ((opt = getopt_long(argc, argv, "d:e:f:hil:prvV:",
				  opts, NULL)) != -1) {
		switch (opt) {
		case 'd':
			media_opts.devname = make_devname(optarg);
			break;

		case 'e':
			media_opts.entity = optarg;
			break;

		/* 'f' is supported for backward compatibility reasons and will
		 * be removed later.
		 */
		case 'f':
			fprintf(stderr, "Warning: the -f option is deprecated "
				"and has been replaced by -V.\n");
			/* fall through */
		case 'V':
			media_opts.formats = optarg;
			break;

		case 'h':
			usage(argv[0]);
			exit(0);

		case 'i':
			media_opts.interactive = 1;
			break;

		case 'l':
			media_opts.links = optarg;
			break;

		case 'p':
			media_opts.print = 1;
			break;

		case 'r':
			media_opts.reset = 1;
			break;

		case 'v':
			media_opts.verbose = 1;
			break;

		case OPT_PRINT_DOT:
			media_opts.print_dot = 1;
			break;

		case OPT_GET_FORMAT:
			media_opts.fmt_pad = optarg;
			break;

		case OPT_GET_DV:
			media_opts.get_dv_pad = optarg;
			break;

		case OPT_SET_DV:
			media_opts.dv_pad = optarg;
			break;

		case OPT_LIST_KNOWN_MBUS_FMTS:
			list_known_mbus_formats();
			exit(0);

		case OPT_VERSION:
			print_version();
			exit(0);

		default:
			printf("Invalid option -%c\n", opt);
			printf("Run %s -h for help.\n", argv[0]);
			return 1;
		}
	}

	return 0;
}

