""" ReceiverController implementation """
import logging
import sqlite3
import time
from multiprocessing import Queue
from typing import Optional, Dict, List

from marshmallow import EXCLUDE

from .decorators import deprecated
from ..entities.data.kr_packet import KrPacket
from ..entities.receiver import Receiver
from ..utils.ProcessService import ProcessService
from ..utils.json_func import json_loads


logger = logging.getLogger()


class ReceiverCommands:
    """ Receiver Controller commands """
    REFRESH_RECEIVERS = 0
    HANDLE_DATA = 200
    REGISTER_RECEIVER = 210  # Register new receiver
    UNREGISTER_RECEIVER = 220  # Unregister receiver
    LOAD_RECEIVERS = 300  # "Load" from DB/File registered receiver
    GET_RECEIVERS = 350  # Get registered receivers list
    GET_DOA = 355
    UPDATE_RECEIVER = 360
    UPDATE_RECEIVER_ACTIVE = 370
    SET_EPSILON = 410
    SET_SAMPLING = 420
    SET_CONFIDENCE = 430


# noinspection SqlDialectInspection
class ReceiverController(ProcessService):
    """ The receiver controller.
        Controls all working receivers and potentially new ones """
    def __init__(self, database_name: Optional[str],
                 receivers_file: Optional[str] = None) -> None:
        self.database_name = database_name
        self.receivers_file = receivers_file
        self._registered_receivers = {}
        self._unregistered_receivers = {}
        self.inner_queue = Queue()
        self.doa_queue = Queue()
        super().__init__()

    @property
    def receivers(self) -> List[Receiver]:
        """ get receivers """
        self.cmd_queue.put_nowait((ReceiverCommands.GET_RECEIVERS, ))
        return self.inner_queue.get()

    def get_doa(self) -> List[Dict]:
        """ Get receivers' bearings """
        self.cmd_queue.put_nowait((ReceiverCommands.GET_DOA, ))
        return self.doa_queue.get()

    def set_epsilon(self, value: int) -> None:
        """ Set epsilon for all receivers """
        self.cmd_queue.put_nowait((ReceiverCommands.SET_EPSILON, value))

    def set_sampling(self, value: int) -> None:
        """ Set min sampling size for all receivers """
        self.cmd_queue.put_nowait((ReceiverCommands.SET_SAMPLING, value))

    def set_confidence(self, value: int) -> None:
        """ Set min confidence for all receivers """
        self.cmd_queue.put_nowait((ReceiverCommands.SET_CONFIDENCE, value))

    def update_receiver(self, data: Dict) -> None:
        """ Update receiver parameters """
        self.cmd_queue.put_nowait((ReceiverCommands.UPDATE_RECEIVER, data))

    def update_receiver_active(self, data: Dict) -> None:
        """ Update receiver active parameter """
        self.cmd_queue.put_nowait((ReceiverCommands.UPDATE_RECEIVER_ACTIVE,
                                   data))

    @deprecated
    def load_receivers(self) -> None:
        """ Load stored receivers """
        self.cmd_queue.put_nowait((ReceiverCommands.LOAD_RECEIVERS, ))

    def register_receiver(self, station_id: str) -> None:
        """ Register receiver """
        self.cmd_queue.put_nowait((ReceiverCommands.REGISTER_RECEIVER,
                                   station_id))

    def unregister_receiver(self, station_id: str) -> None:
        """ Unregister receiver """
        self.cmd_queue.put_nowait((ReceiverCommands.UNREGISTER_RECEIVER,
                                   station_id))

    def get_registered_receiver(self, station_id) -> Optional[Receiver]:
        """ Get registered receiver by station_id """
        return self._registered_receivers.get(station_id, None)

    def get_or_create_unregistered_receiver(self,
                                            station_id) -> Optional[Receiver]:
        """ Get or create unregistered receiver """
        receiver = self._unregistered_receivers.get(station_id, None)
        if receiver is None and station_id is not None:
            receiver = Receiver(station_id)
            self._unregistered_receivers[station_id] = receiver
        return receiver

    def get_receiver(self, station_id) -> Optional[Receiver]:
        """ Get a receiver by station_id """
        return (self.get_registered_receiver(station_id) or
                self.get_or_create_unregistered_receiver(station_id))

    def add_registered_receiver(self, receiver: Receiver) -> None:
        """ Add registered receiver """
        if receiver.station_id not in self._registered_receivers:
            self._registered_receivers[receiver.station_id] = receiver
            return
        logger.warning(f"Receiver '{receiver.station_id}' is already "
                       f"in registered list")

    def pop_registered_receiver(self, station_id: str) -> Optional[Receiver]:
        """ Pop receiver from the unregistered list.
            We execute this when we're adding a receiver into registered """
        if station_id in self._registered_receivers:
            return self._registered_receivers.pop(station_id, None)

    def del_registered_receiver(self, receiver: Receiver) -> None:
        """ Delete receiver from the unregistered list.
            We execute this when we're adding a receiver into registered """
        if receiver.station_id in self._registered_receivers:
            del self._registered_receivers[receiver.station_id]

    def add_unregistered_receiver(self, receiver: Receiver) -> None:
        """ Add Unregistered receiver.
            Whether receiver was unregistered or metadata received
            from unknown receiver, the receiver goes to this list """
        if receiver.station_id not in self._unregistered_receivers:
            self._unregistered_receivers[receiver.station_id] = receiver
            return
        logger.warning(f"Receiver '{receiver.station_id}' is already "
                       f"in unregistered list")

    def pop_unregistered_receiver(self, station_id: str) -> Optional[Receiver]:
        """ Pop receiver from the unregistered list.
            We execute this when we're adding a receiver into registered """
        if station_id in self._unregistered_receivers:
            return self._unregistered_receivers.pop(station_id, None)

    def del_unregistered_receiver(self, receiver: Receiver) -> None:
        """ Delete receiver from the unregistered list.
            We execute this when we're adding a receiver into registered """
        if receiver.station_id in self._unregistered_receivers:
            del self._unregistered_receivers[receiver.station_id]

    def handle_data(self, data: bytes) -> None:
        """ Handle incoming data """
        self.cmd_queue.put_nowait((ReceiverCommands.HANDLE_DATA, data))

    def load_receivers_from_file(self):
        """ Reads receivers from the text file """
        if self.receivers_file:
            with open(self.receivers_file, "r") as f:
                for receiver_name in f.readlines():
                    self.register_receiver(receiver_name.strip())

    def load_receivers_from_db(self):
        """ Reads receivers from the database into the program. """
        conn = None
        try:
            conn = sqlite3.connect(self.database_name)
            c = conn.cursor()
            c.execute("SELECT station_id FROM receivers")
            rx_list = c.fetchall()
            for data in rx_list:
                (station_id,) = [v.strip() for v in data]
                self.register_receiver(station_id)
        except sqlite3.OperationalError:
            logger.error("Failed to fetch receivers from DB")
        finally:
            conn.close() if conn is not None else None

    def handle_command(self, command: int, *params) -> None:
        """ Handle receiver commands """
        if command == ReceiverCommands.HANDLE_DATA:
            (data_bytes, ) = params
            data = json_loads(data_bytes, {})
            kr_packet = KrPacket.load(data, unknown=EXCLUDE)
            kr_packet.t_stamp = int(time.time() * 1000)
            receiver = self.get_registered_receiver(kr_packet.id)
            if receiver and receiver.active:
                # TODO(s1z): Add bearings to the queue
                receiver.update_config(kr_packet)
                return receiver.add_packet(kr_packet)

            # create receiver if does not exist
            if receiver is None:
                receiver = self.get_or_create_unregistered_receiver(
                    kr_packet.id
                )
                receiver.update_config(kr_packet)

        elif command == ReceiverCommands.REGISTER_RECEIVER:
            (station_id, ) = params
            if station_id not in self._registered_receivers:
                receiver = self.pop_unregistered_receiver(station_id)
                if receiver is None:
                    receiver = Receiver(station_id)
                self.add_registered_receiver(receiver)

        elif command == ReceiverCommands.UNREGISTER_RECEIVER:
            (station_id, ) = params
            if station_id not in self._unregistered_receivers:
                receiver = self.pop_registered_receiver(station_id)
                if receiver is None:
                    receiver = Receiver(station_id)
                self.add_unregistered_receiver(receiver)

        elif command == ReceiverCommands.LOAD_RECEIVERS:
            self.load_receivers_from_file()
            self.load_receivers_from_db()

        elif command == ReceiverCommands.GET_RECEIVERS:
            data = [receiver for _, receiver in
                    self._registered_receivers.items()]
            self.inner_queue.put(data)

        elif command == ReceiverCommands.SET_CONFIDENCE:
            (confidence, ) = params
            for _, receiver in self._registered_receivers.items():
                receiver.confidence = confidence

        elif command == ReceiverCommands.SET_SAMPLING:
            (samplings, ) = params
            for _, receiver in self._registered_receivers.items():
                receiver.samplings = samplings
            for _, receiver in self._unregistered_receivers.items():
                receiver.samplings = samplings

        elif command == ReceiverCommands.UPDATE_RECEIVER:
            (data, ) = params
            update_receiver = Receiver.load(data)
            station_id = update_receiver.station_id
            receiver = self.get_receiver(station_id)
            if receiver is not None:
                receiver.mobile = update_receiver.mobile
                receiver.inverted = update_receiver.inverted
                receiver.single = update_receiver.single
                receiver.active = update_receiver.active

        elif command == ReceiverCommands.UPDATE_RECEIVER_ACTIVE:
            (data, ) = params
            update_receiver = Receiver.load(data)
            station_id = update_receiver.station_id
            receiver = self.get_receiver(station_id)
            if receiver is not None:
                receiver.active = update_receiver.active

            if not receiver.active:
                receiver.clear_bearings()

        elif command == ReceiverCommands.GET_DOA:
            doa_list = {}
            for _, receiver in self._registered_receivers.items():
                receiver_doa = []
                for doa in receiver.bearings:
                    doa.latitude = receiver.latitude
                    doa.longitude = receiver.longitude
                    doa.heading = receiver.heading

                    receiver_doa.append(doa)
                doa_list[receiver.station_id] = receiver_doa
            self.doa_queue.put_nowait(doa_list)

        elif command == ReceiverCommands.REFRESH_RECEIVERS:
            for _, receiver in self._registered_receivers.items():
                receiver.refresh()
