"""
Signal Level Attenuation Characterization (SLAC)
HomePlug Green PHY Specification
"""
import logging
import sys

from pypacker import pypacker
from pypacker.pypacker import Packet
from pypacker.structcbs import pack_H_le, unpack_H, unpack_H_le
from pypacker.triggerlist import TriggerList

logger = logging.getLogger("pypacker")

MASK_MSGTYPE_LE = 0xFCF9
MASK_MMTYPELSB_LE = 0x0300
MASK_MMTYPEMSB_LE = 0x0006

module_this = sys.modules[__name__]

# who defined all that useless messages???
TYPEINFO_DESCRIPTION = {
	# Central <-> Station
	0x0000: "CC_CCO_APPOINT",
	0x0004: "CC_BACKUP_APP",
	0x0008: "CC_LINK_INFO",
	0x000C: "CC_HANDOVER",
	0x0010: "CC_HANDOVER_INFO",
	0x0014: "CC_DISCOVER_LIST",
	0x0018: "CC_LINK_NEW",
	0x001C: "CC_LINK_MOD",
	0x0020: "CC_LINK_SQZ",
	0x0024: "CC_LINK_REL",
	0x0028: "CC_DETECT_REPORT",
	0x002C: "CC_WHO_RU",
	0x0030: "CC_ASSOC",
	0x0034: "CC_LEAVE",
	0x0038: "CC_SET_TEI_MAP",
	0x003C: "CC_RELAY",
	0x0040: "CC_BEACON_RELIABILITY.REQ",
	0x0044: "CC_ALLOC_MOVE",
	0x0048: "CC_ACCESS_NEW",
	0x004C: "CC_ACCESS_REL",
	0x0050: "CC_DCPPC",
	0x0054: "CC_HP1_DET",
	0x0058: "CC_BLE_UPDATE",
	0x005C: "CC_BCAST_REPEAT",
	0x0060: "CC_MH_LINK_NEW",
	0x0064: "CC_ISP_DetectionReport.IND",
	0x0068: "CC_ISP_StartReSync",
	0x006C: "CC_ISP_FinishReSync",
	0x0070: "CC_ISP_ReSyncDetected.IND",
	0x0074: "CC_ISP_ReSyncTransmit.REQ",
	0x0078: "CC_POWERSAVE.",
	0x007C: "CC_POWERSAVE_EXIT.REQ",
	0x0080: "CC_POWERSAVE_LIST.REQ",
	0x0084: "CC_STOP_POW",
	# Proxy Coordinator
	0x2000: "CP_PROXY_APPOINT",
	0x2004: "PH_PROXY_APPOINT",
	0x2008: "CP_PROXY_WAKE.",
	# CCo - CCo
	0x4000: "NN_INL.REQ",
	0x4004: "NN_NEW_NET.RE",
	0x4008: "NN_ADD_ALLOC.R",
	0x400C: "NN_REL_ALLOC.R",
	0x4010: "NN_REL_NET.IND",
	# Station - Station
	0x6000: "CM_UNASSOCIATED",
	0x6004: "CM_ENCRYPTED_PAYLOAD",
	0x6008: "CM_SET_KEY",
	0x600C: "CM_GET_KEY",
	0x6010: "CM_SC_JOIN",
	0x6014: "CM_CHAN_EST",
	0x6018: "CM_TM_UPDATE",
	0x601C: "CM_AMP_MAP",
	0x6020: "CM_BRG_INFO",
	0x6024: "CM_CONN_NEW",
	0x6028: "CM_CONN_REL",
	0x602C: "CM_CONN_MOD",
	0x6030: "CM_CONN_INFO",
	0x6034: "CM_STA_CAP",
	0x6038: "CM_NW_INFO",
	0x603C: "CM_GET_BEACON",
	0x6040: "CM_HFID",
	0x6044: "CM_MME_ERROR",
	0x6048: "CM_NW_STATS",
	0x604C: "CM_LINK_STATS",
	0x6050: "CM_ROUTE_INFO",
	0x6054: "CM_UNREACHABLE",
	0x6058: "CM_MH_CONN_NEW",
	0x605C: "CM_EXTENDED_TONEMASK",
	0x6060: "CM_STA_IDENTIFY",
	0x6064: "CM_SLAC_PARM",
	0x6068: "CM_START_ATTEN_CHAR",
	0x606C: "CM_ATTEN_CHAR",
	0x6070: "CM_PKCS_CERT",
	0x6074: "CM_MNBC_SOUND",
	0x6078: "CM_VALIDATE",
	0x607C: "CM_SLAC_MATCH",
	0x6080: "CM_SLAC_USER_DATA",
	0x6084: "CM_ATTEN_PROFILE",
	0xA0B8: "VS_PL_LNK_STATUS"
}

