"""Radiotap"""
import logging

from pypacker import pypacker, triggerlist
# Handler
from pypacker.layer12 import ieee80211
from pypacker.structcbs import pack_H_le, unpack_H_le, pack_I, pack_I_le, unpack_I, unpack_I_le

logger = logging.getLogger("pypacker")


RTAP_TYPE_80211 = 0

# Ref: http://www.radiotap.org
# Fields Ref: http://www.radiotap.org/defined-fields/all

# Defined flags ordered by appearance (little endian)
TSFT_MASK		= 0x01000000
FLAGS_MASK		= 0x02000000
RATE_MASK		= 0x04000000
CHANNEL_MASK		= 0x08000000

FHSS_MASK		= 0x10000000
DB_ANT_SIG_MASK		= 0x20000000
DB_ANT_NOISE_MASK	= 0x40000000
LOCK_QUAL_MASK		= 0x80000000

TX_ATTN_MASK		= 0x00010000
DB_TX_ATTN_MASK		= 0x00020000
DBM_TX_POWER_MASK	= 0x00040000
ANTENNA_MASK		= 0x00080000

ANT_SIG_MASK		= 0x00100000
ANT_NOISE_MASK		= 0x00200000
RX_FLAGS_MASK		= 0x00400000

CHANNELPLUS_MASK	= 0x00000400
HT_MASK			= 0x00000800

AMPDU_MASK		= 0x00001000
VHT_MASK		= 0x00002000

# 7 bits reserved

RT_NS_NEXT_MASK		= 0x00000020
VENDOR_NS_NEXT		= 0x00000040
EXT_MASK		= 0x00000080

# mask -> (length, alignment)
RADIO_FIELDS = {
	TSFT_MASK: (8, 8),
	FLAGS_MASK: (1, 1),
	RATE_MASK: (1, 1),
	# channel + flags
	CHANNEL_MASK: (4, 2),

	# fhss + pattern
	FHSS_MASK: (2, 1),
	DB_ANT_SIG_MASK: (1, 1),
	DB_ANT_NOISE_MASK: (1, 1),
	LOCK_QUAL_MASK: (2, 2),

	TX_ATTN_MASK: (2, 2),
	DB_TX_ATTN_MASK: (2, 2),
	DBM_TX_POWER_MASK: (1, 1),
	ANTENNA_MASK: (1, 1),

	ANT_SIG_MASK: (1, 1),
	ANT_NOISE_MASK: (1, 1),
	RX_FLAGS_MASK: (2, 2),

	# CHANNELPLUS_MASK	:,
	HT_MASK: (3, 1),

	AMPDU_MASK: (8, 4),
	VHT_MASK: (12, 2)

	# RT_NS_NEXT_MASK	:,
	# VENDOR_NS_NEXT	:,
	# EXT_MASK		:
}

RADIO_FIELDS_MASKS = [
	TSFT_MASK,
	FLAGS_MASK,
	RATE_MASK,
	# channel + flags
	CHANNEL_MASK,

	# fhss + pattern
	FHSS_MASK,
	DB_ANT_SIG_MASK,
	DB_ANT_NOISE_MASK,
	LOCK_QUAL_MASK,

	TX_ATTN_MASK,
	DB_TX_ATTN_MASK,
	DBM_TX_POWER_MASK,
	ANTENNA_MASK,

	ANT_SIG_MASK,
	ANT_NOISE_MASK,
	RX_FLAGS_MASK,

	HT_MASK,

	AMPDU_MASK,
	VHT_MASK
]


class FlagTriggerList(triggerlist.TriggerList):
	# no __init__ needed: we just add tuples
	def _pack(self, tuple_entry):
		#return b"".join([flag[1] for flag in self])
		return tuple_entry[1]


def get_channelinfo(channel_bytes):
	"""
	return -- [channel_mhz, channel_flags]
	"""
	return [unpack_H_le(channel_bytes[0:2])[0], unpack_H_le(channel_bytes[2:4])[0]]


def freq_to_channel(freq):
	"""
	freq -- frequqncy in Hz
	return -- channel number
	"""
	if 2412000000 <= freq <= 2472000000:
		return 1 + int((freq - 2412000000) / (5 * 1000000))
	if freq == 2484000000:
		return 14
	if 5035000000 <= freq <= 5825000000:
		return 7 + int((freq - 5035000000) / (5 * 1000000))
	return None


