import logging

import numpy as np

import flora_tools.sim.cad_search as sim_cad_search
import flora_tools.sim.sim_event_manager as sim_event_manager
import flora_tools.sim.sim_node as sim_node
from flora_tools import lwb_slot
from flora_tools.sim.sim_message import SimMessage, SimMessageType

CAD_SYNC_MAX_BACKOFF_EXPONENT = 5  # 143.165576533 min


class CADSync:
    def __init__(self, node: 'sim_node.SimNode'):
        self.logger = logging.getLogger(self.__class__.__qualname__)

        self.node = node
        self.cad_scanner = None
        self.callback = None

        self.start: float = None
        self.backoff_counter: int = -1

    @property
    def backoff_period(self):
        return lwb_slot.LWB_SYNC_PERIOD

    def run(self, callback):
        self.start = self.node.local_timestamp
        self.backoff_counter = -1
        self.callback = callback

        self.scan()

    def scan(self, modulation: int = None):
        self.cad_scanner = sim_cad_search.CADSearch(self.node, self.scanner_callback, start_modulation=modulation)

    def scanner_callback(self, message: SimMessage):
        if message is not None:
            self.start = self.node.local_timestamp
            self.backoff_counter = -1

            self.logger.info(
                "Marker:{:10f}\tNode:{:3d}\tTx_End:{:10f}\tMod:{:2d}\tType:{:16s}".format(self.node.local_timestamp,
                                                                                          self.node.id,
                                                                                          message.tx_end,
                                                                                          message.modulation,
                                                                                          message.type))

            if message.type is SimMessageType.SYNC:
                self.node.lwb.schedule_manager.register_sync(message)
                self.sync_timestamp(message)
                self.callback()
            elif message.type is SimMessageType.SLOT_SCHEDULE:
                self.node.lwb.schedule_manager.register_slot_schedule(message)
                self.sync_timestamp(message)
                self.callback()
            elif message.type is SimMessageType.ROUND_SCHEDULE:
                self.node.lwb.schedule_manager.register_round_schedule(message)
                self.sync_timestamp(message)
                self.callback()
            else:
                self.scan(modulation=message.modulation)

        else:
            elapsed = self.node.local_timestamp - self.start

            if elapsed > self.backoff_period:
                if self.backoff_counter < CAD_SYNC_MAX_BACKOFF_EXPONENT - 1:
                    self.backoff_counter += 1
                self.node.em.register_event(
                    self.node.local_timestamp + self.backoff_period * np.exp2(self.backoff_counter), self.node,
                    sim_event_manager.SimEventType.GENERIC, self.run)
            else:
                self.scan()

    def sync_timestamp(self, message: SimMessage):
        self.node.local_timestamp = message.timestamp + message.tx_end - message.tx_start
