/* -*- 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"

#ifdef ENABLE_DOT3

static lldpctl_map_t port_dot3_power_devicetype_map[] = {
	{ LLDP_DOT3_POWER_PSE, "PSE" }, { LLDP_DOT3_POWER_PD, "PD" }, { 0, NULL }
};

static lldpctl_map_t port_dot3_power_pse_source_map[] = { { LLDP_DOT3_POWER_SOURCE_BOTH,
							      "PSE + Local" },
	{ LLDP_DOT3_POWER_SOURCE_PSE, "PSE" }, { 0, NULL } };

static lldpctl_map_t port_dot3_power_pd_source_map[] = {
	{ LLDP_DOT3_POWER_SOURCE_BACKUP, "Backup source" },
	{ LLDP_DOT3_POWER_SOURCE_PRIMARY, "Primary power source" }, { 0, NULL }
};

static struct atom_map port_dot3_power_pairs_map = {
	.key = lldpctl_k_dot3_power_pairs,
	.map = { { LLDP_DOT3_POWERPAIRS_SIGNAL, "signal" },
	    { LLDP_DOT3_POWERPAIRS_SPARE, "spare" }, { 0, NULL } },
};

static struct atom_map port_dot3_power_class_map = {
	.key = lldpctl_k_dot3_power_class,
	.map = { { 1, "class 0" }, { 2, "class 1" }, { 3, "class 2" }, { 4, "class 3" },
	    { 5, "class 4" }, { 0, NULL } },
};

static struct atom_map port_dot3_power_priority_map = {
	.key = lldpctl_k_dot3_power_priority,
	.map = {
		{ 0,                          "unknown" },
		{ LLDP_MED_POW_PRIO_CRITICAL, "critical" },
		{ LLDP_MED_POW_PRIO_HIGH,     "high" },
		{ LLDP_MED_POW_PRIO_LOW,      "low" },
		{ 0, NULL },
	},
};

static struct atom_map port_dot3_power_pd_4pid_map = {
	.key = lldpctl_k_dot3_power_pd_4pid,
	.map = {
		{ 0, "PD does not support powering both modes" },
		{ 1, "PD supports powering both modes" },
		{ 0, NULL},
	},
};

static struct atom_map port_dot3_power_pse_status_map = {
	.key = lldpctl_k_dot3_power_pse_status,
	.map = {
		{ 0, "unknown" },
		{ 1, "2-pair powering" },
		{ 2, "4-pair powering dual-signature PD" },
		{ 3, "4-pair powering single-signature PD" },
		{ 0, NULL },
	},
};

static struct atom_map port_dot3_power_pd_status_map = {
	.key = lldpctl_k_dot3_power_pd_status,
	.map = {
		{ 0, "unknown" },
		{ 1, "2-pair powered PD" },
		{ 2, "4-pair powered dual-signature PD" },
		{ 3, "4-pair powered single-signature PD" },
		{ 0, NULL },
	},
};

static struct atom_map port_dot3_power_pse_pairs_ext_map = {
	.key = lldpctl_k_dot3_power_pse_pairs_ext,
	.map = {
		{ 0, "unknown" },
		{ 1, "alternative A" },
		{ 2, "alternative B" },
		{ 3, "both alternatives" },
		{ 0, NULL },
	},
};

static struct atom_map port_dot3_power_class_a_map = {
	.key = lldpctl_k_dot3_power_class_a,
	.map = {
		{ 0, "unknown" },
		{ 1, "class 1" },
		{ 2, "class 2" },
		{ 3, "class 3" },
		{ 4, "class 4" },
		{ 5, "class 5" },
		{ 6, "unknown" },
		{ 7, "single-signature PD or 2-pair only PSE" },
		{ 0, NULL },
	},
};

static struct atom_map port_dot3_power_class_b_map = {
	.key = lldpctl_k_dot3_power_class_b,
	.map = {
		{ 0, "unknown" },
		{ 1, "class 1" },
		{ 2, "class 2" },
		{ 3, "class 3" },
		{ 4, "class 4" },
		{ 5, "class 5" },
		{ 6, "unknown" },
		{ 7, "single-signature PD or 2-pair only PSE" },
		{ 0, NULL },
	},
};

static struct atom_map port_dot3_power_class_ext_map = {
	.key = lldpctl_k_dot3_power_class_ext,
	.map = {
		{ 0, "unknown" },
		{ 1, "class 1" },
		{ 2, "class 2" },
		{ 3, "class 3" },
		{ 4, "class 4" },
		{ 5, "class 5" },
		{ 6, "class 6" },
		{ 7, "class 7" },
		{ 8, "class 8" },
		{ 9, "unknown" },
		{ 10, "unknown" },
		{ 11, "unknown" },
		{ 12, "unknown" },
		{ 13, "unknown" },
		{ 14, "unknown" },
		{ 15, "dual-signature PD" },
		{ 0, NULL },
	},
};

static struct atom_map port_dot3_power_type_ext_map = {
	.key = lldpctl_k_dot3_power_type_ext,
	.map = {
		{ LLDP_DOT3_POWER_8023BT_OFF, "802.3bt off" },
		{ 1, "type 3 PSE" },
		{ 2, "type 4 PSE" },
		{ 3, "type 3 single-signature PD" },
		{ 4, "type 3 dual-signature PD" },
		{ 5, "type 4 single-signature PD" },
		{ 6, "type 4 dual-signature PD" },
		{ 7, "unknown" },
		{ 8, "unknown" },
		{ 0, NULL },
	},
};

static struct atom_map port_dot3_power_pd_load_map = {
	.key = lldpctl_k_dot3_power_pd_load,
	.map = {
		{ 0, "PD is single- or dual-signature and power is not "
		  "electrically isolated" },
		{ 1, "PD is dual-signature and power is electrically "
		  "isolated" },
		{ 0, NULL },
	},
};

ATOM_MAP_REGISTER(port_dot3_power_pairs_map, 4);
ATOM_MAP_REGISTER(port_dot3_power_class_map, 5);
ATOM_MAP_REGISTER(port_dot3_power_priority_map, 6);

static int
_lldpctl_atom_new_dot3_power(lldpctl_atom_t *atom, va_list ap)
{
	struct _lldpctl_atom_dot3_power_t *dpow =
	    (struct _lldpctl_atom_dot3_power_t *)atom;
	dpow->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
	lldpctl_atom_inc_ref((lldpctl_atom_t *)dpow->parent);
	return 1;
}

static void
_lldpctl_atom_free_dot3_power(lldpctl_atom_t *atom)
{
	struct _lldpctl_atom_dot3_power_t *dpow =
	    (struct _lldpctl_atom_dot3_power_t *)atom;
	lldpctl_atom_dec_ref((lldpctl_atom_t *)dpow->parent);
}

static const char *
_lldpctl_atom_get_str_dot3_power(lldpctl_atom_t *atom, lldpctl_key_t key)
{
	struct _lldpctl_atom_dot3_power_t *dpow =
	    (struct _lldpctl_atom_dot3_power_t *)atom;
	struct lldpd_port *port = dpow->parent->port;

	/* Local and remote port */
	switch (key) {
	case lldpctl_k_dot3_power_devicetype:
		return map_lookup(port_dot3_power_devicetype_map,
		    port->p_power.devicetype);
	case lldpctl_k_dot3_power_pairs:
		return map_lookup(port_dot3_power_pairs_map.map, port->p_power.pairs);
	case lldpctl_k_dot3_power_class:
		return map_lookup(port_dot3_power_class_map.map, port->p_power.class);
	case lldpctl_k_dot3_power_source:
		return map_lookup((port->p_power.devicetype == LLDP_DOT3_POWER_PSE) ?
			port_dot3_power_pse_source_map :
			port_dot3_power_pd_source_map,
		    port->p_power.source);
	case lldpctl_k_dot3_power_priority:
		return map_lookup(port_dot3_power_priority_map.map,
		    port->p_power.priority);
	case lldpctl_k_dot3_power_pd_4pid:
		return map_lookup(port_dot3_power_pd_4pid_map.map,
		    port->p_power.pd_4pid);
	case lldpctl_k_dot3_power_pse_status:
		return map_lookup(port_dot3_power_pse_status_map.map,
		    port->p_power.pse_status);
	case lldpctl_k_dot3_power_pd_status:
		return map_lookup(port_dot3_power_pd_status_map.map,
		    port->p_power.pd_status);
	case lldpctl_k_dot3_power_pse_pairs_ext:
		return map_lookup(port_dot3_power_pse_pairs_ext_map.map,
		    port->p_power.pse_pairs_ext);
	case lldpctl_k_dot3_power_class_a:
		return map_lookup(port_dot3_power_class_a_map.map,
		    port->p_power.class_a);
	case lldpctl_k_dot3_power_class_b:
		return map_lookup(port_dot3_power_class_b_map.map,
		    port->p_power.class_b);
	case lldpctl_k_dot3_power_class_ext:
		return map_lookup(port_dot3_power_class_ext_map.map,
		    port->p_power.class_ext);
	case lldpctl_k_dot3_power_type_ext:
		return map_lookup(port_dot3_power_type_ext_map.map,
		    port->p_power.type_ext);
	case lldpctl_k_dot3_power_pd_load:
		return map_lookup(port_dot3_power_pd_load_map.map,
		    port->p_power.pd_load);
	default:
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}
}