def channel_to_freq(channel):
	"""
	freq -- frequqncy in Hz
	return -- channel number
	"""
	if 1 <= channel <= 13:
		return 2407000000 + channel * 5 * 1000000
	if channel == 14:
		return 2484000000
	if channel >= 15:
		return 5035000000 + (channel - 7) * 5 * 1000000
	return None


class Radiotap(pypacker.Packet):
	__hdr__ = (
		("version", "B", 0),
		("pad", "B", 0),
		("len", "H", 0x0800),
		("present_flags", "I", 0),
		("flags", None, FlagTriggerList)		# stores: (XXX_MASK, value)
	)

	__handler__ = {
		RTAP_TYPE_80211: ieee80211.IEEE80211
	}

	# handle frame check sequence
	def _get_fcs(self):
		try:
			return self._fcs
		except AttributeError:
			return b""

	def _set_fcs(self, fcs):
		self._fcs = fcs

	fcs = property(_get_fcs, _set_fcs)

	def _get_channel(self):
		return self.flags[lambda v: v[0] == CHANNEL_MASK][0][1][1]

	def _set_channel(self, channel):
		freq = int(channel_to_freq(channel) / 1000000)
		self.flags[lambda v: v[0] == CHANNEL_MASK] = (CHANNEL_MASK, pack_H_le(freq))

	# get/set channel, set frequency under the hood
	channel = property(_get_channel, _set_channel)

	"""
	def _get_present_flags_be(self):
		return unpack_I(pack_I_le(self.present_flags))[0]

	def _set_present_flags_be(self, present_flags_be):
		self.present_flags = unpack_I_le(pack_I(present_flags_be))[0]

	present_flags_be = property(_get_present_flags_be, _set_present_flags_be)
	"""

	def _get_signal_strength(self):
		"""return -- Signal strength in dB or None"""
		sig = self.flags[lambda v: v[0] == DB_ANT_SIG_MASK][0][1][1]

		if sig is not None:
			return int.from_bytes(sig[1], byteorder="big", signed=True)

		return sig

	signal_strength = property(_get_signal_strength)

	def _dissect(self, buf):
		present_flags = unpack_I(buf[4:8])[0]
		#logger.debug("Flags: 0x%X" % flags)
		pos_end = len(buf)

		# Check for FSC, needs to be skipped for upper layers
		if present_flags & FLAGS_MASK == FLAGS_MASK:
			#logger.debug("Flags mask matched")
			flags_off = 8
			# TSFT present -> Flags values are after TSFT value
			if present_flags & TSFT_MASK == TSFT_MASK:
				flags_off = 8 + 8
			if buf[flags_off] & 0x10 != 0:
				#logger.debug("FCS found")
				self._fcs = buf[-4:]
				pos_end = -4

		hdr_len = unpack_H_le(buf[2:4])[0]
		self.flags(buf[8: hdr_len], self._parse_flags)
		# Now we got the correct header length
		#logger.debug("hlen=%r, buf=%r" % (hdr_len, buf[hdr_len: pos_end]))
		return hdr_len, RTAP_TYPE_80211, buf[hdr_len: pos_end]

	def _parse_flags(self, buf):
		off = 0
		flags = []

		# Assume order of flags is correctly stated by "present_flags"
		# We need to know if fcs is present: minimum TSFT and flags must get parsed
		for mask in RADIO_FIELDS_MASKS:
			#logger.debug(self.present_flags)
			# flag not set
			if mask & self.present_flags == 0:
				continue

			size_align = RADIO_FIELDS[mask]
			size = size_align[0]
			# check alignment
			mod = off % size_align[1]

			if mod != 0:
				# enlarge size by alignment
				size += (size_align[1] - mod)

			# logger.debug("got flag %02X, length/align: %r" % (mask, size_align))
			# add all fields for the stated flag
			value = buf[off: off + size]

			# FCS present?
			# if mask == FLAGS_MASK and unpack_B(value)[0] & 0x10 != 0:
			#	# logger.debug("fcs found")
			#	fcs_present = True

			#logger.debug("adding flag: %s" % str(mask))
			flags.append((mask, value))
			off += size
		return flags

	def bin(self, update_auto_fields=True):
		"""Custom bin(): handle FCS."""
		return pypacker.Packet.bin(self, update_auto_fields=update_auto_fields) + self.fcs
