#include <net/if.h>
#include <errno.h>
#include <string.h>

#include <netlink/genl/genl.h>
#include <netlink/genl/family.h>
#include <netlink/genl/ctrl.h>
#include <netlink/msg.h>
#include <netlink/attr.h>

#include "nl80211.h"
#include "iw.h"

SECTION(mesh);


typedef struct _any_t {
	union {
		uint32_t as_32;
		int32_t as_s32;
		uint16_t as_16;
		uint8_t as_8;
	} u;
} _any;

/* describes a mesh parameter */
struct mesh_param_descr {
	const char *name;
	enum nl80211_meshconf_params mesh_param_num;
	int (*nla_put_fn)(struct nl_msg*, int, _any*);
	uint32_t (*parse_fn)(const char*, _any*);
	void (*nla_print_fn)(struct nlattr *);
};

/* utility functions for manipulating and printing u8/u16/u32 values and
 * timesouts. */
static int _my_nla_put_u8(struct nl_msg *n, int mesh_param_num, _any *value)
{
	return nla_put(n, mesh_param_num, sizeof(uint8_t), &value->u.as_8);
}

static int _my_nla_put_u16(struct nl_msg *n, int mesh_param_num, _any *value)
{
	return nla_put(n, mesh_param_num, sizeof(uint16_t), &value->u.as_16);
}

static int _my_nla_put_u32(struct nl_msg *n, int mesh_param_num, _any *value)
{
	return nla_put(n, mesh_param_num, sizeof(uint32_t), &value->u.as_32);
}

static uint32_t _parse_u8(const char *str, _any *ret)
{
	char *endptr = NULL;
	unsigned long int v = strtoul(str, &endptr, 10);
	if (*endptr != '\0')
		return 0xff;
	if (v > 0xff)
		return 0xff;
	ret->u.as_8 = (uint8_t)v;
	return 0;
}

static uint32_t _parse_u8_as_bool(const char *str, _any *ret)
{
	char *endptr = NULL;
	unsigned long int v = strtoul(str, &endptr, 10);
	if (*endptr != '\0')
		return 0x1;
	if (v > 0x1)
		return 0x1;
	ret->u.as_8 = (uint8_t)v;
	return 0;
}

static uint32_t _parse_u16(const char *str, _any *ret)
{
	char *endptr = NULL;
	long int v = strtol(str, &endptr, 10);
	if (*endptr != '\0')
		return 0xffff;
	if ((v < 0) || (v > 0xffff))
		return 0xffff;
	ret->u.as_16 = (uint16_t)v;
	return 0;
}

static uint32_t _parse_u32(const char *str, _any *ret)
{
	char *endptr = NULL;
	long long int v = strtoll(str, &endptr, 10);
	if (*endptr != '\0')
		return 0xffffffff;
	if ((v < 0) || (v > 0xffffffff))
		return 0xffffffff;
	ret->u.as_32 = (uint32_t)v;
	return 0;
}

static uint32_t _parse_s32(const char *str, _any *ret)
{
	char *endptr = NULL;
	long int v = strtol(str, &endptr, 10);
	if (*endptr != '\0')
		return 0xffffffff;
	if (v > 0xff)
		return 0xffffffff;
	ret->u.as_s32 = (int32_t)v;
	return 0;
}

static uint32_t _parse_u32_power_mode(const char *str, _any *ret)
{
	unsigned long int v;

	/* Parse attribute for the name of power mode */
	if (!strcmp(str, "active"))
		v = NL80211_MESH_POWER_ACTIVE;
	else if (!strcmp(str, "light"))
		v = NL80211_MESH_POWER_LIGHT_SLEEP;
	else if (!strcmp(str, "deep"))
		v = NL80211_MESH_POWER_DEEP_SLEEP;
	else
		return 0xff;

	ret->u.as_32 = (uint32_t)v;
	return 0;
}

static void _print_u8(struct nlattr *a)
{
	printf("%d", nla_get_u8(a));
}

static void _print_u16(struct nlattr *a)
{
	printf("%d", nla_get_u16(a));
}

static void _print_u16_timeout(struct nlattr *a)
{
	printf("%d milliseconds", nla_get_u16(a));
}

static void _print_u16_in_TUs(struct nlattr *a)
{
	printf("%d TUs", nla_get_u16(a));
}

static void _print_u32(struct nlattr *a)
{
	printf("%d", nla_get_u32(a));
}