# Reverse access of message IDs
for msgid, name in TYPEINFO_DESCRIPTION.items():
	setattr(module_this, name, msgid)

# Management message type LSB
MMTYPE_LSB_DESCRIPTION = {
	0x00: "MMTYPELSB_REQUEST",
	0x01: "MMTYPELSB_CONFIRM",
	0x02: "MMTYPELSB_INDICATION",
	0x03: "MMTYPELSB_RESPONSE"
}

for msgid, name in MMTYPE_LSB_DESCRIPTION.items():
	setattr(module_this, name, msgid)

# Management message type MSB
MMTYPE_MSB_DESCRIPTION = {
	0x00: "MMTYPEMSB_STA__CentralCoordinator",
	0x01: "MMTYPEMSB_ProxyCoordinator",
	0x02: "MMTYPEMSB_CentralCoordinator__CentralCoordinator",
	0x03: "MMTYPEMSB_STA__STA",
	0x04: "MMTYPEMSB_Manufacturer_Specific"
}

for msgid, name in MMTYPE_MSB_DESCRIPTION.items():
	setattr(module_this, name, msgid)


class CMSetKeyReq(Packet):
	__hdr__ = (
		("keytype", "B", 0),
		("mynonce", "I", 0),
		("yournonce", "I", 0),
		("pid", "B", 0),
		("prn", "H", 0),
		("pwm", "B", 0),
		("ccocapa", "B", 0),
		("nid", "7s", b"\x00" * 7),
		("neweks", "B", 0),
		# length = 0, 16
		("newkey", None, None),
	)


class CMSetKeyICnf(Packet):
	__hdr__ = (
		("result", "B", 0),
		("mynonce", "I", 0),
		("yournonce", "I", 0),
		("pid", "B", 0),
		("prn", "H", 0),
		("pwm", "B", 0),
		("ccocapa", "B", 0)
	)


class CMAttenCharInd(Packet):
	__hdr__ = (
		("apptype", "B", 0),
		("sectype", "B", 0),
		("sourceaddr", "6s", b"\x00" * 6),
		("runid", "Q", 0),
		("sourceid", "17s", b"\x00" * 17),
		("respid", "17s", b"\x00" * 17),
		("numsounds", "B", 0)
	)

	sourceaddr_s = pypacker.get_property_mac("sourceaddr")


class CMAttenCharRsp(Packet):
	__hdr__ = (
		("apptype", "B", 0),
		("sectype", "B", 0),
		("sourceaddr", "6s", b"\x00" * 6),
		("runid", "Q", 0),
		("sourceid", "17s", b"\x00" * 17),
		("respid", "17s", b"\x00" * 17),
		("result", "B", 0)
	)

	sourceaddr_s = pypacker.get_property_mac("sourceaddr")


class CMSlacParmReq(Packet):
	__hdr__ = (
		("apptype", "B", 0),
		("sectype", "B", 0),
		("runid", "Q", 0),
		# Only present if security type is 1
		("ciphersuitesize", "B", None),
		("ciphersuites", None, TriggerList)
	)


