/* -*- mode: c; c-file-style: "openbsd" -*- */
/*
 * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <arpa/inet.h>

#include "../lldpctl.h"
#include "../../log.h"
#include "../atom.h"
#include "../helpers.h"

static lldpctl_map_t chassis_id_subtype_map[] = {
	{ LLDP_CHASSISID_SUBTYPE_IFNAME, "ifname" },
	{ LLDP_CHASSISID_SUBTYPE_IFALIAS, "ifalias" },
	{ LLDP_CHASSISID_SUBTYPE_LOCAL, "local" },
	{ LLDP_CHASSISID_SUBTYPE_LLADDR, "mac" },
	{ LLDP_CHASSISID_SUBTYPE_ADDR, "ip" },
	{ LLDP_CHASSISID_SUBTYPE_PORT, "unhandled" },
	{ LLDP_CHASSISID_SUBTYPE_CHASSIS, "unhandled" },
	{ 0, NULL },
};

#ifdef ENABLE_LLDPMED

static lldpctl_map_t chassis_med_type_map[] = {
	{ LLDP_MED_CLASS_I, "Generic Endpoint (Class I)" },
	{ LLDP_MED_CLASS_II, "Media Endpoint (Class II)" },
	{ LLDP_MED_CLASS_III, "Communication Device Endpoint (Class III)" },
	{ LLDP_MED_NETWORK_DEVICE, "Network Connectivity Device" },
	{ 0, NULL },
};

#endif

static int
_lldpctl_atom_new_chassis(lldpctl_atom_t *atom, va_list ap)
{
	struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
	p->chassis = va_arg(ap, struct lldpd_chassis *);
	p->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
	p->embedded = va_arg(ap, int);
	if (p->parent && !p->embedded)
		lldpctl_atom_inc_ref((lldpctl_atom_t *)p->parent);
	return 1;
}

static void
_lldpctl_atom_free_chassis(lldpctl_atom_t *atom)
{
	struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
	/* When we have a parent, the chassis structure is in fact part of the
	 * parent, just decrement the reference count of the parent. Otherwise,
	 * we need to free the whole chassis. When embedded, we don't alter the
	 * reference count of the parent. Therefore, it's important to also not
	 * increase the reference count of this atom. See
	 * `_lldpctl_atom_get_atom_chassis' for how to handle that. */
	if (p->parent) {
		if (!p->embedded) lldpctl_atom_dec_ref((lldpctl_atom_t *)p->parent);
	} else
		lldpd_chassis_cleanup(p->chassis, 1);
}

static lldpctl_atom_t *
_lldpctl_atom_get_atom_chassis(lldpctl_atom_t *atom, lldpctl_key_t key)
{
	struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
	struct lldpd_chassis *chassis = p->chassis;

	switch (key) {
	case lldpctl_k_chassis_mgmt:
		return _lldpctl_new_atom(atom->conn, atom_mgmts_list,
		    (p->parent && p->embedded) ? (lldpctl_atom_t *)p->parent :
						 (lldpctl_atom_t *)p,
		    chassis);
	default:
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}
}

#ifdef ENABLE_LLDPMED
static lldpctl_atom_t *
_lldpctl_atom_set_str_chassis(lldpctl_atom_t *atom, lldpctl_key_t key,
    const char *value)
{
	struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
	struct lldpd_chassis *chassis = p->chassis;

	char *canary = NULL;

	int rc;

	switch (key) {
	case lldpctl_k_chassis_med_inventory_hw:
		free(chassis->c_med_hw);
		chassis->c_med_hw = xstrdup(value);
		break;
	case lldpctl_k_chassis_med_inventory_sw:
		free(chassis->c_med_sw);
		chassis->c_med_sw = xstrdup(value);
		break;
	case lldpctl_k_chassis_med_inventory_fw:
		free(chassis->c_med_fw);
		chassis->c_med_fw = xstrdup(value);
		break;
	case lldpctl_k_chassis_med_inventory_sn:
		free(chassis->c_med_sn);
		chassis->c_med_sn = xstrdup(value);
		break;
	case lldpctl_k_chassis_med_inventory_manuf:
		free(chassis->c_med_manuf);
		chassis->c_med_manuf = xstrdup(value);
		break;
	case lldpctl_k_chassis_med_inventory_model:
		free(chassis->c_med_model);
		chassis->c_med_model = xstrdup(value);
		break;
	case lldpctl_k_chassis_med_inventory_asset:
		free(chassis->c_med_asset);
		chassis->c_med_asset = xstrdup(value);
		break;
	default:
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}

	if (asprintf(&canary, "%d%s", key, value ? value : "(NULL)") == -1) {
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
		return NULL;
	}

	rc = _lldpctl_do_something(atom->conn, CONN_STATE_SET_CHASSIS_SEND,
	    CONN_STATE_SET_CHASSIS_RECV, canary, SET_CHASSIS, chassis,
	    &MARSHAL_INFO(lldpd_chassis), NULL, NULL);

	free(canary);
	if (rc == 0) return atom;
	return NULL;
}
#endif /* ENABLE_LLDPMED */