static void _print_u32_timeout(struct nlattr *a)
{
	printf("%u milliseconds", nla_get_u32(a));
}

static void _print_u32_in_TUs(struct nlattr *a)
{
	printf("%d TUs", nla_get_u32(a));
}

static void _print_u32_power_mode(struct nlattr *a)
{
	unsigned long v = nla_get_u32(a);

	switch (v) {
	case NL80211_MESH_POWER_ACTIVE:
		printf("active");
		break;
	case NL80211_MESH_POWER_LIGHT_SLEEP:
		printf("light");
		break;
	case NL80211_MESH_POWER_DEEP_SLEEP:
		printf("deep");
		break;
	default:
		printf("undefined");
		break;
	}
}

static void _print_s32_in_dBm(struct nlattr *a)
{
	printf("%d dBm", (int32_t) nla_get_u32(a));
}


/* The current mesh parameters */
const static struct mesh_param_descr _mesh_param_descrs[] =
{
	{"mesh_retry_timeout",
	NL80211_MESHCONF_RETRY_TIMEOUT,
	_my_nla_put_u16, _parse_u16, _print_u16_timeout},
	{"mesh_confirm_timeout",
	NL80211_MESHCONF_CONFIRM_TIMEOUT,
	_my_nla_put_u16, _parse_u16, _print_u16_timeout},
	{"mesh_holding_timeout",
	NL80211_MESHCONF_HOLDING_TIMEOUT,
	_my_nla_put_u16, _parse_u16, _print_u16_timeout},
	{"mesh_max_peer_links",
	NL80211_MESHCONF_MAX_PEER_LINKS,
	_my_nla_put_u16, _parse_u16, _print_u16},
	{"mesh_max_retries",
	NL80211_MESHCONF_MAX_RETRIES,
	_my_nla_put_u8, _parse_u8, _print_u8},
	{"mesh_ttl",
	NL80211_MESHCONF_TTL,
	_my_nla_put_u8, _parse_u8, _print_u8},
	{"mesh_element_ttl",
	NL80211_MESHCONF_ELEMENT_TTL,
	_my_nla_put_u8, _parse_u8, _print_u8},
	{"mesh_auto_open_plinks",
	NL80211_MESHCONF_AUTO_OPEN_PLINKS,
	_my_nla_put_u8, _parse_u8_as_bool, _print_u8},
	{"mesh_hwmp_max_preq_retries",
	NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES,
	_my_nla_put_u8, _parse_u8, _print_u8},
	{"mesh_path_refresh_time",
	NL80211_MESHCONF_PATH_REFRESH_TIME,
	_my_nla_put_u32, _parse_u32, _print_u32_timeout},
	{"mesh_min_discovery_timeout",
	NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT,
	_my_nla_put_u16, _parse_u16, _print_u16_timeout},
	{"mesh_hwmp_active_path_timeout",
	NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT,
	_my_nla_put_u32, _parse_u32, _print_u32_in_TUs},
	{"mesh_hwmp_preq_min_interval",
	NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL,
	_my_nla_put_u16, _parse_u16, _print_u16_in_TUs},
	{"mesh_hwmp_net_diameter_traversal_time",
	NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME,
	_my_nla_put_u16, _parse_u16, _print_u16_in_TUs},
	{"mesh_hwmp_rootmode", NL80211_MESHCONF_HWMP_ROOTMODE,
	_my_nla_put_u8, _parse_u8, _print_u8},
	{"mesh_hwmp_rann_interval", NL80211_MESHCONF_HWMP_RANN_INTERVAL,
	_my_nla_put_u16, _parse_u16, _print_u16_in_TUs},
	{"mesh_gate_announcements", NL80211_MESHCONF_GATE_ANNOUNCEMENTS,
	_my_nla_put_u8, _parse_u8, _print_u8},
	{"mesh_fwding", NL80211_MESHCONF_FORWARDING,
	_my_nla_put_u8, _parse_u8_as_bool, _print_u8},
	{"mesh_sync_offset_max_neighor",
	NL80211_MESHCONF_SYNC_OFFSET_MAX_NEIGHBOR,
	_my_nla_put_u32, _parse_u32, _print_u32},
	{"mesh_rssi_threshold", NL80211_MESHCONF_RSSI_THRESHOLD,
	_my_nla_put_u32, _parse_s32, _print_s32_in_dBm},
	{"mesh_hwmp_active_path_to_root_timeout",
	NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT,
	_my_nla_put_u32, _parse_u32, _print_u32_in_TUs},
	{"mesh_hwmp_root_interval", NL80211_MESHCONF_HWMP_ROOT_INTERVAL,
	_my_nla_put_u16, _parse_u16, _print_u16_in_TUs},
	{"mesh_hwmp_confirmation_interval",
	NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL,
	_my_nla_put_u16, _parse_u16, _print_u16_in_TUs},
	{"mesh_power_mode", NL80211_MESHCONF_POWER_MODE,
	_my_nla_put_u32, _parse_u32_power_mode, _print_u32_power_mode},
	{"mesh_awake_window", NL80211_MESHCONF_AWAKE_WINDOW,
	_my_nla_put_u16, _parse_u16, _print_u16_in_TUs},
};

