/* SPDX-License-Identifier: GPL-2.0-or-later */

#include "data.h"

#include <ctype.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "utils.h"

void print_data(const uint8_t data[], size_t data_size, enum data_type type)
{
	if (data_size == 0)
		return;

	switch (type) {
	case DATA_TYPE_BOOL:
		bool value = false;
		for (size_t i = 0; i < data_size; ++i) {
			if (data[i] != 0) {
				value = true;
				break;
			}
		}
		printf("%s\n", value ? "true" : "false");
		break;
	case DATA_TYPE_UINT8:
		if (data_size != 1) {
			fprintf(stderr,
				"warning: expected size of 1, got %zu\n",
				data_size);
		}

		if (data_size >= 1)
			printf("%u\n", *(uint8_t *)data);
		break;
	case DATA_TYPE_UINT16:
		if (data_size != 2) {
			fprintf(stderr,
				"warning: expected size of 2, got %zu\n",
				data_size);
		}

		if (data_size >= 2)
			printf("%u\n", *(uint16_t *)data);
		break;
	case DATA_TYPE_UINT32:
		if (data_size != 4) {
			fprintf(stderr,
				"warning: expected size of 4, got %zu\n",
				data_size);
		}

		if (data_size >= 4)
			printf("%u\n", *(uint32_t *)data);
		break;
	case DATA_TYPE_UINT64:
		if (data_size != 8) {
			fprintf(stderr,
				"warning: expected size of 8, got %zu\n",
				data_size);
		}

		if (data_size >= 8)
			printf("%llu\n", (unsigned long long)*(uint64_t *)data);
		break;
	case DATA_TYPE_ASCII:
		for (size_t i = 0; i < data_size; ++i) {
			char c = data[i];
			if (isprint(c))
				printf("%c", c);
		}
		printf("\n");
		break;
	case DATA_TYPE_UNICODE:
		char *chars = to_chars((const CHAR16 *)data, data_size);
		printf("%s\n", chars);
		free(chars);
		break;
	case DATA_TYPE_RAW:
		fwrite(data, 1, data_size, stdout);
		break;
	}
}

static uint64_t parse_uint(const char source[],
			   const char type[],
			   unsigned long long max,
			   bool *failed)
{
	char *end;
	unsigned long long uint = strtoull(source, &end, /*base=*/0);
	if (*end != '\0') {
		fprintf(stderr, "Trailing characters in \"%s\": %s\n",
			source, end);
		*failed = true;
		return 0;
	}
	if (uint > max) {
		fprintf(stderr, "Invalid %s value: %llu\n", type, uint);
		*failed = true;
		return 0;
	}

	*failed = false;
	return uint;
}

void *make_data(const char source[], size_t *data_size, enum data_type type)
{
	switch (type) {
	void *data;
	bool boolean;
	uint64_t uint;
	bool failed;

	case DATA_TYPE_BOOL:
		if (str_eq(source, "true")) {
			boolean = true;
		} else if (str_eq(source, "false")) {
			boolean = false;
		} else {
			fprintf(stderr, "Invalid boolean value: \"%s\"\n",
				source);
			return NULL;
		}

		*data_size = 1;
		data = xmalloc(*data_size);
		*(uint8_t *)data = boolean;
		return data;
	case DATA_TYPE_UINT8:
		uint = parse_uint(source, "uint8", UINT8_MAX, &failed);
		if (failed)
			return NULL;

		*data_size = 1;
		data = xmalloc(*data_size);
		*(uint8_t *)data = uint;
		return data;
	case DATA_TYPE_UINT16:
		uint = parse_uint(source, "uint16", UINT16_MAX, &failed);
		if (failed)
			return NULL;

		*data_size = 2;
		data = xmalloc(*data_size);
		*(uint16_t *)data = uint;
		return data;
	case DATA_TYPE_UINT32:
		uint = parse_uint(source, "uint32", UINT32_MAX, &failed);
		if (failed)
			return NULL;

		*data_size = 4;
		data = xmalloc(*data_size);
		*(uint32_t *)data = uint;
		return data;
	case DATA_TYPE_UINT64:
		uint = parse_uint(source, "uint64", UINT64_MAX, &failed);
		if (failed)
			return NULL;

		*data_size = 8;
		data = xmalloc(*data_size);
		*(uint64_t *)data = uint;
		return data;
	case DATA_TYPE_ASCII:
		*data_size = strlen(source) + 1;
		return strdup(source);
	case DATA_TYPE_UNICODE:
		return to_uchars(source, data_size);
	case DATA_TYPE_RAW:
		fprintf(stderr, "Raw data type is output only\n");
		return NULL;
	}

	return NULL;
}

bool parse_data_type(const char str[], enum data_type *type)
{
	if (str_eq(str, "bool"))
		*type = DATA_TYPE_BOOL;
	else if (str_eq(str, "uint8"))
		*type = DATA_TYPE_UINT8;
	else if (str_eq(str, "uint16"))
		*type = DATA_TYPE_UINT16;
	else if (str_eq(str, "uint32"))
		*type = DATA_TYPE_UINT32;
	else if (str_eq(str, "uint64"))
		*type = DATA_TYPE_UINT64;
	else if (str_eq(str, "ascii"))
		*type = DATA_TYPE_ASCII;
	else if (str_eq(str, "unicode"))
		*type = DATA_TYPE_UNICODE;
	else if (str_eq(str, "raw"))
		*type = DATA_TYPE_RAW;
	else
		return false;

	return true;
}