#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>

#include <drm.h>
#include <drm_fourcc.h>
#include <errno.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

#include "bo.h"
#include "dev.h"
#include "modeset.h"

static void show_usage(char *name)
{
	printf("Usage: %s [OPTION]\n", name);
	printf("   -c, --card      Index of dri card (ie: /dev/dri/cardN)\n");
	printf("   -r, --crtc      Index of crtc to use for test\n");
	printf("\n\n");
}

void parse_arguments(int argc, char *argv[], int *card, int *crtc)
{
	static struct option options[] = {
		{ "card", required_argument, NULL, 'c' },
		{ "crtc", required_argument, NULL, 'r' },
		{ "help", no_argument, NULL, 'h' },
	};
	int option_index = 0;
	int c;

	*card = -1;
	*crtc = -1;
	do {
		c = getopt_long(argc, argv, "c:r:h", options, &option_index);
		switch (c) {
		case 0:
		case 'h':
			show_usage(argv[0]);
			exit(0);
		case -1:
			break;
		case 'c':
			if (optarg[0] < '0' || optarg[0] > '9') {
				printf("Invalid card value '%s'!\n", optarg);
				show_usage(argv[0]);
				exit(-1);
			}
			*card = optarg[0] - '0';
			break;
		case 'r':
			if (optarg[0] < '0' || optarg[0] > '9') {
				printf("Invalid crtc value '%s'!\n", optarg);
				show_usage(argv[0]);
				exit(-1);
			}
			*crtc = optarg[0] - '0';
			break;
		}
	} while (c != -1);

	if (*card < 0 || *crtc < 0) {
		show_usage(argv[0]);
		exit(-1);
	}
}

static uint32_t get_prop_id(struct sp_dev *dev,
			drmModeObjectPropertiesPtr props, const char *name)
{
	drmModePropertyPtr p;
	uint32_t i, prop_id = 0; /* Property ID should always be > 0 */

	for (i = 0; !prop_id && i < props->count_props; i++) {
		p = drmModeGetProperty(dev->fd, props->props[i]);
		if (!strcmp(p->name, name))
			prop_id = p->prop_id;
		drmModeFreeProperty(p);
	}
	if (!prop_id)
		printf("Could not find %s property\n", name);
	return prop_id;
}

static int get_supported_format(struct sp_plane *plane, uint32_t *format)
{
	uint32_t i;

	for (i = 0; i < plane->plane->count_formats; i++) {
		if (plane->plane->formats[i] == DRM_FORMAT_XRGB8888 ||
		    plane->plane->formats[i] == DRM_FORMAT_ARGB8888 ||
		    plane->plane->formats[i] == DRM_FORMAT_RGBA8888 ||
		    plane->plane->formats[i] == DRM_FORMAT_NV12) {
			*format = plane->plane->formats[i];
			return 0;
		}
	}
	printf("No suitable formats found!\n");
	return -ENOENT;
}

struct sp_dev *create_sp_dev(int card)
{
	struct sp_dev *dev;
	int ret, fd, i, j;
	drmModeRes *r = NULL;
	drmModePlaneRes *pr = NULL;
	char card_path[256];

	snprintf(card_path, sizeof(card_path), "/dev/dri/card%d", card);

	fd = open(card_path, O_RDWR);
	if (fd < 0) {
		printf("failed to open card0\n");
		return NULL;
	}

	dev = calloc(1, sizeof(*dev));
	if (!dev) {
		printf("failed to allocate dev\n");
		return NULL;
	}

	dev->fd = fd;