static void print_all_mesh_param_descr(void)
{
	int i;

	printf("Possible mesh parameters are:\n");

	for (i = 0; i < ARRAY_SIZE(_mesh_param_descrs); i++)
		printf(" - %s\n", _mesh_param_descrs[i].name);
}

static const struct mesh_param_descr *find_mesh_param(const char *name)
{
	int i;

	/* Find out what mesh parameter we want to change. */
	for (i = 0; i < ARRAY_SIZE(_mesh_param_descrs); i++) {
		if (strcmp(_mesh_param_descrs[i].name, name) == 0)
			return _mesh_param_descrs + i;
	}

	print_all_mesh_param_descr();
	return NULL;
}

/* Setter */
static int set_interface_meshparam(struct nl80211_state *state,
				   struct nl_cb *cb,
				   struct nl_msg *msg,
				   int argc, char **argv,
				   enum id_input id)
{
	const struct mesh_param_descr *mdescr;
	struct nlattr *container;
	uint32_t ret;
	int err;

	container = nla_nest_start(msg, NL80211_ATTR_MESH_PARAMS);
	if (!container)
		return -ENOBUFS;

	if (!argc)
		return 1;

	while (argc) {
		const char *name;
		char *value;
		_any any;

		memset(&any, 0, sizeof(_any));

		name = argv[0];
		value = strchr(name, '=');
		if (value) {
			*value = '\0';
			value++;
			argc--;
			argv++;
		} else {
			/* backward compat -- accept w/o '=' */
			if (argc < 2) {
				printf("Must specify a value for %s.\n", name);
				return 2;
			}
			value = argv[1];
			argc -= 2;
			argv += 2;
		}

		mdescr = find_mesh_param(name);
		if (!mdescr)
			return 2;

		/* Parse the new value */
		ret = mdescr->parse_fn(value, &any);
		if (ret != 0) {
			if (mdescr->mesh_param_num
			    == NL80211_MESHCONF_POWER_MODE)
				printf("%s must be set to active, light or "
					"deep.\n", mdescr->name);
			else
				printf("%s must be set to a number "
					"between 0 and %u\n",
					mdescr->name, ret);

			return 2;
		}

		err = mdescr->nla_put_fn(msg, mdescr->mesh_param_num, &any);
		if (err)
			return err;
	}
	nla_nest_end(msg, container);

	return err;
}

COMMAND(set, mesh_param, "<param>=<value> [<param>=<value>]*",
	NL80211_CMD_SET_MESH_PARAMS, 0, CIB_NETDEV, set_interface_meshparam,
	"Set mesh parameter (run command without any to see available ones).");

/* Getter */
static int print_mesh_param_handler(struct nl_msg *msg, void *arg)
{
	const struct mesh_param_descr *mdescr = arg;
	struct nlattr *attrs[NL80211_ATTR_MAX + 1];
	struct nlattr *parent_attr;
	struct nlattr *mesh_params[NL80211_MESHCONF_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));

	/* locate NL80211_ATTR_MESH_PARAMS */
	nla_parse(attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
		  genlmsg_attrlen(gnlh, 0), NULL);
	parent_attr = attrs[NL80211_ATTR_MESH_PARAMS];
	if (!parent_attr)
		return -EINVAL;

	/* unpack the mesh parameters */
	if (nla_parse_nested(mesh_params, NL80211_MESHCONF_ATTR_MAX,
			     parent_attr, NULL))
		return -EINVAL;

	if (!mdescr) {
		int i;

		for (i = 0; i < ARRAY_SIZE(_mesh_param_descrs); i++) {
			mdescr = &_mesh_param_descrs[i];
			printf("%s = ", mdescr->name);
			mdescr->nla_print_fn(mesh_params[mdescr->mesh_param_num]);
			printf("\n");
		}
		return NL_SKIP;
	}

	/* print out the mesh parameter */
	mdescr->nla_print_fn(mesh_params[mdescr->mesh_param_num]);
	printf("\n");
	return NL_SKIP;
}