static long int
_lldpctl_atom_get_int_dot3_power(lldpctl_atom_t *atom, lldpctl_key_t key)
{
	struct _lldpctl_atom_dot3_power_t *dpow =
	    (struct _lldpctl_atom_dot3_power_t *)atom;
	struct lldpd_port *port = dpow->parent->port;

	/* Local and remote port */
	switch (key) {
	case lldpctl_k_dot3_power_devicetype:
		return port->p_power.devicetype;
	case lldpctl_k_dot3_power_supported:
		return port->p_power.supported;
	case lldpctl_k_dot3_power_enabled:
		return port->p_power.enabled;
	case lldpctl_k_dot3_power_paircontrol:
		return port->p_power.paircontrol;
	case lldpctl_k_dot3_power_pairs:
		return port->p_power.pairs;
	case lldpctl_k_dot3_power_class:
		return port->p_power.class;
	case lldpctl_k_dot3_power_type:
		return port->p_power.powertype;
	case lldpctl_k_dot3_power_source:
		return port->p_power.source;
	case lldpctl_k_dot3_power_priority:
		return port->p_power.priority;
	case lldpctl_k_dot3_power_requested:
		return port->p_power.requested * 100;
	case lldpctl_k_dot3_power_allocated:
		return port->p_power.allocated * 100;
	/* 802.3bt additions */
	case lldpctl_k_dot3_power_pd_4pid:
		return port->p_power.pd_4pid;
	case lldpctl_k_dot3_power_requested_a:
		return port->p_power.requested_a * 100;
	case lldpctl_k_dot3_power_requested_b:
		return port->p_power.requested_b * 100;
	case lldpctl_k_dot3_power_allocated_a:
		return port->p_power.allocated_a * 100;
	case lldpctl_k_dot3_power_allocated_b:
		return port->p_power.allocated_b * 100;
	case lldpctl_k_dot3_power_pse_status:
		return port->p_power.pse_status;
	case lldpctl_k_dot3_power_pd_status:
		return port->p_power.pd_status;
	case lldpctl_k_dot3_power_pse_pairs_ext:
		return port->p_power.pse_pairs_ext;
	case lldpctl_k_dot3_power_class_a:
		return port->p_power.class_a;
	case lldpctl_k_dot3_power_class_b:
		return port->p_power.class_b;
	case lldpctl_k_dot3_power_class_ext:
		return port->p_power.class_ext;
	case lldpctl_k_dot3_power_type_ext:
		return port->p_power.type_ext;
	case lldpctl_k_dot3_power_pd_load:
		return port->p_power.pd_load;
	case lldpctl_k_dot3_power_pse_max:
		return port->p_power.pse_max * 100;
	default:
		return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
	}
}

