/*  Copyright (C) CZ.NIC, z.s.p.o. and contributors
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  For more information, see <https://www.knot-dns.cz/>
 */

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <tap/basic.h>

#include "libknot/yparser/ypschema.h"
#include "libknot/yparser/yptrafo.h"
#include "libknot/libknot.h"

#define C_ID		"\x02""id"
#define C_INT		"\x07""integer"
#define C_BOOL		"\x04""bool"
#define C_OPT		"\x06""option"
#define C_STR		"\x06""string"
#define C_ADDR		"\x07""address"
#define C_DNAME		"\x05""dname"
#define C_HEX		"\x03""hex"
#define C_BASE64	"\x06""base64"
#define C_DATA		"\x04""data"
#define C_REF		"\x09""reference"
#define C_GRP		"\x05""group"
#define C_MULTIGRP	"\x0B""multi-group"
#define C_OPTINT	"\x06""optint"

static const yp_item_t group[] = {
	{ C_INT, YP_TINT, YP_VINT = { 0, 100, YP_NIL } },
	{ C_STR, YP_TSTR, YP_VNONE, YP_FMULTI },
	{ NULL }
};

static const yp_item_t multi_group[] = {
	{ C_ID,     YP_TSTR, YP_VNONE },
	{ C_HEX,    YP_THEX, YP_VNONE },
	{ C_BASE64, YP_TB64, YP_VNONE },
	{ NULL }
};

static const knot_lookup_t opts[] = {
	{ 1,   "one" },
	{ 10,  "ten" },
	{ 0, NULL }
};

static const knot_lookup_t optints[] = {
	{  1000,  "opt1" },
	{ -2000,  "opt2" },
	{ 0, NULL }
};

static const yp_item_t static_schema[] = {
	{ C_OPT,      YP_TOPT,   YP_VOPT = { opts } },
	{ C_BOOL,     YP_TBOOL,  YP_VNONE },
	{ C_DNAME,    YP_TDNAME, YP_VNONE },
	{ C_GRP,      YP_TGRP,   YP_VGRP = { group } },
	{ C_MULTIGRP, YP_TGRP,   YP_VGRP = { multi_group }, YP_FMULTI },
	{ C_REF,      YP_TREF,   YP_VREF = { C_MULTIGRP } },
	{ C_DATA,     YP_TDATA,  YP_VNONE },
	{ C_OPTINT,   YP_TOPTINT,YP_VOPTINT = { -2, 1000, -10, YP_STIME, 0, optints } },
	{ NULL }
};

static void schema_find_test(void)
{
	yp_item_t *schema = NULL;

	int ret = yp_schema_copy(&schema, static_schema);
	is_int(KNOT_EOK, ret, "schema copy");

	const yp_item_t *i = yp_schema_find(C_OPT, NULL, schema);
	ok(i != NULL, "schema find");
	if (i == NULL) {
		goto error_schema;
	}
	ok(strcmp(&i->name[1], &C_OPT[1]) == 0, "name check");

	i = yp_schema_find(C_STR, C_GRP, schema);
	ok(i != NULL, "schema find with parent");
	if (i == NULL) {
		goto error_schema;
	}
	ok(strcmp(&i->name[1], &C_STR[1]) == 0, "name check");

	i = yp_schema_find(C_ADDR, NULL, schema);
	ok(i == NULL, "schema not find");

	i = yp_schema_find(C_ADDR, C_GRP, schema);
	ok(i == NULL, "schema not find with parent");

error_schema:
	yp_schema_free(schema);
}

static void schema_merge_test(void)
{
	static const yp_item_t items1[] = {
		{ "\x01""1", YP_TSTR,  YP_VNONE },
		{ "\x01""2", YP_TSTR,  YP_VNONE },
		{ NULL }
	};

	static const yp_item_t items2[] = {
		{ "\x01""3", YP_TSTR,  YP_VNONE },
		{ "\x01""4", YP_TSTR,  YP_VNONE },
		{ NULL }
	};

	yp_item_t *schema = NULL;
	yp_item_t *tmp = NULL;

	int ret = yp_schema_copy(&tmp, items1);
	is_int(KNOT_EOK, ret, "schema copy");

	ret = yp_schema_merge(&schema, items1, items2);
	is_int(KNOT_EOK, ret, "schema merge");

	yp_schema_free(tmp);

	for (uint8_t i = 0; i < 4; i++) {
		yp_name_t name[3] = { '\x01', '1' + i };
		const yp_item_t *item = yp_schema_find(name, NULL, schema);
		ok(item != NULL, "schema find");
	}

	yp_schema_free(schema);
}

#define SET_INPUT_STR(str) \
	ret = yp_set_input_string(yp, str, strlen(str)); \
	is_int(KNOT_EOK, ret, "set input string");

