/*
 * lws-api-test-jrpc
 *
 * Written in 2010-2020 by Andy Green <andy@warmcat.com>
 *
 * This file is made available under the Creative Commons CC0 1.0
 * Universal Public Domain Dedication.
 *
 * sanity tests for jrpc
 */

#include <libwebsockets.h>

/*
 * These came from https://www.jsonrpc.org/specification but amended since we
 * do not support batch
 */

static const char * const jrpc_request_tests[] = {

	"{" /* req 1 */
		"\"jsonrpc\":"	"\"2.0\", "
		"\"method\":"	"\"subtract\", "
		"\"params\":"	"[42, 23], "
		"\"id\":"	"1"
	"}",
	"{" /* req 2 */
		"\"jsonrpc\":"	"\"2.0\", "
		"\"method\":"	"\"subtract\", "
		"\"params\":"	"[23, 42], "
		"\"id\":"	"2"
	"}",
	"{" /* req 3 */
		"\"jsonrpc\":"	"\"2.0\", "
		"\"method\":"	"\"subtract\", "
		"\"params\":"	"{"
			"\"subtrahend\":"	"23, "
			"\"minuend\":"		"42"
		"}, \"id\":"	"3"
	"}",
	/* req 4 */
	"{\"jsonrpc\": \"2.0\","
	  "\"method\": \"update\", "
	  "\"params\": [1,2,3,4,5]}",

	/* req 5 */
	"{\"jsonrpc\": \"2.0\", \"method\": \"foobar\"}",

	/* req 6: unknown method: well-known error -32601 Method Not Found */
	"{\"jsonrpc\": \"2.0\", \"method\": \"noexist\", \"id\": \"1\"}",

	/* req 7: Invalid JSON should yield well-known error -32700 Parse Error */
	"{\"jsonrpc\": \"2.0\", \"method\": \"foobar, \"params\": \"bar\", \"baz]",

	/* req 8: Invalid req (method must be string): wke -32600 Invalid Request */
	"{\"jsonrpc\": \"2.0\", \"method\": 1, \"params\": \"bar\"}",

	/* req 9: Incomplete JSON, just -32700 Parse Error */
	"{\"jsonrpc\": \"2.0\", \"method\"}",

	/* req 10: OK */
	"{\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1,2,4], \"id\": \"1\"}",

	/* req 11: OK (notify) */
	"{\"jsonrpc\": \"2.0\", \"method\": \"notify_hello\", \"params\": [7]}",

	/* req 12: OK */
	"{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42,23], \"id\": \"2\"}",

	/* req 13: -32600 */
	"{\"foo\": \"boo\"}",

	/* req 14: -32601 */
	"{\"jsonrpc\": \"2.0\", \"method\": \"noexist\", \"params\": {\"name\": \"myself\"}, \"id\": \"5\"}",

	/* req 15: OK */
	"{\"jsonrpc\": \"2.0\", \"method\": \"get_data\", \"id\": \"9\"}",

	/* req 16: OK (notify) */
	"{\"jsonrpc\": \"2.0\", \"method\": \"notify_sum\", \"params\": [1,2,4]}",

	/* req 17: OK (notify) */
	"{\"jsonrpc\": \"2.0\", \"method\": \"notify_hello\", \"params\": [7]}",
};

static const char * const jrpc_response_tests[] = {

	"{" /* req 1 */
		"\"jsonrpc\":"	"\"2.0\","
		"\"id\":"	"1, "
		"\"response\":"	"\"string\""
	"}",
	"{" /* req 2 */
		"\"jsonrpc\":"	"\"2.0\","
		"\"id\":"	"2, "
		"\"response\":"	"123"
	"}",
	"{" /* req 3 */
		"\"jsonrpc\":"	"\"2.0\","
		"\"id\":"	"3, "
		"\"response\":"	"[1,2,3]"
	"}",
	"{" /* req 4 */
		"\"jsonrpc\":"	"\"2.0\","
		"\"id\":"	"4, "
		"\"response\":"	"{\"a\": \"b\"}"
	"}",
	"{" /* req 5 */
		"\"jsonrpc\":"	"\"2.0\","
		"\"error\": {"
			"\"code\":	-32601,"
			"\"message\":"	"\"Method not found\""
		"},"
		"\"id\": \"5\""
	"}",
};

