#include <sys/ioctl.h>
#include <errno.h>
#include <linux/v4l2-subdev.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "util.h"
#include "log.h"

int
xioctl(int fd, int request, void *arg) {
	int r;
	do {
		r = ioctl(fd, request, arg);
	} while (r == -1 && errno == EINTR);
	return r;
}

// Copied from drivers/media/v4l2-core/v4l2-ctrls-defs.c, trimmed down
const char
*
v4l2_ctrl_get_name(unsigned int id) {
	switch (id) {
		case V4L2_CID_FLASH_LED_MODE:
			return "LED Mode";
		case V4L2_CID_FLASH_STROBE_SOURCE:
			return "Strobe Source";
		case V4L2_CID_FLASH_STROBE:
			return "Strobe";
		default:
			return "Unknown";
	}
}

// Copied from drivers/media/v4l2-core/v4l2-ctrls-defs.c, trimmed down and slightly modified
const char
*
v4l2_ctrl_get_menu_value(unsigned int id, signed int val) {
	static const char *const flash_led_mode[] = {
			"Off",
			"Flash",
			"Torch",
			NULL,
	};
	static const char *const flash_strobe_source[] = {
			"Software",
			"External",
			NULL,
	};

	switch (id) {
		case V4L2_CID_FLASH_LED_MODE:
			return flash_led_mode[val];
		case V4L2_CID_FLASH_STROBE_SOURCE:
			return flash_strobe_source[val];
		default:
			return "None/Unknown";
	}
}

// Set a control value, but ignore if it fails
void
set_control(int fd, unsigned int ctrl_id, signed int ctrl_val) {
	struct v4l2_control ctrl;
	ctrl.id = ctrl_id;
	ctrl.value = ctrl_val;
	int res = xioctl(fd, VIDIOC_S_CTRL, &ctrl);
	if (res == -1) {
		log_debug(
				"Failed to set %s to %s for flash.\n",
				v4l2_ctrl_get_name(ctrl_id),
				v4l2_ctrl_get_menu_value(ctrl_id, ctrl_val)
		);
	}
}

char *
pixfmt_to_str(unsigned int pixfmt) {
	static char result[5] = "    ";
	result[0] = (char) (pixfmt >> 0 & 0xFF);
	result[1] = (char) (pixfmt >> 8 & 0xFF);
	result[2] = (char) (pixfmt >> 16 & 0xFF);
	result[3] = (char) (pixfmt >> 24 & 0xFF);
	return result;
}


char *mprintf(const char *fmt, ...) {
	char *str = NULL;
	va_list args;
	va_start(args, fmt);
	va_list args_copy;
	va_copy(args_copy, args);
	int len = vsnprintf(NULL, 0, fmt, args);
	va_end(args);
	if (len < 0) {
		goto out;
	}
	str = malloc(len + 1);
	if (str == NULL) {
		goto out;
	}
	vsnprintf(str, len + 1, fmt, args_copy);
	out:
	va_end(args_copy);
	return str;
}

char *mread(const char *path, long *size_out) {
	FILE *src = fopen(path, "r");
	if (src == NULL) {
		return NULL;
	}
	fseek(src, 0L, SEEK_END);
	long src_size = ftell(src);
	rewind(src);
	char *data = malloc(src_size + 1);
	if (data == NULL) {
		return data;
	}
	fread(data, src_size, 1, src);
	fclose(src);
	data[src_size] = '\0';
	if(size_out != NULL) {
		*size_out = src_size;
	}
	return data;
}