#define PARSER_CHECK(depth) \
	ret = yp_parse(yp); \
	is_int(KNOT_EOK, ret, "parse"); \
	ret = yp_schema_check_parser(ctx, yp); \
	is_int(KNOT_EOK, ret, "check parser"); \
	node = &ctx->nodes[ctx->current]; \
	parent = node->parent; \
	ok(ctx->current == depth, "depth check");

#define PARSER_RET_CHECK(code) \
	ret = yp_parse(yp); \
	is_int(KNOT_EOK, ret, "parse"); \
	ret = yp_schema_check_parser(ctx, yp); \
	ok(ret == code, "return check parser");

static void parser_test(void)
{
	yp_parser_t yparser;
	yp_parser_t *yp = &yparser;
	yp_item_t *schema = NULL;
	yp_check_ctx_t *ctx = NULL;

	yp_init(yp);

	int ret = yp_schema_copy(&schema, static_schema);
	is_int(KNOT_EOK, ret, "schema copy");
	if (ret != KNOT_EOK) {
		goto error_parser;
	}

	ctx = yp_schema_check_init(&schema);
	ok(ctx != NULL, "create check ctx");
	if (ctx == NULL) {
		goto error_parser;
	}

	yp_node_t *node;
	yp_node_t *parent;
	const yp_item_t *id;

	diag("parser key0 test");
	SET_INPUT_STR("option: one");
	PARSER_CHECK(0);
	ok(strcmp(node->item->name + 1, "option") == 0, "name check");
	ok(node->item->type == YP_TOPT, "type check");
	ok(yp_opt(node->data) == 1, "value check");

	diag("parser group test");
	SET_INPUT_STR("group:\n integer: 20\n string: [short, \"long string\"]");
	PARSER_CHECK(0);
	ok(strcmp(node->item->name + 1, "group") == 0, "name check");
	ok(node->item->type == YP_TGRP, "type check");
	ok(node->data_len == 0, "value length check");
	PARSER_CHECK(1);
	ok(strcmp(node->item->name + 1, "integer") == 0, "name check");
	ok(node->item->type == YP_TINT, "type check");
	ok(yp_int(node->data) == 20, "value check");
	PARSER_CHECK(1);
	ok(strcmp(node->item->name + 1, "string") == 0, "name check");
	ok(node->item->type == YP_TSTR, "type check");
	ok(strcmp(yp_str(node->data), "short") == 0, "value check");
	PARSER_CHECK(1);
	ok(strcmp(node->item->name + 1, "string") == 0, "name check");
	ok(node->item->type == YP_TSTR, "type check");
	ok(strcmp(yp_str(node->data), "long string") == 0, "value check");

	diag("parser multi-group test");
	SET_INPUT_STR("multi-group:\n - id: foo\n   base64: Zm9vYmFy\nreference: foo");
	PARSER_CHECK(0);
	ok(strcmp(node->item->name + 1, "multi-group") == 0, "name check");
	ok(node->item->type == YP_TGRP, "type check");
	ok(node->data_len == 0, "value length check");
	PARSER_CHECK(0);
	ok(node->id_len > 0, "id check");
	ok(strcmp(node->item->name + 1, "multi-group") == 0, "name check");
	ok(node->item->type == YP_TGRP, "type check");
	ok(node->data_len == 0, "value length check");
	id = node->item->var.g.id;
	ok(strcmp(id->name + 1, "id") == 0, "name check");
	ok(id->type == YP_TSTR, "type check");
	ok(strcmp(yp_str(node->id), "foo") == 0, "value check");
	PARSER_CHECK(1);
	id = parent->item->var.g.id;
	ok(strcmp(parent->item->name + 1, "multi-group") == 0, "name check");
	ok(parent->item->type == YP_TGRP, "type check");
	ok(parent->data_len == 0, "value length check");
	ok(strcmp(yp_str(parent->id), "foo") == 0, "value check");
	ok(strcmp(id->name + 1, "id") == 0, "name check");
	ok(id->type == YP_TSTR, "type check");
	ok(strcmp(node->item->name + 1, "base64") == 0, "name check");
	ok(node->item->type == YP_TB64, "type check");
	ok(memcmp(yp_bin(node->data), "foobar", yp_bin_len(node->data)) == 0,
	   "value check");
	ok(node->id_len == 0, "id length check");
	PARSER_CHECK(0);
	ok(strcmp(node->item->name + 1, "reference") == 0, "name check");
	ok(node->item->type == YP_TREF, "type check");
	ok(strcmp(yp_str(node->data), "foo") == 0, "value check");

	diag("parser check return");
	SET_INPUT_STR("unknown:");
	PARSER_RET_CHECK(KNOT_YP_EINVAL_ITEM);

	SET_INPUT_STR("group:\n unknown:");
	PARSER_RET_CHECK(KNOT_EOK);
	PARSER_RET_CHECK(KNOT_YP_EINVAL_ITEM);

	SET_INPUT_STR("group:\n - unknown: data");
	PARSER_RET_CHECK(KNOT_EOK);
	PARSER_RET_CHECK(KNOT_YP_EINVAL_ITEM);

	SET_INPUT_STR("group:\n - hex: data");
	PARSER_RET_CHECK(KNOT_EOK);
	PARSER_RET_CHECK(KNOT_YP_EINVAL_ITEM);

	SET_INPUT_STR("dname:");
	PARSER_RET_CHECK(KNOT_EINVAL);

	SET_INPUT_STR("group: data");
	PARSER_RET_CHECK(KNOT_YP_ENOTSUP_DATA);

	SET_INPUT_STR("group:\n integer:");
	PARSER_RET_CHECK(KNOT_EOK);
	PARSER_RET_CHECK(KNOT_EINVAL);

	SET_INPUT_STR("multi-group:\n id:");
	PARSER_RET_CHECK(KNOT_EOK);
	PARSER_RET_CHECK(KNOT_YP_ENODATA);

	SET_INPUT_STR("multi-group:\n hex:");
	PARSER_RET_CHECK(KNOT_EOK);
	PARSER_RET_CHECK(KNOT_YP_ENOID);

error_parser:
	yp_schema_check_deinit(ctx);
	yp_schema_free(schema);
	yp_deinit(yp);
}

