import os

import numpy as np
import zmq

from common.runtime import load_root_env
from common.shared_stream_addrs import SHARED_868_ADDR, SHARED_915_ADDR
from core.multichannelswitcher import MultiChannel
from core.sig_n_medi_collect import Signal


load_root_env(__file__)


class Scheduler868:
    def __init__(self):
        self.signal_length = int(os.getenv('signal_length_868'))
        self.multi_channel = MultiChannel(
            [*map(float, os.getenv('f_step_868').split())],
            [*map(float, os.getenv('f_bases_868').split())],
            [*map(float, os.getenv('f_roofs_868').split())],
        )
        self.base_freq = float(self.multi_channel.init_f())
        self.signal = Signal()

    def get_current_freq(self):
        return float(self.multi_channel.get_cur_channel())

    def process(self, lvl):
        current_before = self.get_current_freq()
        metric = self.signal.fill_signal(lvl, self.signal_length)
        if metric == 0:
            return current_before, False

        next_freq = float(self.multi_channel.change_channel())
        self.signal.clear()
        lane_complete = next_freq == self.base_freq and current_before != self.base_freq
        return next_freq, lane_complete


class Scheduler915:
    def __init__(self):
        self.porog = float(os.getenv('POROG_915'))
        self.point_amount = 100_000
        self.split_size = 400_000
        self.show_amount = int(0.8 * self.point_amount)
        self.f_base = 0.91e9
        self.f_step = 20e6
        self.f_roof = 0.98e9
        self.f = self.f_base
        self.channel = 1
        self.flag = 0
        self.signal_arr = np.array([], dtype=np.complex64)

    def get_current_freq(self):
        return float(self.f)

    def _median(self, sig):
        samples = np.asarray(np.abs(np.array(sig, dtype=np.complex64)), dtype=np.float32)
        sorted_samples = sorted(samples)
        median = abs(float(np.median(sorted_samples[self.show_amount:])))
        self.flag = 0 if self.porog > median else 1

    def _advance(self):
        next_freq = self.f + self.f_step
        if next_freq >= self.f_roof:
            self.f = self.f_base
            self.channel = 1
            return float(self.f), True

        self.f = next_freq
        self.channel += 1
        return float(self.f), False

    def process(self, lvl):
        y = np.asarray(lvl, dtype=np.complex64).ravel()
        self.signal_arr = np.concatenate((self.signal_arr, y), axis=None)

        if self.flag == 0 and len(self.signal_arr) >= self.point_amount:
            self._median(self.signal_arr[:self.point_amount])
            self.signal_arr = np.array([], dtype=np.complex64)
            if self.flag == 0:
                return self._advance()

        if len(self.signal_arr) >= self.split_size:
            self.flag = 0
            self.signal_arr = np.array([], dtype=np.complex64)
            return self._advance()

        return float(self.f), False


class SharedRouter868915:
    def __init__(self):
        self.frontends = {
            '868': {
                'sample_rate': 20e6,
                'gain': 24,
                'if_gain': 24,
                'bb_gain': 100,
                'bandwidth': 0,
            },
            '915': {
                'sample_rate': 20e6,
                'gain': 16,
                'if_gain': 16,
                'bb_gain': 0,
                'bandwidth': 0,
            },
        }
        self.schedulers = {
            '868': Scheduler868(),
            '915': Scheduler915(),
        }
        self.context = zmq.Context.instance()
        self.sockets = {
            '868': self._make_socket(SHARED_868_ADDR),
            '915': self._make_socket(SHARED_915_ADDR),
        }
        self.active_name = '868'
        self.switch_counter = 0

    def _make_socket(self, addr):
        sock = self.context.socket(zmq.PUSH)
        sock.setsockopt(zmq.SNDHWM, 64)
        sock.setsockopt(zmq.LINGER, 0)
        sock.setsockopt(zmq.IMMEDIATE, 1)
        sock.bind(addr)
        return sock

    def close(self):
        for sock in self.sockets.values():
            try:
                sock.close(0)
            except Exception:
                pass

    def get_active_name(self):
        return self.active_name

    def get_active_freq(self):
        return self.schedulers[self.active_name].get_current_freq()

    def get_active_frontend(self):
        return dict(self.frontends[self.active_name])

    def get_start_freq(self):
        return self.get_active_freq()

    def route_vector(self, lvl):
        vector = np.asarray(lvl, dtype=np.complex64).ravel()
        active_name = self.active_name

        try:
            self.sockets[active_name].send(vector.tobytes(), zmq.NOBLOCK)
        except zmq.Again:
            pass

        next_freq, lane_complete = self.schedulers[active_name].process(vector)
        lane_switched = False

        if lane_complete:
            previous = self.active_name
            self.active_name = '915' if self.active_name == '868' else '868'
            next_freq = self.get_active_freq()
            self.switch_counter += 1
            lane_switched = True
            print(
                f'[shared-router-868-915] switch#{self.switch_counter}: '
                f'{previous} -> {self.active_name}, tune={next_freq}'
            )

        return float(next_freq), lane_switched
