
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdbool.h>

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <json-c/json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>

#include <easy/easy.h>

#include <1905_tlvs.h>
#include <cmdu.h>
#include <easymesh.h>
#include <map_module.h>

#ifndef BIT
#define BIT(n)	(1U << (n))
#endif

const char *objname;

#define map_plugin	IEEE1905_OBJECT_MULTIAP


struct mapclient_private {
	uint16_t cmdu_mask;
	struct ubus_context *ctx;
	uint32_t oid;
	uint32_t map_oid;
	struct ubus_subscriber sub;
};

static int handle_topology_query(const char *ifname, uint8_t *from,
				 uint8_t *rxdata, size_t rxlen, void *priv,
				 void *cookie)
{
	fprintf(stderr, "mapclient: %s ===>\n", __func__);

	return 0;
}

static int handle_1905_ack(const char *ifname, uint8_t *from,
			   uint8_t *rxdata, size_t rxlen, void *priv,
			   void *cookie)
{
	fprintf(stderr, "mapclient: %s ===>\n", __func__);
	return 0;
}

static int handle_ap_caps_query(const char *ifname, uint8_t *from,
				uint8_t *rxdata, size_t rxlen, void *priv,
				void *cookie)
{
	fprintf(stderr, "mapclient: %s ===>\n", __func__);
	return 0;
}


typedef int (*cmdu_handler_t)(const char *ifname, uint8_t *from, uint8_t *rxdata,
			      size_t rxlen, void *priv, void *cookie);


static const cmdu_handler_t i1905ftable[] = {
	[0x00] = NULL,
	[0x01] = NULL,
	[0x02] = handle_topology_query,
	[0x03] = NULL,
	[0x04] = NULL,
	[0x05] = NULL,
	[0x06] = NULL,
	[0x07] = NULL,
	[0x08] = NULL,
	[0x09] = NULL,
	[0x0a] = NULL,
};

#define CMDU_TYPE_MAP_START	0x8000
#define CMDU_TYPE_MAP_END	0x8001

static const cmdu_handler_t mapclient_ftable[] = {
	[0x00] = handle_1905_ack,
	[0x01] = handle_ap_caps_query,
};

static int mapclient_handle_cmdu_notification(struct blob_attr *msg)
{
	static const struct blobmsg_policy attr[5] = {
		[0] = { .name = "type", .type = BLOBMSG_TYPE_INT16 },
		[1] = { .name = "mid", .type = BLOBMSG_TYPE_INT16 },
		[2] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[3] = { .name = "source", .type = BLOBMSG_TYPE_STRING },
		[4] = { .name = "cmdu", .type = BLOBMSG_TYPE_STRING },
	};
	struct blob_attr *tb[5];
	uint16_t type, mid;
	char *data_str;
	int data_strlen;
	uint8_t *data;
	int data_len;
	const cmdu_handler_t *f;
	char ifname[16] = {0};
	uint8_t origin[6] = {0};
	char origin_str[18] = {0};
	int ret = 0;
	int idx;


	blobmsg_parse(attr, 5, tb, blob_data(msg), blob_len(msg));
	if (!tb[0] || !tb[1] || !tb[2] || !tb[3] || !tb[4]) {
		fprintf(stderr, "%s: error parsing notification\n", __func__);
		return -1;
	}

	type = blobmsg_get_u16(tb[0]);
	mid = blobmsg_get_u16(tb[1]);
	strncpy(ifname, blobmsg_data(tb[2]), 15);
	strncpy(origin_str, blobmsg_data(tb[3]), 17);
	hwaddr_aton(origin_str, origin);

	fprintf(stderr, "type = %hu  mid = %hu\n", type, mid);

	data_strlen = blobmsg_data_len(tb[4]);
	data_len = (data_strlen - 1) / 2;

	data_str = calloc(1, data_strlen * sizeof(char));
	data = calloc(1, data_len * sizeof(uint8_t));
	if (data_str && data) {
		strncpy(data_str, blobmsg_data(tb[4]), data_strlen);
		strtob(data_str, data_len, data);
	}

	if (type >= CMDU_TYPE_MAP_START) {
		idx = type - CMDU_TYPE_MAP_START;
		f = mapclient_ftable;
	} else {
		idx = type;
		f = i1905ftable;
	}

	if (ARRAY_SIZE(f) > idx && f[idx]) {
		ret = f[idx](ifname, origin, data, data_len, NULL, NULL);
		//ret = f[idx](dev_ifname, origin, rxf, priv, cookie);
	}

	if (data_str)
		free(data_str);

	if (data)
		free(data);

	return ret;
}