#define STR_CHECK(depth, key0, key1, id, data) \
	ret = yp_schema_check_str(ctx, key0, key1, id, data); \
	is_int(KNOT_EOK, ret, "check str"); \
	ok(ctx->current == depth, "depth check"); \
	node = &ctx->nodes[ctx->current]; \
	parent = node->parent;

#define STR_RET_CHECK(code, key0, key1, id, data) \
	ret = yp_schema_check_str(ctx, key0, key1, id, data); \
	ok(ret == code, "return check str");

static void str_test(void)
{
	yp_item_t *schema;
	yp_check_ctx_t *ctx = NULL;

	int ret = yp_schema_copy(&schema, static_schema);
	is_int(KNOT_EOK, ret, "schema copy");
	if (ret != KNOT_EOK) {
		goto error_str;
	}

	ctx = yp_schema_check_init(&schema);
	ok(ctx != NULL, "create check ctx");
	if (ctx == NULL) {
		goto error_str;
	}

	yp_node_t *node;
	yp_node_t *parent;
	const yp_item_t *id;

	diag("str key0 test");
	STR_CHECK(0, "option", NULL, NULL, "one");
	ok(strcmp(node->item->name + 1, "option") == 0, "name check");
	ok(node->item->type == YP_TOPT, "type check");
	ok(yp_opt(node->data) == 1, "value check");

	diag("str group test");
	STR_CHECK(0, "group", NULL, NULL, NULL);
	ok(strcmp(node->item->name + 1, "group") == 0, "name check");
	ok(node->item->type == YP_TGRP, "type check");
	ok(node->data_len == 0, "value length check");
	STR_CHECK(1, "group", "integer", NULL, "20");
	ok(strcmp(node->item->name + 1, "integer") == 0, "name check");
	ok(node->item->type == YP_TINT, "type check");
	ok(yp_int(node->data) == 20, "value check");
	STR_CHECK(1, "group", "string", NULL, "short");
	ok(strcmp(node->item->name + 1, "string") == 0, "name check");
	ok(node->item->type == YP_TSTR, "type check");
	ok(strcmp(yp_str(node->data), "short") == 0, "value check");
	STR_CHECK(1, "group", "string", NULL, "long string");
	ok(strcmp(node->item->name + 1, "string") == 0, "name check");
	ok(node->item->type == YP_TSTR, "type check");
	ok(strcmp(yp_str(node->data), "long string") == 0, "value check");

	diag("str multi-group test");
	STR_CHECK(0, "multi-group", NULL, NULL, NULL);
	ok(strcmp(node->item->name + 1, "multi-group") == 0, "name check");
	ok(node->item->type == YP_TGRP, "type check");
	ok(node->data_len == 0, "value length check");
	STR_CHECK(0, "multi-group", NULL, "foo", NULL);
	ok(node->id_len > 0, "id check");
	ok(strcmp(node->item->name + 1, "multi-group") == 0, "name check");
	ok(node->item->type == YP_TGRP, "type check");
	ok(node->data_len == 0, "value length check");
	id = node->item->var.g.id;
	ok(strcmp(id->name + 1, "id") == 0, "name check");
	ok(id->type == YP_TSTR, "type check");
	ok(strcmp(yp_str(node->id), "foo") == 0, "value check");
	STR_CHECK(1, "multi-group", "base64", "foo", "Zm9vYmFy");
	id = parent->item->var.g.id;
	ok(strcmp(parent->item->name + 1, "multi-group") == 0, "name check");
	ok(parent->item->type == YP_TGRP, "type check");
	ok(parent->data_len == 0, "value length check");
	ok(strcmp(yp_str(parent->id), "foo") == 0, "value check");
	ok(strcmp(id->name + 1, "id") == 0, "name check");
	ok(id->type == YP_TSTR, "type check");
	ok(strcmp(node->item->name + 1, "base64") == 0, "name check");
	ok(node->item->type == YP_TB64, "type check");
	ok(memcmp(yp_bin(node->data), "foobar", yp_bin_len(node->data)) == 0,
	   "value check");
	ok(node->id_len == 0, "id length check");
	STR_CHECK(0, "reference", NULL, NULL, "foo");
	ok(strcmp(node->item->name + 1, "reference") == 0, "name check");
	ok(node->item->type == YP_TREF, "type check");
	ok(strcmp(yp_str(node->data), "foo") == 0, "value check");

	diag("str check return");
	STR_RET_CHECK(KNOT_YP_EINVAL_ITEM,  "",        "",        "",   "");
	STR_RET_CHECK(KNOT_YP_EINVAL_ITEM,  NULL,      NULL,      NULL, NULL);
	STR_RET_CHECK(KNOT_YP_EINVAL_ITEM,  "unknown", NULL,      NULL, NULL);
	STR_RET_CHECK(KNOT_YP_EINVAL_ITEM,  NULL,      "unknown", NULL, NULL);
	STR_RET_CHECK(KNOT_EINVAL,          "dname",   "",        "",   "");
	STR_RET_CHECK(KNOT_EOK,             "dname",   NULL,      NULL, NULL);
	STR_RET_CHECK(KNOT_EOK,             "dname",   NULL,      NULL, ".");
	STR_RET_CHECK(KNOT_EINVAL,          "dname",   NULL,      NULL, "..");
	STR_RET_CHECK(KNOT_YP_ENOTSUP_ID,   "dname",   NULL,      "id", NULL);
	STR_RET_CHECK(KNOT_YP_EINVAL_ITEM,  "dname",   "unknown", NULL, NULL);

	STR_RET_CHECK(KNOT_EOK,             "group",   "",        "",   "");
	STR_RET_CHECK(KNOT_EOK,             "group",   NULL,      NULL, NULL);
	STR_RET_CHECK(KNOT_YP_ENOTSUP_DATA, "group",   "",        "",   "data");
	STR_RET_CHECK(KNOT_YP_EINVAL_ITEM,  "group",   "unknown", NULL, NULL);
	STR_RET_CHECK(KNOT_EOK,             "group",   "string",  NULL, NULL);
	STR_RET_CHECK(KNOT_EOK,             "group",   "string",  NULL, "data");
	STR_RET_CHECK(KNOT_EOK,             "group",   "string",  NULL, "");
	STR_RET_CHECK(KNOT_YP_ENOTSUP_ID,   "group",   "",        "id", NULL);
	STR_RET_CHECK(KNOT_YP_ENOTSUP_ID,   "group",   "string",  "id", NULL);

	STR_RET_CHECK(KNOT_EOK,             "multi-group", "",    "",      "");
	STR_RET_CHECK(KNOT_EOK,             "multi-group", NULL,  NULL,    NULL);
	STR_RET_CHECK(KNOT_YP_ENOTSUP_DATA, "multi-group", NULL,  NULL,    "data");
	STR_RET_CHECK(KNOT_EOK,             "multi-group", NULL,  "idval", NULL);
	STR_RET_CHECK(KNOT_YP_ENOTSUP_DATA, "multi-group", NULL,  "idval", "data");
	STR_RET_CHECK(KNOT_EOK,             "multi-group", "hex", "idval", NULL);
	STR_RET_CHECK(KNOT_EOK,             "multi-group", "hex", "idval", "data");
	STR_RET_CHECK(KNOT_EOK,             "multi-group", "hex", NULL,    NULL);
	STR_RET_CHECK(KNOT_EOK,             "multi-group", "hex", NULL,    "data");
	STR_RET_CHECK(KNOT_EOK,             "multi-group", "id",  "",      NULL);
	STR_RET_CHECK(KNOT_EOK,             "multi-group", "id",  NULL,    "idval");
	STR_RET_CHECK(KNOT_EOK,             "multi-group", "id",  "idval", NULL);
	STR_RET_CHECK(KNOT_YP_ENOTSUP_DATA, "multi-group", "id",  "idval", "data");

error_str:
	yp_schema_check_deinit(ctx);
	yp_schema_free(schema);
}

int main(int argc, char *argv[])
{
	plan_lazy();

	schema_find_test();
	schema_merge_test();
	parser_test();
	str_test();

	return 0;
}