static const char *
_lldpctl_atom_get_str_chassis(lldpctl_atom_t *atom, lldpctl_key_t key)
{
	struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
	struct lldpd_chassis *chassis = p->chassis;
	char *ipaddress = NULL;
	size_t len;

	/* Local and remote port */
	switch (key) {

	case lldpctl_k_chassis_id_subtype:
		return map_lookup(chassis_id_subtype_map, chassis->c_id_subtype);
	case lldpctl_k_chassis_id:
		switch (chassis->c_id_subtype) {
		case LLDP_CHASSISID_SUBTYPE_IFNAME:
		case LLDP_CHASSISID_SUBTYPE_IFALIAS:
		case LLDP_CHASSISID_SUBTYPE_LOCAL:
			return chassis->c_id;
		case LLDP_CHASSISID_SUBTYPE_LLADDR:
			return _lldpctl_dump_in_atom(atom, (uint8_t *)chassis->c_id,
			    chassis->c_id_len, ':', 0);
		case LLDP_CHASSISID_SUBTYPE_ADDR:
			switch (chassis->c_id[0]) {
			case LLDP_MGMT_ADDR_IP4:
				len = INET_ADDRSTRLEN + 1;
				break;
			case LLDP_MGMT_ADDR_IP6:
				len = INET6_ADDRSTRLEN + 1;
				break;
			default:
				len = 0;
			}
			if (len > 0) {
				ipaddress = _lldpctl_alloc_in_atom(atom, len);
				if (!ipaddress) return NULL;
				if (inet_ntop((chassis->c_id[0] == LLDP_MGMT_ADDR_IP4) ?
					    AF_INET :
					    AF_INET6,
					&chassis->c_id[1], ipaddress, len) == NULL)
					break;
				return ipaddress;
			}
			break;
		}
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	case lldpctl_k_chassis_name:
		return chassis->c_name;
	case lldpctl_k_chassis_descr:
		return chassis->c_descr;

#ifdef ENABLE_LLDPMED
	case lldpctl_k_chassis_med_type:
		return map_lookup(chassis_med_type_map, chassis->c_med_type);
	case lldpctl_k_chassis_med_inventory_hw:
		return chassis->c_med_hw;
	case lldpctl_k_chassis_med_inventory_sw:
		return chassis->c_med_sw;
	case lldpctl_k_chassis_med_inventory_fw:
		return chassis->c_med_fw;
	case lldpctl_k_chassis_med_inventory_sn:
		return chassis->c_med_sn;
	case lldpctl_k_chassis_med_inventory_manuf:
		return chassis->c_med_manuf;
	case lldpctl_k_chassis_med_inventory_model:
		return chassis->c_med_model;
	case lldpctl_k_chassis_med_inventory_asset:
		return chassis->c_med_asset;
#endif

	default:
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}
}

static lldpctl_atom_t *
_lldpctl_atom_set_int_chassis(lldpctl_atom_t *atom, lldpctl_key_t key, long int value)
{
	int rc;
	char *canary = NULL;
	struct _lldpctl_atom_chassis_t *c = (struct _lldpctl_atom_chassis_t *)atom;
	struct lldpd_chassis chassis;
	memcpy(&chassis, c->chassis, sizeof(struct lldpd_chassis));

	switch (key) {
	case lldpctl_k_chassis_cap_enabled:
		chassis.c_cap_enabled = c->chassis->c_cap_enabled =
		    chassis.c_cap_available & value;
		break;
	default:
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}

	if (asprintf(&canary, "%d%ld", key, value) == -1) {
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
		return NULL;
	}

	rc = _lldpctl_do_something(atom->conn, CONN_STATE_SET_CHASSIS_SEND,
	    CONN_STATE_SET_CHASSIS_RECV, canary, SET_CHASSIS, &chassis,
	    &MARSHAL_INFO(lldpd_chassis), NULL, NULL);

	free(canary);
	if (rc == 0) return atom;
	return NULL;
}

static long int
_lldpctl_atom_get_int_chassis(lldpctl_atom_t *atom, lldpctl_key_t key)
{
	struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
	struct lldpd_chassis *chassis = p->chassis;

	/* Local and remote port */
	switch (key) {
	case lldpctl_k_chassis_index:
		return chassis->c_index;
	case lldpctl_k_chassis_id_subtype:
		return chassis->c_id_subtype;
	case lldpctl_k_chassis_cap_available:
		return chassis->c_cap_available;
	case lldpctl_k_chassis_cap_enabled:
		return chassis->c_cap_enabled;
#ifdef ENABLE_LLDPMED
	case lldpctl_k_chassis_med_type:
		return chassis->c_med_type;
	case lldpctl_k_chassis_med_cap:
		return chassis->c_med_cap_available;
#endif
	default:
		return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
	}
}

static const uint8_t *
_lldpctl_atom_get_buf_chassis(lldpctl_atom_t *atom, lldpctl_key_t key, size_t *n)
{
	struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
	struct lldpd_chassis *chassis = p->chassis;

	switch (key) {
	case lldpctl_k_chassis_id:
		*n = chassis->c_id_len;
		return (uint8_t *)chassis->c_id;
	default:
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}
}

static struct atom_builder chassis = {
	atom_chassis,
	sizeof(struct _lldpctl_atom_chassis_t),
	.init = _lldpctl_atom_new_chassis,
	.free = _lldpctl_atom_free_chassis,
	.get = _lldpctl_atom_get_atom_chassis,
	.get_str = _lldpctl_atom_get_str_chassis,
	.get_int = _lldpctl_atom_get_int_chassis,
	.set_int = _lldpctl_atom_set_int_chassis,
	.get_buffer = _lldpctl_atom_get_buf_chassis,
#ifdef ENABLE_LLDPMED
	.set_str = _lldpctl_atom_set_str_chassis,
#endif
};

ATOM_BUILDER_REGISTER(chassis, 3);
