"""
Link Aggregation Control Protocol
IEEE 802.3ad
"""
from packetracer import packetracer, triggerlist
from packetracer.structcbs import unpack_BB


# TLVs length in bytes
LACP_HEADER_LEN = 2
LACP_ACTOR_LEN = 20
LACP_PARTNER_LEN = 20
LACP_COLLECTOR_LEN = 16
LACP_TERMINATOR_LEN = 2
LACP_RESERVED_LEN = 50
# TLV types
ACTOR_TYPE = 1
PARTNER_TYPE = 2
COLLECTOR_TYPE = 3
TERMINATOR_TYPE = 0
DEFAULT_PRIORITY = 0x8000

# Bit encoding of the Actor_State and Partner_State fields
STATE_FIELD_BIT_ORDERING = {
	"expired": (0x80, 7),
	"defaulted": (0x40, 6),
	"distribute": (0x20, 5),
	"collect": (0x10, 4),
	"synch": (0x08, 3),
	"aggregate": (0x04, 2),
	"timeout": (0x02, 1),
	"activity": (0x01, 0),
}


def get_property_of_state_field(varname):
	"""Create a get/set-property for bits encoding of state field in Actor and Partner TLVs."""
	mask = STATE_FIELD_BIT_ORDERING.get(varname)[0]
	bit_order = STATE_FIELD_BIT_ORDERING.get(varname)[1]
	return property(
		lambda obj: (obj.state & mask) >> bit_order,
		lambda obj, val: obj.__setattr__("state",
			(obj.state & ~mask) | (val << bit_order)),
	)


class LACP(packetracer.Packet):
	__hdr__ = (
		("subtype", "B", 1),
		("version", "B", 1),
		("tlvlist", None, triggerlist.TriggerList),
	)

	def _dissect(self, buf):
		self._init_triggerlist("tlvlist", buf[LACP_HEADER_LEN:], self.__parse_tlv)
		return len(buf)

	@staticmethod
	def __parse_tlv(buf):
		"""Parse LACP TLVs and return them as list."""
		tlvlist = []
		shift = 0
		tlv_type, tlv_len = 1, 1
		while (tlv_type | tlv_len) != 0:
			tlv_type, tlv_len = unpack_BB(buf[shift:shift + 2])
			clz = LACP_TLV_CLS.get(tlv_type)
			if not clz == LACP_TLV_CLS.get(0):
				tlv_body = buf[shift: tlv_len + shift]
				shift += tlv_len
			else:
				tlv_body = buf[shift: LACP_TERMINATOR_LEN + shift]
				shift += LACP_TERMINATOR_LEN
			tlvlist.append(clz(tlv_body))
		tlvlist.append(LACPReserved(buf[shift: shift + LACP_RESERVED_LEN]))
		return tlvlist


class LACPActorInfoTlv(packetracer.Packet):
	__hdr__ = (
		("type", "B", ACTOR_TYPE),
		("len", "B", LACP_ACTOR_LEN),
		("sysprio", "H", DEFAULT_PRIORITY),
		("sys", "6s", b"\x00" * 6),
		("key", "H", 0),
		("portprio", "H", DEFAULT_PRIORITY),
		("port", "H", 1),
		("state", "B", 0),
		("reserved", "3s", b"\x00" * 3),
	)

	sys_s = packetracer.get_property_mac("sys")
	expired = get_property_of_state_field("expired")
	defaulted = get_property_of_state_field("defaulted")
	distribute = get_property_of_state_field("distribute")
	collect = get_property_of_state_field("collect")
	synch = get_property_of_state_field("synch")
	aggregate = get_property_of_state_field("aggregate")
	timeout = get_property_of_state_field("timeout")
	activity = get_property_of_state_field("activity")


class LACPPartnerInfoTlv(packetracer.Packet):
	__hdr__ = (
		("type", "B", PARTNER_TYPE),
		("len", "B", LACP_PARTNER_LEN),
		("sysprio", "H", DEFAULT_PRIORITY),
		("sys", "6s", b"\x00" * 6),
		("key", "H", 0),
		("portprio", "H", DEFAULT_PRIORITY),
		("port", "H", 1),
		("state", "B", 0),
		("reserved", "3s", b"\x00" * 3),
	)

	sys_s = packetracer.get_property_mac("sys")
	expired = get_property_of_state_field("expired")
	defaulted = get_property_of_state_field("defaulted")
	distribute = get_property_of_state_field("distribute")
	collect = get_property_of_state_field("collect")
	synch = get_property_of_state_field("synch")
	aggregate = get_property_of_state_field("aggregate")
	timeout = get_property_of_state_field("timeout")
	activity = get_property_of_state_field("activity")


class LACPCollectorInfoTlv(packetracer.Packet):
	__hdr__ = (
		("type", "B", COLLECTOR_TYPE),
		("len", "B", LACP_COLLECTOR_LEN),
		("maxdelay", "H", 10),
		("reserved", "12s", b"\x00" * 12),
	)


class LACPTerminatorTlv(packetracer.Packet):
	__hdr__ = (
		("type", "B", TERMINATOR_TYPE),
		("len", "B", 0),
	)


class LACPReserved(packetracer.Packet):
	__hdr__ = (
		("reserved", "50s", b"\x00" * 50),
	)


LACP_TLV_CLS = {
	ACTOR_TYPE: LACPActorInfoTlv,
	PARTNER_TYPE: LACPPartnerInfoTlv,
	COLLECTOR_TYPE: LACPCollectorInfoTlv,
	TERMINATOR_TYPE: LACPTerminatorTlv,
}
