#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>

#include <osmocom/core/utils.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/application.h>

static void check_type(enum ctrl_type c)
{
	const char *t = get_value_string(ctrl_type_vals, c);
	int v = get_string_value(ctrl_type_vals, t);

	printf("ctrl type %d is %s ", c, t);
	if (v < 0)
		printf("[PARSE FAILED]\n");
	else
		printf("-> %d %s\n", v, c != v ? "FAIL" : "OK");
}

struct msgb *msgb_from_string(const char *str)
{
	char *rc;
	size_t len = strlen(str) + 1;
	/* ctrl_cmd_parse() appends a '\0' to the msgb, allow one more byte. */
	struct msgb *msg = msgb_alloc(len + 1, str);
	msg->l2h = msg->head;
	rc = (char*)msgb_put(msg, len);
	OSMO_ASSERT(rc == (char*)msg->l2h);
	strcpy(rc, str);
	return msg;
}

static void *ctx = NULL;

void print_escaped(const char *str)
{
	if (!str) {
		printf("NULL");
		return;
	}

	printf("'");
	for (;*str; str++) {
		switch (*str) {
		case '\n':
			printf("\\n");
			break;
		case '\r':
			printf("\\r");
			break;
		case '\t':
			printf("\\t");
			break;
		default:
			printf("%c", *str);
			break;
		}
	}
	printf("'");
}

void assert_same_str(const char *label, const char *expect, const char *got)
{
	if ((expect == got) || (expect && got && (strcmp(expect, got) == 0))) {
		printf("%s = ", label);
		print_escaped(got);
		printf("\n");
		return;
	}

	printf("MISMATCH for '%s':\ngot:      ", label); print_escaped(got);
	printf("\nexpected: "); print_escaped(expect);
	printf("\n");
	OSMO_ASSERT(expect == got);
}

static void assert_parsing(const char *str, const struct ctrl_cmd *expect)
{
	struct ctrl_cmd *cmd;
	struct msgb *msg = msgb_from_string(str);

	printf("test parsing: ");
	print_escaped(str);
	printf("\n");

	cmd = ctrl_cmd_parse(ctx, msg);
	OSMO_ASSERT(cmd);

	OSMO_ASSERT(expect->type == cmd->type);

#define ASSERT_SAME_STR(field) \
	assert_same_str(#field, expect->field, cmd->field)

	ASSERT_SAME_STR(id);
	ASSERT_SAME_STR(variable);
	ASSERT_SAME_STR(value);
	ASSERT_SAME_STR(reply);

	talloc_free(cmd);
	msgb_free(msg);

	printf("ok\n");
}

struct one_parsing_test {
	const char *cmd_str;
	struct ctrl_cmd expect;
};

static const struct one_parsing_test test_parsing_list[] = {
	{ "GET 1 variable",
		{
			.type = CTRL_TYPE_GET,
			.id = "1",
			.variable = "variable",
		}
	},
	{ "GET 1 variable\n",
		{
			.type = CTRL_TYPE_GET,
			.id = "1",
			.variable = "variable\n", /* current bug */
		}
	},
	{ "GET 1 var\ni\nable",
		{
			.type = CTRL_TYPE_GET,
			.id = "1",
			.variable = "var\ni\nable", /* current bug */
		}
	},
	{ "GET 1 variable value",
		{
			.type = CTRL_TYPE_GET,
			.id = "1",
			.variable = "variable",
			.value = NULL,
		}
	},
	{ "GET 1 variable value\n",
		{
			.type = CTRL_TYPE_GET,
			.id = "1",
			.variable = "variable",
			.value = NULL,
		}
	},
	{ "GET 1 variable multiple value tokens",
		{
			.type = CTRL_TYPE_GET,
			.id = "1",
			.variable = "variable",
			.value = NULL,
		}
	},
	{ "GET 1 variable multiple value tokens\n",
		{
			.type = CTRL_TYPE_GET,
			.id = "1",
			.variable = "variable",
			.value = NULL,
		}
	},
	{ "SET 1 variable value",
		{
			.type = CTRL_TYPE_SET,
			.id = "1",
			.variable = "variable",
			.value = "value",
		}
	},
	{ "SET 1 variable value\n",
		{
			.type = CTRL_TYPE_SET,
			.id = "1",
			.variable = "variable",
			.value = "value",
		}
	},
	{ "SET weird_id variable value",
		{
			.type = CTRL_TYPE_SET,
			.id = "weird_id",
			.variable = "variable",
			.value = "value",
		}
	},
	{ "SET weird_id variable value\n",
		{
			.type = CTRL_TYPE_SET,
			.id = "weird_id",
			.variable = "variable",
			.value = "value",
		}
	},
	{ "SET 1 variable multiple value tokens",
		{
			.type = CTRL_TYPE_SET,
			.id = "1",
			.variable = "variable",
			.value = "multiple value tokens",
		}
	},
	{ "SET 1 variable multiple value tokens\n",
		{
			.type = CTRL_TYPE_SET,
			.id = "1",
			.variable = "variable",
			.value = "multiple value tokens",
		}
	},
	{ "SET 1 variable value_with_trailing_spaces  ",
		{
			.type = CTRL_TYPE_SET,
			.id = "1",
			.variable = "variable",
			.value = "value_with_trailing_spaces  ",
		}
	},
	{ "SET 1 variable value_with_trailing_spaces  \n",
		{
			.type = CTRL_TYPE_SET,
			.id = "1",
			.variable = "variable",
			.value = "value_with_trailing_spaces  ",
		}
	},
	{ "SET \n special_char_id value",
		{
			.type = CTRL_TYPE_SET,
			.id = "\n",
			.variable = "special_char_id",
			.value = "value",
		}
	},
	{ "SET \t special_char_id value",
		{
			.type = CTRL_TYPE_SET,
			.id = "\t",
			.variable = "special_char_id",
			.value = "value",
		}
	},
};

static void test_parsing()
{
	int i;

	for (i = 0; i < ARRAY_SIZE(test_parsing_list); i++)
		assert_parsing(test_parsing_list[i].cmd_str,
			       &test_parsing_list[i].expect);
}

static struct log_info_cat test_categories[] = {
};

static struct log_info info = {
	.cat = test_categories,
	.num_cat = ARRAY_SIZE(test_categories),
};

int main(int argc, char **argv)
{
	ctx = talloc_named_const(NULL, 1, "ctrl_test");
	osmo_init_logging(&info);

	printf("Checking ctrl types...\n");

	check_type(CTRL_TYPE_UNKNOWN);
	check_type(CTRL_TYPE_GET);
	check_type(CTRL_TYPE_SET);
	check_type(CTRL_TYPE_GET_REPLY);
	check_type(CTRL_TYPE_SET_REPLY);
	check_type(CTRL_TYPE_TRAP);
	check_type(CTRL_TYPE_ERROR);
	check_type(64);

	test_parsing();

	return 0;
}