static lldpctl_atom_t *
_lldpctl_atom_set_int_dot3_power(lldpctl_atom_t *atom, lldpctl_key_t key,
    long int value)
{
	struct _lldpctl_atom_dot3_power_t *dpow =
	    (struct _lldpctl_atom_dot3_power_t *)atom;
	struct lldpd_port *port = dpow->parent->port;

	/* Only local port can be modified */
	if (!dpow->parent->local) {
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}

	switch (key) {
	case lldpctl_k_dot3_power_devicetype:
		switch (value) {
		case 0: /* Disabling */
		case LLDP_DOT3_POWER_PSE:
		case LLDP_DOT3_POWER_PD:
			port->p_power.devicetype = value;
			return atom;
		default:
			goto bad;
		}
	case lldpctl_k_dot3_power_supported:
		switch (value) {
		case 0:
		case 1:
			port->p_power.supported = value;
			return atom;
		default:
			goto bad;
		}
	case lldpctl_k_dot3_power_enabled:
		switch (value) {
		case 0:
		case 1:
			port->p_power.enabled = value;
			return atom;
		default:
			goto bad;
		}
	case lldpctl_k_dot3_power_paircontrol:
		switch (value) {
		case 0:
		case 1:
			port->p_power.paircontrol = value;
			return atom;
		default:
			goto bad;
		}
	case lldpctl_k_dot3_power_pairs:
		switch (value) {
		case 1:
		case 2:
			port->p_power.pairs = value;
			return atom;
		default:
			goto bad;
		}
	case lldpctl_k_dot3_power_class:
		if (value < 0 || value > 5) goto bad;
		port->p_power.class = value;
		return atom;
	case lldpctl_k_dot3_power_type:
		switch (value) {
		case LLDP_DOT3_POWER_8023AT_TYPE1:
		case LLDP_DOT3_POWER_8023AT_TYPE2:
		case LLDP_DOT3_POWER_8023AT_OFF:
			port->p_power.powertype = value;
			return atom;
		default:
			goto bad;
		}
	case lldpctl_k_dot3_power_source:
		if (value < 0 || value > 3) goto bad;
		port->p_power.source = value;
		return atom;
	case lldpctl_k_dot3_power_priority:
		switch (value) {
		case LLDP_DOT3_POWER_PRIO_UNKNOWN:
		case LLDP_DOT3_POWER_PRIO_CRITICAL:
		case LLDP_DOT3_POWER_PRIO_HIGH:
		case LLDP_DOT3_POWER_PRIO_LOW:
			port->p_power.priority = value;
			return atom;
		default:
			goto bad;
		}
	case lldpctl_k_dot3_power_allocated:
		if (value < 0) goto bad;
		port->p_power.allocated = value / 100;
		return atom;
	case lldpctl_k_dot3_power_requested:
		if (value < 0) goto bad;
		port->p_power.requested = value / 100;
		return atom;
	default:
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}

	return atom;
bad:
	SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
	return NULL;
}