class CMSlacParmCnf(Packet):
	__hdr__ = (
		("msoundtarget", "6s", b"\x00" * 6),
		("numsounds", "B", 0),
		("timeout", "B", 0),
		("resptype", "B", 0),
		("forwardingsta", "6s", b"\x00" * 6),
		("apptype", "B", 0),
		("sectype", "B", 0),
		("runid", "Q", 0),
		# Only present if security type is 1
		("ciphersuite", "H", None)
	)

	msoundtarget_s = pypacker.get_property_mac("msoundtarget")
	forwardingsta_s = pypacker.get_property_mac("forwardingsta")


class CMStartAttenCharInd(Packet):
	__hdr__ = (
		("apptype", "B", 0),
		("sectype", "B", 0),
		("numsounds", "B", 0),
		("timeout", "B", 0),
		("resptype", "B", 0),
		("forwardingsta", "6s", b"\x00" * 6),
		("runid", "Q", 0),
	)

	forwardingsta_s = pypacker.get_property_mac("forwardingsta")


class CMMnbcSoundInd(Packet):
	__hdr__ = (
		("apptype", "B", 0),
		("sectype", "B", 0),
		("senderid", "17s", b"\x00" * 17),
		("cnt", "B", 0),
		("runid", "Q", 0),
		("rsvd", "8s", b"\x00" * 8),
		("rnd", "16s", b"\x00" * 16)
	)


class CMSlacMatchReq(Packet):
	__hdr__ = (
		("apptype", "B", 0),
		("sectype", "B", 0),
		("mvflen", "H", 0),
		("pevid", "17s", b"\x00" * 17),
		("pevmac", "6s", b"\x00" * 6),
		("evseid", "17s", b"\x00" * 17),
		("evsemac", "6s", b"\x00" * 6),
		("runid", "Q", 0),
		("rsvd", "8s", b"\x00" * 8)
	)

	def _get_mvflen_be(self):
		return unpack_H(pack_H_le(self.mvflen))[0]

	def _set_mvflen_be(self, val):
		self.mvflen = unpack_H(pack_H_le(val))[0]

	mvflen_be = property(_get_mvflen_be, _set_mvflen_be)

	pevmac_s = pypacker.get_property_mac("pevmac")
	evsemac_s = pypacker.get_property_mac("evsemac")


class CMSlacMatchCnf(Packet):
	__hdr__ = (
		("apptype", "B", 0),
		("sectype", "B", 0),
		("mvflen", "H", 0),
		("pevid", "17s", b"\x00" * 17),
		("pevmac", "6s", b"\x00" * 6),
		("evseid", "17s", b"\x00" * 17),
		("evsemac", "6s", b"\x00" * 6),
		("runid", "Q", 0),
		("rsvd1", "8s", b"\x00" * 8),
		("nid", "7s", b"\x00" * 7),
		("rsvd2", "B", 0),
		("nmk", "16s", b"\x00" * 16),
	)

	pevmac_s = pypacker.get_property_mac("pevmac")
	evsemac_s = pypacker.get_property_mac("evsemac")


class CMLinkStatsReq(Packet):
	__hdr__ = (
		("reqtype", "B", 0),
		("reqid", "B", 0),
		("nid", "7s", b"\x00" * 7),
		("lid", "B", 0),
		("tlflag", "B", 0),
		("mgmtflag", "B", 0),
		("dasa", "6s", b"\x00" * 6)
	)

	dasa_s = pypacker.get_property_mac("dasa")


# TODO: LinkStats payload as handler for CMLinkStatsCnf, how to differentiate?

class CMLinkStatsCnf(Packet):
	__hdr__ = (
		("reqid", "B", 0),
		("restype", "B", 0),
		("linkstats", "H", 0)
	)


class VSPLLinkStatusReq(Packet):
	__hdr__ = (
		("oui", "3s", b"\x00" * 3),
	)


class VSPLLinkStatusCnf(Packet):
	__hdr__ = (
		("oui", "3s", b"\x00" * 3),
		("link", "H", 0),
	)