static int get_interface_meshparam(struct nl80211_state *state,
				   struct nl_cb *cb,
				   struct nl_msg *msg,
				   int argc, char **argv,
				   enum id_input id)
{
	const struct mesh_param_descr *mdescr = NULL;

	if (argc > 1)
		return 1;

	if (argc == 1) {
		mdescr = find_mesh_param(argv[0]);
		if (!mdescr)
			return 2;
	}

	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM,
		  print_mesh_param_handler, (void *)mdescr);
	return 0;
}

COMMAND(get, mesh_param, "[<param>]",
	NL80211_CMD_GET_MESH_PARAMS, 0, CIB_NETDEV, get_interface_meshparam,
	"Retrieve mesh parameter (run command without any to see available ones).");

static int join_mesh(struct nl80211_state *state, struct nl_cb *cb,
		     struct nl_msg *msg, int argc, char **argv,
		     enum id_input id)
{
	struct nlattr *container;
	float rate;
	int bintval, dtim_period;
	char *end;

	if (argc < 1)
		return 1;

	NLA_PUT(msg, NL80211_ATTR_MESH_ID, strlen(argv[0]), argv[0]);
	argc--;
	argv++;

	if (argc > 1 && strcmp(argv[0], "mcast-rate") == 0) {
		argv++;
		argc--;

		rate = strtod(argv[0], &end);
		if (*end != '\0')
			return 1;

		NLA_PUT_U32(msg, NL80211_ATTR_MCAST_RATE, (int)(rate * 10));
		argv++;
		argc--;
	}

	if (argc > 1 && strcmp(argv[0], "beacon-interval") == 0) {
		argc--;
		argv++;

		bintval = strtoul(argv[0], &end, 10);
		if (*end != '\0')
			return 1;

		NLA_PUT_U32(msg, NL80211_ATTR_BEACON_INTERVAL, bintval);
		argv++;
		argc--;
	}

	if (argc > 1 && strcmp(argv[0], "dtim-period") == 0) {
		argc--;
		argv++;

		dtim_period = strtoul(argv[0], &end, 10);
		if (*end != '\0')
			return 1;

		NLA_PUT_U32(msg, NL80211_ATTR_DTIM_PERIOD, dtim_period);
		argv++;
		argc--;
	}

	container = nla_nest_start(msg, NL80211_ATTR_MESH_SETUP);
	if (!container)
		return -ENOBUFS;

	if (argc > 1 && strcmp(argv[0], "vendor_sync") == 0) {
		argv++;
		argc--;
		if (strcmp(argv[0], "on") == 0)
			NLA_PUT_U8(msg,
				   NL80211_MESH_SETUP_ENABLE_VENDOR_SYNC, 1);
		else
			NLA_PUT_U8(msg,
				   NL80211_MESH_SETUP_ENABLE_VENDOR_SYNC, 0);
		argv++;
		argc--;
	}
	/* parse and put other NL80211_ATTR_MESH_SETUP elements here */

	nla_nest_end(msg, container);

	if (!argc)
		return 0;
	return set_interface_meshparam(state, cb, msg, argc, argv, id);
 nla_put_failure:
	return -ENOBUFS;
}
COMMAND(mesh, join, "<mesh ID> [mcast-rate <rate in Mbps>]"
	" [beacon-interval <time in TUs>] [dtim-period <value>]"
	" [vendor_sync on|off] [<param>=<value>]*",
	NL80211_CMD_JOIN_MESH, 0, CIB_NETDEV, join_mesh,
	"Join a mesh with the given mesh ID with mcast-rate and mesh parameters.");

static int leave_mesh(struct nl80211_state *state, struct nl_cb *cb,
		      struct nl_msg *msg, int argc, char **argv,
		      enum id_input id)
{
	if (argc)
		return 1;

	return 0;
}
COMMAND(mesh, leave, NULL, NL80211_CMD_LEAVE_MESH, 0, CIB_NETDEV, leave_mesh,
	"Leave a mesh.");