static int mapclient_sub_cb(struct ubus_context *ctx, struct ubus_object *obj,
			    struct ubus_request_data *req, const char *type,
			    struct blob_attr *msg)
{
	char *str;
	struct ubus_subscriber *s = container_of(obj, struct ubus_subscriber, obj);
	struct mapclient_private *priv = container_of(s, struct mapclient_private, sub);


	fprintf(stderr, "sub_cb: &mapclient_private = %p\n", priv);

	str = blobmsg_format_json(msg, true);
	fprintf(stderr, "Received notification string: '%s': %s\n", type, str);

	mapclient_handle_cmdu_notification(msg);

	free(str);

	return 0;
}

static void mapclient_sub_remove_cb(struct ubus_context *ctx,
				    struct ubus_subscriber *sub,
				    uint32_t obj)
{
	fprintf(stderr, "Object 0x%x no longer present\n", obj);
}

static int mapclient_subscribe(struct mapclient_private *priv, uint32_t oid)
{
	int ret;

	/* register mapclient as a subscriber with ubus */
	priv->sub.cb = mapclient_sub_cb;
	priv->sub.remove_cb = mapclient_sub_remove_cb;
	ret = ubus_register_subscriber(priv->ctx, &priv->sub);
	if (ret)
		fprintf(stderr, "Failed to register sub: %s\n", ubus_strerror(ret));


	/* now subscribe to events from map plugin over passed oid */
	ret = ubus_subscribe(priv->ctx, &priv->sub, oid);
	if (ret)
		fprintf(stderr, "Failed to subscribe: %s\n", ubus_strerror(ret));

	fprintf(stderr, "&mapclient_private = %p\n", priv);
	return 0;
}

static void register_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	struct mapclient_private *priv = (struct mapclient_private *)req->priv;
	const struct blobmsg_policy pol[1] = {
		[0] = { .name = "oid", .type = BLOBMSG_TYPE_INT32 },
	};
	struct blob_attr *tb[1];


	blobmsg_parse(pol, 1, tb, blob_data(msg), blob_len(msg));

	if (tb[0]) {
		uint32_t oid = blobmsg_get_u32(tb[0]);

		fprintf(stderr, "Response ID: %u\n", oid);
		mapclient_subscribe(priv, oid);
	}
}



static int mapclient_subscribe_for_cmdus(struct mapclient_private *priv)
{
	char data[2 * sizeof(struct map_module) + 1] = {0};
	int ret;
	uint32_t map_id;
	struct blob_buf bb = {};
	struct map_module m = {
		.id = 0x11111111,
		.process_cmdu_funcname = "mapclient_process_cmdu",
	};


	map_prepare_cmdu_mask(m.cmdu_mask,
			      CMDU_TYPE_TOPOLOGY_DISCOVERY,
			      CMDU_TYPE_TOPOLOGY_NOTIFICATION,
			      CMDU_TYPE_TOPOLOGY_QUERY,
			      CMDU_TYPE_TOPOLOGY_RESPONSE,
			      CMDU_TYPE_VENDOR_SPECIFIC,
			      CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH,
			      CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE,
			      CMDU_TYPE_AP_AUTOCONFIGURATION_WSC,
			      -1);


	ret = ubus_lookup_id(priv->ctx, map_plugin, &map_id);
	if (ret) {
		fprintf(stderr, "%s: %s\n",
			map_plugin, ubus_strerror(ret));
		return -1;
	}

	priv->map_oid = map_id;

	/* register as client to the map module */
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "module", "mapclient1");
	btostr((unsigned char *)&m, sizeof(struct map_module), data);
	blobmsg_add_string(&bb, "data", data);
	ret = ubus_invoke(priv->ctx, priv->map_oid, "register", bb.head, register_cb,
			  priv, 1000);
	if (ret) {
		fprintf(stderr, "Failed to 'register' with %s (err = %s)\n",
			map_plugin, ubus_strerror(ret));
	}

	blob_buf_free(&bb);

	return ret;
}

int main(int argc, char **argv)
{
	struct mapclient_private *priv;
	const char *ubus_socket = NULL;
	int ch;
	int ret;

	while ((ch = getopt(argc, argv, "s:o:")) != -1) {
		switch (ch) {
		case 's':
			ubus_socket = optarg;
			break;
		case 'o':
			objname = optarg;
			break;
		default:
			break;
		}
	}

	priv = calloc(1, sizeof(*priv));
	if (!priv)
		return -1;

	uloop_init();
	priv->ctx = ubus_connect(ubus_socket);
	if (!priv->ctx) {
		fprintf(stderr, "Failed to connect to ubus\n");
		free(priv);
		return -1;
	}

	ubus_add_uloop(priv->ctx);

	priv->oid = 0xdeadbeaf;

	ret = mapclient_subscribe_for_cmdus(priv);
	if (!ret)
		uloop_run();

	ubus_free(priv->ctx);
	uloop_done();
	free(priv);

	return 0;
}