class AMPMapReq(Packet):
	__hdr__ = (
		("amlen", "H", 0),
	)


class AMPMapCnf(Packet):
	__hdr__ = (
		("restype", "B", 0),
	)


class CMPKCSCertReq(Packet):
	__hdr__ = (
		("targetmac", "6s", b"\x00" * 6),
		("ciphersuitesize", "B", 0),
		("cipersuite", None, TriggerList)
	)

	targetmac_s = pypacker.get_property_mac("targetmac")


class CMPKCSCertCnf(Packet):
	__hdr__ = (
		("targetmac", "6s", b"\x00" * 6),
		("status", "B", 0),
		("cipersuite", "H", 0),
		("certlen", "H", 0),
		("certpackage", None, TriggerList)
	)

	targetmac_s = pypacker.get_property_mac("targetmac")


class CMPKCSCertInd(Packet):
	"""
	When the CM_SLAC_PARM.CNF indicates that Secure SLAC is required, the PEV-HLE
	shall send a CM_PKCS_CERT.IND message. The Target MAC address for this message
	shall be set to MAC address of the PEV Green PHY station. To ensure reliable
	reception of this message at all EVSEs, it is recommended that this message be
	transmitted at least three times by the PEV-HLE. If the CM_PKCS_CERT.IND message is
	larger than 502 Octets, the message shall be fragmented by the HLE (refer to
	Section 11.1.7).
	"""
	__hdr__ = (
		("targetmac", "6s", b"\x00" * 6),
		("cipersuite", "H", 0),
		("certlen", "H", 0),
		("certpackage", None, TriggerList)
	)

	targetmac_s = pypacker.get_property_mac("targetmac")


class CMPKCSCertRsp(Packet):
	__hdr__ = (
		("targetmac", "6s", b"\x00" * 6),
		("status", "B", 0),
		("ciphersuitesize", "B", 0),  # optional
		("cipersuite", None, TriggerList)
	)

	targetmac_s = pypacker.get_property_mac("targetmac")


MASK_FRAGINDEX = 0xF0
MASK_FRAGCOUNT = 0x0F


