// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2018  Intel Corporation. All rights reserved.
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <time.h>

#include <ell/ell.h>

#include "bluetooth/bluetooth.h"
#include "bluetooth/mgmt.h"
#include "src/shared/ad.h"
#include "src/shared/mgmt.h"

#include "mesh/mesh-defs.h"
#include "mesh/mesh-mgmt.h"
#include "mesh/mesh-io.h"
#include "mesh/mesh-io-api.h"

/* List of Mesh-IO Type headers */
#include "mesh/mesh-io-mgmt.h"
#include "mesh/mesh-io-generic.h"
#include "mesh/mesh-io-unit.h"

struct loop_data {
	uint16_t len;
	uint8_t data[];
};

/* List of Supported Mesh-IO Types */
static const struct mesh_io_table table[] = {
	{MESH_IO_TYPE_MGMT,	&mesh_io_mgmt},
	{MESH_IO_TYPE_GENERIC,	&mesh_io_generic},
	{MESH_IO_TYPE_UNIT_TEST, &mesh_io_unit},
};

static const uint8_t unprv_filter[] = { BT_AD_MESH_BEACON, 0 };

static struct mesh_io *default_io;
static struct l_timeout *loop_adv_to;

static const struct mesh_io_api *io_api(enum mesh_io_type type)
{
	uint16_t i;

	for (i = 0; i < L_ARRAY_SIZE(table); i++) {
		if (table[i].type == type)
			return table[i].api;
	}

	return NULL;
}

static void refresh_rx(void *a, void *b)
{
	struct mesh_io_reg *rx_reg = a;
	struct mesh_io *io = b;

	if (io->api && io->api->reg)
		io->api->reg(io, rx_reg->filter, rx_reg->len, rx_reg->cb,
							rx_reg->user_data);
}

static void ctl_alert(int index, bool up, bool pwr, bool mesh, void *user_data)
{
	enum mesh_io_type type = L_PTR_TO_UINT(user_data);
	const struct mesh_io_api *api = NULL;

	l_warn("index %u up:%d pwr: %d mesh: %d", index, up, pwr, mesh);

	/* If specific IO controller requested, honor it */
	if (default_io->favored_index != MGMT_INDEX_NONE) {
		if (default_io->favored_index != index)
			return;

		if (!up | pwr) {
			l_warn("HCI%u failed to start generic IO %s",
				index, pwr ? ": already powered on" : "");
			if (default_io->ready)
				default_io->ready(default_io->user_data, false);
		}
	}

	if (!up && default_io->index == index) {
		/* Our controller has disappeared */
		if (default_io->api && default_io->api->destroy) {
			default_io->api->destroy(default_io);
			default_io->api = NULL;
		}

		/* Re-enumerate controllers */
		mesh_mgmt_list(ctl_alert, user_data);
		return;
	}

	/* If we already have an API, keep using it */
	if (!up || default_io->api)
		return;

	if (mesh && type != MESH_IO_TYPE_GENERIC)
		api = io_api(MESH_IO_TYPE_MGMT);
	else if (!pwr)
		api = io_api(MESH_IO_TYPE_GENERIC);

	if (api) {
		default_io->index = index;
		default_io->api = api;
		api->init(default_io, &index, default_io->user_data);
		l_queue_foreach(default_io->rx_regs, refresh_rx, default_io);
	}
}

static void free_io(struct mesh_io *io)
{
	if (io) {
		if (io->api && io->api->destroy)
			io->api->destroy(io);

		l_queue_destroy(io->rx_regs, l_free);
		io->rx_regs = NULL;
		l_free(io);
		l_warn("Destroy %p", io);
	}
}

static struct mesh_io_reg *find_by_filter(struct l_queue *rx_regs,
					const uint8_t *filter, uint8_t len)
{
	const struct l_queue_entry *entry;

	entry = l_queue_get_entries(rx_regs);

	for (; entry; entry = entry->next) {
		struct mesh_io_reg *rx_reg = entry->data;

		if (rx_reg->len == len && !memcmp(rx_reg->filter, filter, len))
			return rx_reg;
	}

	return NULL;
}

