#include "udebug.h"

static struct blob_attr *
find_attr(struct blob_attr *attr, const char *name, enum blobmsg_type type)
{
	struct blobmsg_policy policy = { name, type };
	struct blob_attr *ret;

	if (!attr)
		return NULL;

	blobmsg_parse_attr(&policy, 1, &ret, attr);
	return ret;
}

static void
udebug_ubus_msg_cb(struct udebug_ubus *ctx, struct blob_attr *data)
{
	struct blob_attr *en_attr;
	bool enabled;

	data = find_attr(data, "service", BLOBMSG_TYPE_TABLE);
	data = find_attr(data, ctx->service, BLOBMSG_TYPE_TABLE);
	if (!data)
		return;

	en_attr = find_attr(data, "enabled", BLOBMSG_TYPE_STRING);
	enabled = en_attr && !!atoi(blobmsg_get_string(en_attr));
	ctx->cb(ctx, data, enabled);
}

static int
udebug_ubus_notify_cb(struct ubus_context *ubus, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct udebug_ubus *ctx = container_of(obj, struct udebug_ubus, sub.obj);

	if (!strcmp(method, "config"))
		udebug_ubus_msg_cb(ctx, msg);

	return 0;
}

static void
udebug_ubus_req_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	udebug_ubus_msg_cb(req->priv, msg);
}

static bool
udebug_ubus_new_obj_cb(struct ubus_context *ubus, struct ubus_subscriber *sub,
		       const char *path)
{
	struct udebug_ubus *ctx = container_of(sub, struct udebug_ubus, sub);

	if (strcmp(path, "udebug") != 0)
		return false;

	uloop_timeout_set(&ctx->t, 1);
	return true;
}

static void udebug_ubus_get_config(struct uloop_timeout *t)
{
	struct udebug_ubus *ctx = container_of(t, struct udebug_ubus, t);
	uint32_t id;

	if (ubus_lookup_id(ctx->ubus, "udebug", &id))
		return;

	ubus_invoke(ctx->ubus, id, "get_config", NULL, udebug_ubus_req_cb, ctx, 1000);
}

void udebug_ubus_ring_init(struct udebug *ud, struct udebug_ubus_ring *ring)
{
	if (!ring->size)
		ring->size = ring->default_size;
	if (!ring->entries)
		ring->entries = ring->default_entries;
	udebug_buf_init(ring->buf, ring->entries, ring->size);
	udebug_buf_add(ud, ring->buf, ring->meta);
}

void udebug_ubus_apply_config(struct udebug *ud, struct udebug_ubus_ring *rings, int n,
			      struct blob_attr *data, bool enabled)
{
	enum {
		CFG_ATTR_ENABLE,
		CFG_ATTR_SIZE,
		CFG_ATTR_ENTRIES,
		__CFG_ATTR_MAX,
	};
	static struct blobmsg_policy policy[] = {
		[CFG_ATTR_ENABLE] = { NULL, BLOBMSG_TYPE_STRING },
		[CFG_ATTR_SIZE] = { NULL, BLOBMSG_TYPE_STRING },
		[CFG_ATTR_ENTRIES] = { NULL, BLOBMSG_TYPE_STRING },
	};

	for (size_t i = 0; i < n; i++) {
		struct blob_attr *tb[__CFG_ATTR_MAX], *cur;
		struct udebug_buf *buf = rings[i].buf;
		const char *name = rings[i].meta->name;
		int name_len = strlen(name);
		unsigned int size, entries;
		bool cur_enabled = enabled;
		char *str;

		policy[CFG_ATTR_ENABLE].name = name;

#define SIZE_FMT "%s_size"
		str = alloca(sizeof(SIZE_FMT) + name_len);
		sprintf(str, SIZE_FMT, name);
		policy[CFG_ATTR_SIZE].name = str;

#define ENTRIES_FMT "%s_entries"
		str = alloca(sizeof(ENTRIES_FMT) + name_len);
		sprintf(str, ENTRIES_FMT, name);
		policy[CFG_ATTR_ENTRIES].name = str;

		blobmsg_parse_attr(policy, __CFG_ATTR_MAX, tb, data);

		if (enabled && (cur = tb[CFG_ATTR_ENABLE]) != NULL)
			cur_enabled = !!atoi(blobmsg_get_string(cur));

		if ((cur = tb[CFG_ATTR_SIZE]) != NULL)
			size = atoi(blobmsg_get_string(cur));
		else
			size = rings[i].default_size;

		if ((cur = tb[CFG_ATTR_ENTRIES]) != NULL)
			entries = atoi(blobmsg_get_string(cur));
		else
			entries = rings[i].default_entries;

		if (udebug_buf_valid(buf) == cur_enabled &&
		    size == rings[i].size &&
		    entries == rings[i].entries)
			continue;

		if (udebug_buf_valid(buf))
			udebug_buf_free(buf);

		rings[i].size = size;
		rings[i].entries = entries;
		if (!cur_enabled)
			continue;

		udebug_ubus_ring_init(ud, &rings[i]);
	}
}

void udebug_netlink_msg(struct udebug_buf *buf, uint16_t proto, const void *data, size_t len)
{
	struct {
		uint16_t pkttype;
		uint16_t arphdr;
		uint16_t _pad[5];
		uint16_t proto;
	} hdr = {
		.arphdr = cpu_to_be16(824),
		.proto = cpu_to_be16(proto),
	};

	if (!udebug_buf_valid(buf))
		return;

	udebug_entry_init(buf);
	udebug_entry_append(buf, &hdr, sizeof(hdr));
	udebug_entry_append(buf, data, len);
	udebug_entry_add(buf);
}

void udebug_ubus_init(struct udebug_ubus *ctx, struct ubus_context *ubus,
		      const char *service, udebug_config_cb cb)
{
	ctx->ubus = ubus;
	ctx->service = service;
	ctx->cb = cb;
	ctx->sub.new_obj_cb = udebug_ubus_new_obj_cb;
	ctx->sub.cb = udebug_ubus_notify_cb;
	ubus_register_subscriber(ubus, &ctx->sub);

	ctx->t.cb = udebug_ubus_get_config;
}

void udebug_ubus_free(struct udebug_ubus *ctx)
{
	if (!ctx->ubus)
		return;

	uloop_timeout_cancel(&ctx->t);
	ubus_unregister_subscriber(ctx->ubus, &ctx->sub);
}