static int expected_parse_result[] = {
	/*  1 */	0,
	/*  2 */	0,
	/*  3 */	0,
	/*  4 */	0,
	/*  5 */	0,
	/*  6 */	LWSJRPCWKE__METHOD_NOT_FOUND,
	/*  7 */	LWSJRPCWKE__PARSE_ERROR,
	/*  8 */	LWSJRPCWKE__INVALID_REQUEST,
	/*  9 */	LWSJRPCWKE__PARSE_ERROR,
	/* 10 */	0,
	/* 11 */	0,
	/* 12 */	0,
	/* 13 */	LWSJRPCWKE__INVALID_REQUEST,
	/* 14 */	LWSJRPCWKE__METHOD_NOT_FOUND,
	/* 15 */	0,
	/* 16 */	0,
	/* 17 */	0,
};

static int expected_parse_result_response[] = {
	/*  1 */	0,
	/*  2 */	0,
	/*  3 */	0,
	/*  4 */	0,
	/*  5 */	0,
};

/*
 * The Method-specific parser is an lejp parser callback that only sees the
 * subtree in request "params":
 */

static const char * const paths_s1[] = {
	"subtrahend",
	"minuend",
	"[]"
};
static const char * const paths_s2[] = {
	"subtrahend",
	"minuend",
	"[]"
};

static signed char
parse_s1(struct lejp_ctx *ctx, char reason)
{
	// struct lws_jrpc_obj *r;

	/*
	 * In the canonical examples, this can take either an array like
	 *   [1,2]
	 * or an object like
	 *   {"subtrahend":23, "minuend":42 }
	 */

	lwsl_notice("%s: reason %d, path %s, buf %.*s sp %d, pst_sp %d\n",
		    __func__, reason, ctx->path, ctx->npos, ctx->buf, ctx->sp,
		    ctx->pst_sp);

	return 0;
}

static signed char
parse_s2(struct lejp_ctx *ctx, char reason)
{
	return 0;
}

static const lws_jrpc_method_t methods[] = {
		/* list methods used by the tests that are expected to exist */
	{ "subtract",		paths_s1, parse_s1, LWS_ARRAY_SIZE(paths_s1) },
	{ "foobar",		paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
	{ "update",		paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
	{ "sum",		paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
	{ "get_data",		paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
	{ "notify_hello",	paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
	{ "notify_sum",		paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
	{ NULL,			NULL, NULL, 0 } /* sentinel */
};

int main(int argc, const char **argv)
{
	int n, m, e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
	struct lws_jrpc_obj *req;
	struct lws_jrpc *jrpc;
	const char *p;

	if ((p = lws_cmdline_option(argc, argv, "-d")))
		logs = atoi(p);

	lws_set_log_level(logs, NULL);
	lwsl_user("LWS API selftest: JSON-RPC\n");

	for (m = 0; m < (int)LWS_ARRAY_SIZE(jrpc_request_tests); m++) {

		lwsl_notice("%s: ++++++++++++++++ request %d\n", __func__, m + 1);

		jrpc = lws_jrpc_create(methods, NULL);
		if (!jrpc) {
			lwsl_err("%s: unable to create JRPC context\n", __func__);
			e++;
			continue;
		}

		req = NULL;
		n = lws_jrpc_obj_parse(jrpc, LWSJRPC_PARSE_REQUEST, NULL,
				       jrpc_request_tests[m],
				       strlen(jrpc_request_tests[m]), &req);

		lwsl_info("%s: %d\n", __func__, n);

		if (n != expected_parse_result[m]) {
			lwsl_err("%s: got %d, expected %d\n", __func__,
				    n, expected_parse_result[m]);
			e++;
		}

		lws_jrpc_destroy(&jrpc);
	}

	if (e)
		goto bail;

	for (m = 0; m < (int)LWS_ARRAY_SIZE(jrpc_response_tests); m++) {

		lwsl_notice("%s: ++++++++++++++++ response %d\n", __func__, m + 1);

		jrpc = lws_jrpc_create(methods, NULL);
		if (!jrpc) {
			lwsl_err("%s: unable to create JRPC context\n", __func__);
			e++;
			continue;
		}

		req = NULL;
		n = lws_jrpc_obj_parse(jrpc, LWSJRPC_PARSE_RESPONSE, NULL,
				       jrpc_response_tests[m],
				       strlen(jrpc_response_tests[m]), &req);

		lwsl_info("%s: %d\n", __func__, n);

		if (n != expected_parse_result_response[m]) {
			lwsl_err("%s: got %d, expected %d\n", __func__, n,
				 expected_parse_result[m]);
			e++;
		}

		lws_jrpc_destroy(&jrpc);
	}

	if (e)
		goto bail;

	lwsl_user("Completed: PASS\n");

	return 0;

bail:
	lwsl_user("Completed: FAIL\n");

	return 1;
}