class Slac(Packet):
	__hdr__ = (
		("version", "B", 1),
		("typeinfo", "H", 0),  #
		("frag_info", "B", 0),
		("frag_seq", "B", 0)
	)

	__handler__ = {
		CM_SET_KEY | MMTYPELSB_REQUEST: CMSetKeyReq,
		CM_SET_KEY | MMTYPELSB_CONFIRM: CMSetKeyICnf,
		CM_SLAC_MATCH | MMTYPELSB_REQUEST: CMSlacMatchReq,
		CM_SLAC_MATCH | MMTYPELSB_CONFIRM: CMSlacMatchCnf,
		CM_ATTEN_CHAR | MMTYPELSB_INDICATION: CMAttenCharInd,
		CM_ATTEN_CHAR | MMTYPELSB_RESPONSE: CMAttenCharRsp,
		CM_SLAC_PARM | MMTYPELSB_REQUEST: CMSlacParmReq,
		CM_SLAC_PARM | MMTYPELSB_CONFIRM: CMSlacParmCnf,
		# MS:new
		CM_START_ATTEN_CHAR | MMTYPELSB_INDICATION: CMStartAttenCharInd,
		CM_MNBC_SOUND | MMTYPELSB_INDICATION: CMMnbcSoundInd,
		CM_LINK_STATS | MMTYPELSB_REQUEST: CMLinkStatsReq,
		CM_LINK_STATS | MMTYPELSB_CONFIRM: CMLinkStatsCnf,
		VS_PL_LNK_STATUS | MMTYPELSB_REQUEST: VSPLLinkStatusReq,
		VS_PL_LNK_STATUS | MMTYPELSB_CONFIRM: VSPLLinkStatusCnf,
		CM_AMP_MAP | MMTYPELSB_REQUEST: AMPMapReq,
		CM_AMP_MAP | MMTYPELSB_CONFIRM: AMPMapCnf,
		CM_PKCS_CERT | MMTYPELSB_REQUEST: CMPKCSCertReq,
		CM_PKCS_CERT | MMTYPELSB_CONFIRM: CMPKCSCertCnf,
		CM_PKCS_CERT | MMTYPELSB_INDICATION: CMPKCSCertInd,
		CM_PKCS_CERT | MMTYPELSB_RESPONSE: CMPKCSCertRsp
	}

	def _dissect(self, buf):
		typeinfo_be = unpack_H_le(buf[1: 3])[0]
		hlen = 5

		# VS_PL_LNK_STATUS does not have frag
		if typeinfo_be in {0xA0B8, 0xA0B9}:
			# logger.debug("disabling frag")
			self.frag_info = None
			self.frag_seq = None
			hlen = 3
		# logger.debug("Got type %X", typeinfo_be)
		return hlen, typeinfo_be

	def _get_fragcount(self):
		return (self.frag_info & MASK_FRAGCOUNT)

	def _set_fragcount(self, fragcount):
		self.frag_info = (self.frag_info & ~MASK_FRAGCOUNT) | (fragcount & MASK_FRAGCOUNT)

	fragcount = property(_get_fragcount, _set_fragcount)

	def _get_fragindex(self):
		return (self.frag_info & MASK_FRAGINDEX) >> 4

	def _set_fragindex(self, fragindex):
		self.frag_info = (self.frag_info & ~MASK_FRAGINDEX) | ((fragindex << 4) & MASK_FRAGINDEX)

	fragindex = property(_get_fragindex, _set_fragindex)

	def _get_msgtype(self):
		typetmp = self.typeinfo & MASK_MSGTYPE_LE
		return unpack_H(pack_H_le(typetmp))[0]

	def _set_msgtype(self, msgtype):
		typetmp = unpack_H(pack_H_le(msgtype))[0]
		self.typeinfo = (self.typeinfo & ~MASK_MSGTYPE_LE) | (typetmp & MASK_MSGTYPE_LE)

	# base message type given as BE
	msgtype = property(_get_msgtype, _set_msgtype)

	def _get_msgtype_full(self):
		return unpack_H(pack_H_le(self.typeinfo))[0]

	def _set_msgtype_full(self, msgtype):
		self.typeinfo = unpack_H(pack_H_le(msgtype))[0]

	# set full typeinfo given as BE
	msgtype_full_be = property(_get_msgtype_full, _set_msgtype_full)

	def _get_msgtype_s(self):
		return TYPEINFO_DESCRIPTION.get(self.msgtype, None)

	msgtype_s = property(_get_msgtype_s)

	def _get_mmtypelsb(self):
		return (self.typeinfo & MASK_MMTYPELSB_LE) >> 8

	def _set_mmtypelsb(self, msgtype):
		typetmp = (self.typeinfo & ~MASK_MMTYPELSB_LE)
		self.typeinfo = typetmp | (msgtype << 8)

	# REQ->CNF, IND->RSP
	mmtypelsb = property(_get_mmtypelsb, _set_mmtypelsb)

	def _get_mmtypelsb_s(self):
		return MMTYPE_LSB_DESCRIPTION.get(self.mmtypelsb, None)

	mmtypelsb_s = property(_get_mmtypelsb_s, None)

	def _get_mmtypemsb(self):
		return (self.typeinfo & MASK_MMTYPEMSB_LE) >> 1

	def _set_mmtypemsb(self, msgtype):
		typetmp = (self.typeinfo & ~MASK_MMTYPEMSB_LE)
		self.typeinfo = typetmp | (msgtype << 1)

	mmtypemsb = property(_get_mmtypemsb, _set_mmtypemsb)

	def _get_mmtypemsb_s(self):
		return MMTYPE_MSB_DESCRIPTION.get(self.mmtypemsb, None)

	mmtypemsb_s = property(_get_mmtypemsb_s, None)
