import logging
import re
from datetime import datetime
from threading import Thread
from time import sleep

import pytz
import telnetlib3

from core.config import SERVER_OWNER_CALLSIGN
from data.spot import Spot
from providers.spot.spot_provider import SpotProvider


class RBN(SpotProvider):
    """Spot provider for the Reverse Beacon Network. Connects to a single port, if you want both CW/RTTY (port 7000) and FT8
    (port 7001) you need to instantiate two copies of this. The port is provided as an argument to the constructor."""

    _LINE_PATTERN = re.compile(
        r"^DX de ([a-z0-9/]+)-.*:\s+([0-9.]+)\s+([a-z0-9/]+)\s+(.*)\s+(\d{4}Z)",
        re.IGNORECASE)

    def __init__(self, provider_config):
        """Constructor requires port number."""

        name = provider_config["name"] if "name" in provider_config else "RBN"
        super().__init__(name, provider_config)
        self._port = provider_config["port"]
        self._telnet = None
        self._thread = Thread(target=self._handle, name=f"RBNSpotProvider-{self.name}")
        self._thread.daemon = True
        self._running = True

    def start(self):
        self._thread.start()

    def stop(self):
        self._running = False
        self._telnet.close()
        self._thread.join()

    def _handle(self):
        while self._running:
            connected = False
            while not connected and self._running:
                try:
                    self.status = "Connecting"
                    logging.info("RBN port " + str(self._port) + " connecting...")
                    self._telnet = telnetlib3.Telnet("telnet.reversebeacon.net", self._port)
                    self._telnet.read_until("Please enter your call: ".encode("latin-1"))
                    self._telnet.write((SERVER_OWNER_CALLSIGN + "\n").encode("latin-1"))
                    connected = True
                    logging.info("RBN port " + str(self._port) + " connected.")
                except Exception:
                    self.status = "Error"
                    logging.exception("Exception while connecting to RBN (port " + str(self._port) + ").")
                    sleep(5)

            self.status = "Waiting for Data"
            while connected and self._running:
                try:
                    # Check new telnet info against regular expression
                    telnet_output = self._telnet.read_until("\n".encode("latin-1"))
                    match = self._LINE_PATTERN.match(telnet_output.decode("latin-1"))
                    if match:
                        spot_time = datetime.strptime(match.group(5), "%H%MZ")
                        spot_datetime = datetime.combine(datetime.now(pytz.UTC).date(), spot_time.time(),
                                                         tzinfo=pytz.UTC)
                        spot = Spot(source=self.name,
                                    dx_call=match.group(3),
                                    de_call=match.group(1),
                                    freq=float(match.group(2)) * 1000,
                                    comment=match.group(4).strip(),
                                    time=spot_datetime.timestamp())

                        # Add to our list
                        self._submit(spot)

                        self.status = "OK"
                        self.last_update_time = datetime.now(pytz.UTC)
                        logging.debug("Data received from RBN on port " + str(self._port) + ".")

                except Exception:
                    connected = False
                    if self._running:
                        self.status = "Error"
                        logging.exception("Exception in RBN provider (port " + str(self._port) + ")")
                        sleep(5)
                    else:
                        logging.info("RBN provider (port " + str(self._port) + ") shutting down...")
                        self.status = "Shutting down"

            self.status = "Disconnected"