	ret = drmSetClientCap(dev->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
	if (ret) {
		printf("failed to set client cap\n");
		goto err;
	}

	ret = drmSetClientCap(dev->fd, DRM_CLIENT_CAP_ATOMIC, 1);
	if (ret) {
		printf("Failed to set atomic cap %d", ret);
		goto err;
	}

	r = drmModeGetResources(dev->fd);
	if (!r) {
		printf("failed to get r\n");
		goto err;
	}

	dev->num_connectors = r->count_connectors;
	dev->connectors = calloc(dev->num_connectors,
				sizeof(struct sp_connector));
	if (!dev->connectors) {
		printf("failed to allocate connectors\n");
		goto err;
	}
	for (i = 0; i < dev->num_connectors; i++) {
		drmModeObjectPropertiesPtr props;
		dev->connectors[i].conn = drmModeGetConnector(dev->fd,
					r->connectors[i]);
		if (!dev->connectors[i].conn) {
			printf("failed to get connector %d\n", i);
			goto err;
		}

		props = drmModeObjectGetProperties(dev->fd, r->connectors[i],
				DRM_MODE_OBJECT_CONNECTOR);
		if (!props) {
			printf("failed to get connector properties\n");
			goto err;
		}

		dev->connectors[i].crtc_id_pid = get_prop_id(dev, props,
								"CRTC_ID");
		drmModeFreeObjectProperties(props);
		if (!dev->connectors[i].crtc_id_pid)
			goto err;
	}

	dev->num_encoders = r->count_encoders;
	dev->encoders = calloc(dev->num_encoders, sizeof(*dev->encoders));
	if (!dev->encoders) {
		printf("failed to allocate encoders\n");
		goto err;
	}
	for (i = 0; i < dev->num_encoders; i++) {
		dev->encoders[i] = drmModeGetEncoder(dev->fd, r->encoders[i]);
		if (!dev->encoders[i]) {
			printf("failed to get encoder %d\n", i);
			goto err;
		}
	}

	dev->num_crtcs = r->count_crtcs;
	dev->crtcs = calloc(dev->num_crtcs, sizeof(struct sp_crtc));
	if (!dev->crtcs) {
		printf("failed to allocate crtcs\n");
		goto err;
	}
	for (i = 0; i < dev->num_crtcs; i++) {
		drmModeObjectPropertiesPtr props;

		dev->crtcs[i].crtc = drmModeGetCrtc(dev->fd, r->crtcs[i]);
		if (!dev->crtcs[i].crtc) {
			printf("failed to get crtc %d\n", i);
			goto err;
		}
		dev->crtcs[i].pipe = i;
		dev->crtcs[i].num_planes = 0;

		props = drmModeObjectGetProperties(dev->fd, r->crtcs[i],
				DRM_MODE_OBJECT_CRTC);
		if (!props) {
			printf("failed to get crtc properties\n");
			goto err;
		}

		dev->crtcs[i].mode_pid = get_prop_id(dev, props, "MODE_ID");
		dev->crtcs[i].active_pid = get_prop_id(dev, props, "ACTIVE");
		drmModeFreeObjectProperties(props);
		if (!dev->crtcs[i].mode_pid || !dev->crtcs[i].active_pid)
			goto err;
	}

	pr = drmModeGetPlaneResources(dev->fd);
	if (!pr) {
		printf("failed to get plane resources\n");
		goto err;
	}
	dev->num_planes = pr->count_planes;
	dev->planes = calloc(dev->num_planes, sizeof(struct sp_plane));
	for(i = 0; i < dev->num_planes; i++) {
		drmModeObjectPropertiesPtr props;
		struct sp_plane *plane = &dev->planes[i];

		plane->dev = dev;
		plane->plane = drmModeGetPlane(dev->fd, pr->planes[i]);
		if (!plane->plane) {
			printf("failed to get plane %d\n", i);
			goto err;
		}
		plane->bo = NULL;
		plane->in_use = 0;

		ret = get_supported_format(plane, &plane->format);
		if (ret) {
			printf("failed to get supported format: %d\n", ret);
			goto err;
		}

		for (j = 0; j < dev->num_crtcs; j++) {
			if (plane->plane->possible_crtcs & (1 << j))
				dev->crtcs[j].num_planes++;
		}

		props = drmModeObjectGetProperties(dev->fd, pr->planes[i],
				DRM_MODE_OBJECT_PLANE);
		if (!props) {
			printf("failed to get plane properties\n");
			goto err;
		}
		plane->crtc_pid = get_prop_id(dev, props, "CRTC_ID");
		if (!plane->crtc_pid) {
			drmModeFreeObjectProperties(props);
			goto err;
		}
		plane->fb_pid = get_prop_id(dev, props, "FB_ID");
		if (!plane->fb_pid) {
			drmModeFreeObjectProperties(props);
			goto err;
		}
		plane->crtc_x_pid = get_prop_id(dev, props, "CRTC_X");
		if (!plane->crtc_x_pid) {
			drmModeFreeObjectProperties(props);
			goto err;
		}
		plane->crtc_y_pid = get_prop_id(dev, props, "CRTC_Y");
		if (!plane->crtc_y_pid) {
			drmModeFreeObjectProperties(props);
			goto err;
		}
		plane->crtc_w_pid = get_prop_id(dev, props, "CRTC_W");
		if (!plane->crtc_w_pid) {
			drmModeFreeObjectProperties(props);
			goto err;
		}
		plane->crtc_h_pid = get_prop_id(dev, props, "CRTC_H");
		if (!plane->crtc_h_pid) {
			drmModeFreeObjectProperties(props);
			goto err;
		}
		plane->src_x_pid = get_prop_id(dev, props, "SRC_X");
		if (!plane->src_x_pid) {
			drmModeFreeObjectProperties(props);
			goto err;
		}
		plane->src_y_pid = get_prop_id(dev, props, "SRC_Y");
		if (!plane->src_y_pid) {
			drmModeFreeObjectProperties(props);
			goto err;
		}
		plane->src_w_pid = get_prop_id(dev, props, "SRC_W");
		if (!plane->src_w_pid) {
			drmModeFreeObjectProperties(props);
			goto err;
		}
		plane->src_h_pid = get_prop_id(dev, props, "SRC_H");
		if (!plane->src_h_pid) {
			drmModeFreeObjectProperties(props);
			goto err;
		}
		drmModeFreeObjectProperties(props);
	}

	if (pr)
		drmModeFreePlaneResources(pr);
	if (r)
		drmModeFreeResources(r);

	return dev;
err:
	if (pr)
		drmModeFreePlaneResources(pr);
	if (r)
		drmModeFreeResources(r);
	destroy_sp_dev(dev);
	return NULL;
}

void destroy_sp_dev(struct sp_dev *dev)
{
	int i;

	if (dev->planes) {
		for (i = 0; i< dev->num_planes; i++) {
			if (dev->planes[i].in_use)
				put_sp_plane(&dev->planes[i]);
			if (dev->planes[i].plane)
				drmModeFreePlane(dev->planes[i].plane);
			if (dev->planes[i].bo)
				free_sp_bo(dev->planes[i].bo);
		}
		free(dev->planes);
	}
	if (dev->crtcs) {
		for (i = 0; i< dev->num_crtcs; i++) {
			if (dev->crtcs[i].crtc)
				drmModeFreeCrtc(dev->crtcs[i].crtc);
		}
		free(dev->crtcs);
	}
	if (dev->encoders) {
		for (i = 0; i< dev->num_encoders; i++) {
			if (dev->encoders[i])
				drmModeFreeEncoder(dev->encoders[i]);
		}
		free(dev->encoders);
	}
	if (dev->connectors) {
		for (i = 0; i< dev->num_connectors; i++) {
			if (dev->connectors[i].conn)
				drmModeFreeConnector(dev->connectors[i].conn);
		}
		free(dev->connectors);
	}

	close(dev->fd);
	free(dev);
}