import curses
import threading
import time
from typing import Any, Dict

import workflows.transport
from workflows.services.common_service import CommonService

basestring = (str, bytes)


class Monitor:  # pragma: no cover
    """A sample implementation of a status monitor showing all running services.
    To use this example class, pass in a transport object and call the run()
    method."""

    shutdown = False
    """Set to true to end the main loop and shut down the service monitor."""

    cards: Dict[Any, Any] = {}
    """Register card shown for seen services"""

    border_chars = ()
    """Characters used for frame borders."""
    border_chars_text = ("|", "|", "=", "=", "/", "\\", "\\", "/")
    """Example alternative set of frame border characters."""

    def __init__(self, transport=None):
        """Set up monitor and connect to the network transport layer"""
        if transport is None or isinstance(transport, basestring):
            self._transport = workflows.transport.lookup(transport)()
        else:
            self._transport = transport()
        assert self._transport.connect(), "Could not connect to transport layer"
        self._lock = threading.RLock()
        self._node_status = {}
        self.message_box = None
        self._transport.subscribe_broadcast(
            "transient.status", self.update_status, retroactive=True
        )

    def update_status(self, header, message):
        """Process incoming status message. Acquire lock for status dictionary before updating."""
        with self._lock:
            if self.message_box:
                self.message_box.erase()
                self.message_box.move(0, 0)
                for n, field in enumerate(header):
                    if n == 0:
                        self.message_box.addstr(field + ":", curses.color_pair(1))
                    else:
                        self.message_box.addstr(
                            ", " + field + ":", curses.color_pair(1)
                        )
                    self.message_box.addstr(header[field])
                self.message_box.addstr(": ", curses.color_pair(1))
                self.message_box.addstr(
                    str(message), curses.color_pair(2) + curses.A_BOLD
                )
                self.message_box.refresh()
            # ActiveMQ used to set timestamp in headers, but these are no longer set in RabbitMQ.
            # In that case fall back to the current local system time.
            receipt_time = int(header.get("timestamp", time.time() * 1000))
            if (
                message["host"] not in self._node_status
                or receipt_time >= self._node_status[message["host"]]["last_seen"]
            ):
                self._node_status[message["host"]] = message
                self._node_status[message["host"]]["last_seen"] = receipt_time

    def run(self):
        """A wrapper for the real _run() function to cleanly enable/disable the
        curses environment."""
        curses.wrapper(self._run)

    def _boxwin(
        self, height, width, row, column, title=None, title_x=7, color_pair=None
    ):
        with self._lock:
            box = curses.newwin(height, width, row, column)
            box.clear()
            if color_pair:
                box.attron(curses.color_pair(color_pair))
            box.border(*self.border_chars)
            if title:
                box.addstr(0, title_x, " " + title + " ")
            if color_pair:
                box.attroff(curses.color_pair(color_pair))
            box.noutrefresh()
            return curses.newwin(height - 2, width - 2, row + 1, column + 1)

    def _redraw_screen(self, stdscr):
        """Redraw screen. This could be to initialize, or to redraw after resizing."""
        with self._lock:
            stdscr.clear()
            stdscr.addstr(
                0, 0, "workflows service monitor -- quit with Ctrl+C", curses.A_BOLD
            )
            stdscr.refresh()
            self.message_box = self._boxwin(
                5, curses.COLS, 2, 0, title="last seen message", color_pair=1
            )
            self.message_box.scrollok(True)
            self.cards = []

    def _get_card(self, number):
        with self._lock:
            if number < len(self.cards):
                return self.cards[number]
            if number == len(self.cards):
                max_cards_horiz = int(curses.COLS / 35)
                self.cards.append(
                    self._boxwin(
                        6,
                        35,
                        7 + 6 * (number // max_cards_horiz),
                        35 * (number % max_cards_horiz),
                        color_pair=3,
                    )
                )
                return self.cards[number]
            raise RuntimeError("Card number too high")

    def _erase_card(self, number):
        """Destroy cards with this or higher number."""
        with self._lock:
            if number < (len(self.cards) - 1):
                self._erase_card(number + 1)
            if number > (len(self.cards) - 1):
                return
            max_cards_horiz = int(curses.COLS / 35)
            obliterate = curses.newwin(
                6,
                35,
                7 + 6 * (number // max_cards_horiz),
                35 * (number % max_cards_horiz),
            )
            obliterate.erase()
            obliterate.noutrefresh()
            del self.cards[number]

    def _run(self, stdscr):
        """Start the actual service monitor"""
        with self._lock:
            curses.use_default_colors()
            curses.curs_set(False)
            curses.init_pair(1, curses.COLOR_RED, -1)
            curses.init_pair(2, curses.COLOR_BLACK, -1)
            curses.init_pair(3, curses.COLOR_GREEN, -1)
            self._redraw_screen(stdscr)

        try:
            while not self.shutdown:
                now = int(time.time())
                with self._lock:
                    overview = self._node_status.copy()
                cardnumber = 0
                for host, status in overview.items():
                    age = now - int(status["last_seen"] / 1000)
                    with self._lock:
                        if age > 90:
                            del self._node_status[host]
                        else:
                            card = self._get_card(cardnumber)
                            card.erase()
                            card.move(0, 0)
                            card.addstr("Host: ", curses.color_pair(3))
                            card.addstr(host)
                            card.move(1, 0)
                            card.addstr("Service: ", curses.color_pair(3))
                            if "service" in status and status["service"]:
                                card.addstr(status["service"])
                            else:
                                card.addstr("---", curses.color_pair(2))
                            card.move(2, 0)
                            card.addstr("State: ", curses.color_pair(3))
                            if "status" in status:
                                status_code = status["status"]
                                state_string = CommonService.human_readable_state.get(
                                    status_code, str(status_code)
                                )
                                state_color = None
                                if status_code in (
                                    CommonService.SERVICE_STATUS_PROCESSING,
                                    CommonService.SERVICE_STATUS_TIMER,
                                ):
                                    state_color = curses.color_pair(3) + curses.A_BOLD
                                if status_code == CommonService.SERVICE_STATUS_IDLE:
                                    state_color = curses.color_pair(2) + curses.A_BOLD
                                if status_code == CommonService.SERVICE_STATUS_ERROR:
                                    state_color = curses.color_pair(1)
                                if state_color:
                                    card.addstr(state_string, state_color)
                                else:
                                    card.addstr(state_string)
                            card.move(3, 0)
                            if age >= 10:
                                card.addstr(
                                    "last seen %d seconds ago" % age,
                                    curses.color_pair(1)
                                    + (0 if age < 60 else curses.A_BOLD),
                                )
                            card.noutrefresh()
                            cardnumber = cardnumber + 1
                if cardnumber < len(self.cards):
                    with self._lock:
                        self._erase_card(cardnumber)
                with self._lock:
                    curses.doupdate()
                time.sleep(0.2)
        except KeyboardInterrupt:
            pass  # User pressed CTRL+C
        self._transport.disconnect()

import curses
import threading
import time
from typing import Any, Dict

import workflows.transport
from workflows.services.common_service import CommonService

basestring = (str, bytes)


class Monitor:  # pragma: no cover
    """A sample implementation of a status monitor showing all running services.
    To use this example class, pass in a transport object and call the run()
    method."""

    shutdown = False
    """Set to true to end the main loop and shut down the service monitor."""

    cards: Dict[Any, Any] = {}
    """Register card shown for seen services"""

    border_chars = ()
    """Characters used for frame borders."""
    border_chars_text = ("|", "|", "=", "=", "/", "\\", "\\", "/")
    """Example alternative set of frame border characters."""

    def __init__(self, transport=None):
        """Set up monitor and connect to the network transport layer"""
        if transport is None or isinstance(transport, basestring):
            self._transport = workflows.transport.lookup(transport)()
        else:
            self._transport = transport()
        assert self._transport.connect(), "Could not connect to transport layer"
        self._lock = threading.RLock()
        self._node_status = {}
        self.message_box = None
        self._transport.subscribe_broadcast(
            "transient.status", self.update_status, retroactive=True
        )

    def update_status(self, header, message):
        """Process incoming status message. Acquire lock for status dictionary before updating."""
        with self._lock:
            if self.message_box:
                self.message_box.erase()
                self.message_box.move(0, 0)
                for n, field in enumerate(header):
                    if n == 0:
                        self.message_box.addstr(field + ":", curses.color_pair(1))
                    else:
                        self.message_box.addstr(
                            ", " + field + ":", curses.color_pair(1)
                        )
                    self.message_box.addstr(header[field])
                self.message_box.addstr(": ", curses.color_pair(1))
                self.message_box.addstr(
                    str(message), curses.color_pair(2) + curses.A_BOLD
                )
                self.message_box.refresh()
            # ActiveMQ used to set timestamp in headers, but these are no longer set in RabbitMQ.
            # In that case fall back to the current local system time.
            receipt_time = int(header.get("timestamp", time.time() * 1000))
            if (
                message["host"] not in self._node_status
                or receipt_time >= self._node_status[message["host"]]["last_seen"]
            ):
                self._node_status[message["host"]] = message
                self._node_status[message["host"]]["last_seen"] = receipt_time

    def run(self):
        """A wrapper for the real _run() function to cleanly enable/disable the
        curses environment."""
        curses.wrapper(self._run)

    def _boxwin(
        self, height, width, row, column, title=None, title_x=7, color_pair=None
    ):
        with self._lock:
            box = curses.newwin(height, width, row, column)
            box.clear()
            if color_pair:
                box.attron(curses.color_pair(color_pair))
            box.border(*self.border_chars)
            if title:
                box.addstr(0, title_x, " " + title + " ")
            if color_pair:
                box.attroff(curses.color_pair(color_pair))
            box.noutrefresh()
            return curses.newwin(height - 2, width - 2, row + 1, column + 1)

    def _redraw_screen(self, stdscr):
        """Redraw screen. This could be to initialize, or to redraw after resizing."""
        with self._lock:
            stdscr.clear()
            stdscr.addstr(
                0, 0, "workflows service monitor -- quit with Ctrl+C", curses.A_BOLD
            )
            stdscr.refresh()
            self.message_box = self._boxwin(
                5, curses.COLS, 2, 0, title="last seen message", color_pair=1
            )
            self.message_box.scrollok(True)
            self.cards = []

    def _get_card(self, number):
        with self._lock:
            if number < len(self.cards):
                return self.cards[number]
            if number == len(self.cards):
                max_cards_horiz = int(curses.COLS / 35)
                self.cards.append(
                    self._boxwin(
                        6,
                        35,
                        7 + 6 * (number // max_cards_horiz),
                        35 * (number % max_cards_horiz),
                        color_pair=3,
                    )
                )
                return self.cards[number]
            raise RuntimeError("Card number too high")

    def _erase_card(self, number):
        """Destroy cards with this or higher number."""
        with self._lock:
            if number < (len(self.cards) - 1):
                self._erase_card(number + 1)
            if number > (len(self.cards) - 1):
                return
            max_cards_horiz = int(curses.COLS / 35)
            obliterate = curses.newwin(
                6,
                35,
                7 + 6 * (number // max_cards_horiz),
                35 * (number % max_cards_horiz),
            )
            obliterate.erase()
            obliterate.noutrefresh()
            del self.cards[number]

    def _run(self, stdscr):
        """Start the actual service monitor"""
        with self._lock:
            curses.use_default_colors()
            curses.curs_set(False)
            curses.init_pair(1, curses.COLOR_RED, -1)
            curses.init_pair(2, curses.COLOR_BLACK, -1)
            curses.init_pair(3, curses.COLOR_GREEN, -1)
            self._redraw_screen(stdscr)

        try:
            while not self.shutdown:
                now = int(time.time())
                with self._lock:
                    overview = self._node_status.copy()
                cardnumber = 0
                for host, status in overview.items():
                    age = now - int(status["last_seen"] / 1000)
                    with self._lock:
                        if age > 90:
                            del self._node_status[host]
                        else:
                            card = self._get_card(cardnumber)
                            card.erase()
                            card.move(0, 0)
                            card.addstr("Host: ", curses.color_pair(3))
                            card.addstr(host)
                            card.move(1, 0)
                            card.addstr("Service: ", curses.color_pair(3))
                            if "service" in status and status["service"]:
                                card.addstr(status["service"])
                            else:
                                card.addstr("---", curses.color_pair(2))
                            card.move(2, 0)
                            card.addstr("State: ", curses.color_pair(3))
                            if "status" in status:
                                status_code = status["status"]
                                state_string = CommonService.human_readable_state.get(
                                    status_code, str(status_code)
                                )
                                state_color = None
                                if status_code in (
                                    CommonService.SERVICE_STATUS_PROCESSING,
                                    CommonService.SERVICE_STATUS_TIMER,
                                ):
                                    state_color = curses.color_pair(3) + curses.A_BOLD
                                if status_code == CommonService.SERVICE_STATUS_IDLE:
                                    state_color = curses.color_pair(2) + curses.A_BOLD
                                if status_code == CommonService.SERVICE_STATUS_ERROR:
                                    state_color = curses.color_pair(1)
                                if state_color:
                                    card.addstr(state_string, state_color)
                                else:
                                    card.addstr(state_string)
                            card.move(3, 0)
                            if age >= 10:
                                card.addstr(
                                    "last seen %d seconds ago" % age,
                                    curses.color_pair(1)
                                    + (0 if age < 60 else curses.A_BOLD),
                                )
                            card.noutrefresh()
                            cardnumber = cardnumber + 1
                if cardnumber < len(self.cards):
                    with self._lock:
                        self._erase_card(cardnumber)
                with self._lock:
                    curses.doupdate()
                time.sleep(0.2)
        except KeyboardInterrupt:
            pass  # User pressed CTRL+C
        self._transport.disconnect()

# pragma: no cover A sample implementation of a status monitor showing all running services.     To use this example class, pass in a transport object and call the run()     method. Set to true to end the main loop and shut down the service monitor. Register card shown for seen services Characters used for frame borders. Example alternative set of frame border characters. Set up monitor and connect to the network transport layer Process incoming status message. Acquire lock for status dictionary before updating. # ActiveMQ used to set timestamp in headers, but these are no longer set in RabbitMQ. # In that case fall back to the current local system time. A wrapper for the real _run() function to cleanly enable/disable the         curses environment. Redraw screen. This could be to initialize, or to redraw after resizing. Destroy cards with this or higher number. Start the actual service monitor # User pressed CTRL+C