struct mesh_io *mesh_io_new(enum mesh_io_type type, void *opts,
				mesh_io_ready_func_t cb, void *user_data)
{
	const struct mesh_io_api *api = NULL;

	/* Only allow one IO */
	if (default_io)
		return NULL;

	default_io = l_new(struct mesh_io, 1);
	default_io->ready = cb;
	default_io->user_data = user_data;
	default_io->favored_index = *(int *) opts;
	default_io->rx_regs = l_queue_new();

	if (type >= MESH_IO_TYPE_AUTO) {
		if (!mesh_mgmt_list(ctl_alert, L_UINT_TO_PTR(type)))
			goto fail;

		return default_io;
	}

	api = io_api(type);

	if (!api || !api->init)
		goto fail;

	default_io->api = api;

	if (!api->init(default_io, opts, user_data))
		goto fail;

	return default_io;

fail:
	free_io(default_io);
	default_io = NULL;
	return NULL;
}

void mesh_io_destroy(struct mesh_io *io)
{
}

bool mesh_io_get_caps(struct mesh_io *io, struct mesh_io_caps *caps)
{
	if (io != default_io)
		return false;

	if (io && io->api && io->api->caps)
		return io->api->caps(io, caps);

	return false;
}

bool mesh_io_register_recv_cb(struct mesh_io *io, const uint8_t *filter,
				uint8_t len, mesh_io_recv_func_t cb,
				void *user_data)
{
	struct mesh_io_reg *rx_reg;

	if (io == NULL)
		io = default_io;

	if (io != default_io || !cb || !filter || !len)
		return false;

	rx_reg = find_by_filter(io->rx_regs, filter, len);

	l_free(rx_reg);
	l_queue_remove(io->rx_regs, rx_reg);

	rx_reg = l_malloc(sizeof(struct mesh_io_reg) + len);
	rx_reg->cb = cb;
	rx_reg->len = len;
	rx_reg->user_data = user_data;
	memcpy(rx_reg->filter, filter, len);

	l_queue_push_head(io->rx_regs, rx_reg);

	if (io && io->api && io->api->reg)
		return io->api->reg(io, filter, len, cb, user_data);

	return false;
}

bool mesh_io_deregister_recv_cb(struct mesh_io *io, const uint8_t *filter,
								uint8_t len)
{
	struct mesh_io_reg *rx_reg;

	if (io != default_io)
		return false;

	rx_reg = find_by_filter(io->rx_regs,  filter, len);

	l_queue_remove(io->rx_regs, rx_reg);
	l_free(rx_reg);

	if (io && io->api && io->api->dereg)
		return io->api->dereg(io, filter, len);

	return false;
}

static void loop_foreach(void *data, void *user_data)
{
	struct mesh_io_reg *rx_reg = data;
	struct loop_data *rx = user_data;

	if (!memcmp(rx_reg->filter, unprv_filter, sizeof(unprv_filter)))
		rx_reg->cb(rx_reg->user_data, NULL, rx->data, rx->len);
}

static void loop_rx(struct l_timeout *timeout, void *user_data)
{
	struct loop_data *rx = user_data;

	l_queue_foreach(default_io->rx_regs, loop_foreach, rx);
	l_timeout_modify_ms(loop_adv_to, 500);
}

static void loop_destroy(void *user_data)
{
	l_free(user_data);
}

static void loop_unprv_beacon(const uint8_t *data, uint16_t len)
{
	struct loop_data *pkt = l_malloc(len + sizeof(struct loop_data));

	memcpy(pkt->data, data, len);
	pkt->len = len;
	l_timeout_remove(loop_adv_to);
	loop_adv_to = l_timeout_create_ms(500, loop_rx, pkt, loop_destroy);
}

bool mesh_io_send(struct mesh_io *io, struct mesh_io_send_info *info,
					const uint8_t *data, uint16_t len)
{
	if (io && io != default_io)
		return false;

	if (!io)
		io = default_io;

	/* Loop unprovisioned beacons for local clients */
	if (!memcmp(data, unprv_filter, sizeof(unprv_filter)))
		loop_unprv_beacon(data, len);

	if (io && io->api && io->api->send)
		return io->api->send(io, info, data, len);

	return false;
}

bool mesh_io_send_cancel(struct mesh_io *io, const uint8_t *pattern,
								uint8_t len)
{
	if (io && io != default_io)
		return false;

	if (!io)
		io = default_io;

	if (loop_adv_to && len >= 2 && !memcmp(pattern, unprv_filter, 2)) {
		l_timeout_remove(loop_adv_to);
		loop_adv_to = NULL;
	}

	if (io && io->api && io->api->cancel)
		return io->api->cancel(io, pattern, len);

	return false;
}