static lldpctl_atom_t *
_lldpctl_atom_set_str_dot3_power(lldpctl_atom_t *atom, lldpctl_key_t key,
    const char *value)
{
	switch (key) {
	case lldpctl_k_dot3_power_devicetype:
		return _lldpctl_atom_set_int_dot3_power(atom, key,
		    map_reverse_lookup(port_dot3_power_devicetype_map, value));
	case lldpctl_k_dot3_power_pairs:
		return _lldpctl_atom_set_int_dot3_power(atom, key,
		    map_reverse_lookup(port_dot3_power_pairs_map.map, value));
	case lldpctl_k_dot3_power_class:
		return _lldpctl_atom_set_int_dot3_power(atom, key,
		    map_reverse_lookup(port_dot3_power_class_map.map, value));
	case lldpctl_k_dot3_power_priority:
		return _lldpctl_atom_set_int_dot3_power(atom, key,
		    map_reverse_lookup(port_dot3_power_priority_map.map, value));
	default:
		SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
		return NULL;
	}
}

static struct atom_builder dot3_power = { atom_dot3_power,
	sizeof(struct _lldpctl_atom_dot3_power_t), .init = _lldpctl_atom_new_dot3_power,
	.free = _lldpctl_atom_free_dot3_power,
	.get_int = _lldpctl_atom_get_int_dot3_power,
	.set_int = _lldpctl_atom_set_int_dot3_power,
	.get_str = _lldpctl_atom_get_str_dot3_power,
	.set_str = _lldpctl_atom_set_str_dot3_power };

ATOM_BUILDER_REGISTER(dot3_power, 8);

#endif
