""" This is a receiver (KrRPI) representation """
import logging
import time
from collections import OrderedDict
from dataclasses import dataclass, field
from typing import List, Dict

from marshmallow import post_dump, EXCLUDE

from .basic_dataclass import BasicDataclass
from .data.kr_packet import KrPacket

from ..utils.rb_tree import RBTree

logger = logging.getLogger(__name__)


load_only = dict(load_only=True)


@dataclass
class Bearing:
    """ Bearing Param for calculation """
    degrees: float
    num: int
    power: float
    confidence: float
    #
    latitude: float = field(default=0.0)
    longitude: float = field(default=0.0)
    heading: float = field(default=0.0)


# noinspection PyPep8Naming
@dataclass
class Receiver(BasicDataclass):
    """ Receiver object """
    station_id: str

    # TODO(s1z): obsolete fields. remove this as soon as possible
    latitude: float = field(default=0.0)
    longitude: float = field(default=0.0)
    heading: float = field(default=0.0)
    doa: float = field(default=0.0)
    frequency: float = field(default=0.0)
    power: float = field(default=0.0)
    confidence: float = field(default=0.0)
    doa_time: int = field(default=0)
    mobile: bool = field(default=False)
    single: bool = field(default=False)
    active: bool = field(default=True)
    auto: bool = field(default=True)
    inverted: bool = field(default=True)
    flipped: bool = field(default=False)

    packets: List[KrPacket] = field(default_factory=list, metadata=load_only)
    bearings: List[Bearing] = field(default_factory=list, metadata=load_only)

    samplings: int = field(init=False, metadata=load_only)
    tree: RBTree = field(init=False, metadata=load_only)

    class Meta:
        """ Meta """
        unknown = EXCLUDE

    @post_dump
    def post_dump(self: "Receiver", data: Dict, **_kwargs) -> Dict:
        """ Remove unwanted fields """
        data.pop("packets", None)
        data.pop("bearings", None)
        data.pop("samplings", None)
        data.pop("tree", None)
        return data

    def __post_init__(self):
        self.tree = RBTree()
        self.samplings = 10

    def __eq__(self: "Receiver", other: "Receiver") -> bool:
        """ compare two receivers """
        return self.station_id == other.station_id

    @property
    def isActive(self: "Receiver") -> bool:
        """ Always active """
        return self.active

    @property
    def isSingle(self: "Receiver") -> bool:
        """ link to the single parameter. Obsolete, remove me when fixed """
        return self.single

    @isSingle.setter
    def isSingle(self: "Receiver", value: bool):
        """ single setter """
        self.single = value

    @property
    def isMobile(self: "Receiver") -> bool:
        """ link to the mobile parameter. Obsolete, remove me when fixed """
        return self.mobile

    @isMobile.setter
    def isMobile(self: "Receiver", value: bool) -> None:
        """ mobile setter """
        self.mobile = value

    def receiver_dict(self: "Receiver") -> Dict[str, any]:
        """ obsolete serializator """
        return self.get_schema().dump(self)

    def clear_bearings(self: "Receiver") -> None:
        """ Remove all bearings """
        self.bearings = []
        nodes = self.tree.get_all()
        for node in nodes:
            node.items = []
            self.tree.delete_node(node.val)

    def update_config(self: "Receiver", packet: KrPacket) -> None:
        """ Update latitude, longitude, frequency, heading """
        # update position if needed
        self.latitude = packet.latitude
        self.longitude = packet.longitude
        self.frequency = packet.freq
        self.heading = packet.gps_bearing

    @staticmethod
    def get_median_bearing(bearing_one: Bearing,
                           bearing_two: Bearing) -> Bearing:
        """ Get median bearing """
        total_num = bearing_one.num + bearing_two.num
        total_power = bearing_one.power + bearing_two.power
        median_degrees = ((bearing_one.degrees * bearing_one.num) +
                          (bearing_two.degrees * bearing_two.num)) / total_num
        total_confidence = bearing_one.confidence + bearing_two.confidence
        return Bearing(median_degrees, total_num,
                       total_power, total_confidence)

    def create_absolute_bearings(self, bearings: List[Bearing],
                                 bearings_len: int) -> List[Bearing]:
        """ Create bearings """
        i = 0
        absolute_bearings = []
        while i < bearings_len:
            curr_bearing = bearings[i]
            if i == 0:
                absolute_bearings.append(curr_bearing)
                i += 1
            prev_bearing = absolute_bearings.pop()
            if abs(prev_bearing.degrees - curr_bearing.degrees) <= 5:
                absolute_bearings.append(self.get_median_bearing(prev_bearing,
                                                                 curr_bearing))
            else:
                absolute_bearings.append(prev_bearing)
                absolute_bearings.append(curr_bearing)
            i += 1

        for a_bearing in absolute_bearings:
            a_bearing.power = a_bearing.num
            a_bearing.confidence /= a_bearing.num

            # This code is just copied from the old DF-AGG
            if self.inverted:
                a_bearing.degrees = self.heading + (360 - a_bearing.degrees)
            elif self.flipped:
                a_bearing.degrees = self.heading + (180 - a_bearing.degrees)
            else:
                a_bearing.degrees += self.heading
            if a_bearing.degrees < 0:
                a_bearing.degrees += 360
            elif a_bearing.degrees > 359:
                a_bearing.degrees -= 360

        return absolute_bearings

    def refresh(self) -> None:
        """ Refresh DOA of the receiver """
        now = int(time.time() * 1000)

        bearings = []
        bearings_len = 0

        # remove old packets
        nodes = self.tree.get_all()
        for node in nodes:
            bearing = Bearing(node.val, 0, 0, 0)
            slice_id = None
            for idx, item in enumerate(node.items):
                if now - item.t_stamp > 5000:  # 5 seconds old
                    slice_id = idx
                else:
                    bearing.num += 1
                    bearing.power += item.power
                    bearing.confidence += item.conf

            # add bearings
            if bearing.num > 0:
                bearings_len += 1
                bearings.append(bearing)

            # slice the list
            if slice_id is not None:
                node.items = node.items[slice_id:]

            # remove node if we have 0 records
            if len(node.items) == 0:
                self.tree.delete_node(node.val)

        self.bearings = self.create_absolute_bearings(bearings, bearings_len)
        logger.info(f"{self.station_id}, "
                    f"{[str(x.degrees) + 'º'for x in self.bearings]}")

    def add_packet(self: "Receiver", packet: KrPacket) -> None:
        """ Add KrRpi packet to the receiver """
        self.tree.insert_node(packet.radio_bearing, packet)

        # insert packet
        # fake_packet = KrPacket(
        #     packet.t_stamp,
        #     packet.latitude,
        #     packet.longitude,
        #     packet.gps_bearing,
        #     13,
        #     packet.conf,
        #     packet.power,
        #     packet.freq,
        #     packet.ant_type,
        #     packet.latency,
        #     packet.doa_array,
        #     packet.id)
        # self.tree.insert_node(fake_packet.radio_bearing, fake_packet)

    @staticmethod
    def lob_length() -> int:
        """ lob_length """
        return 40200
