# Copyright 3D Control Systems, Inc. All Rights Reserved 2017-2019.
# Built in San Francisco.

# This software is distributed under a commercial license for personal,
# educational, corporate or any other use.
# The software as a whole or any parts of it is prohibited for distribution or
# use without obtaining a license from 3D Control Systems, Inc.

# All software licenses are subject to the 3DPrinterOS terms of use
# (available at https://www.3dprinteros.com/terms-and-conditions/),
# and privacy policy (available at https://www.3dprinteros.com/privacy-policy/)

import asyncio
import logging
import pprint
import time

from pydispatch import dispatcher

import event_names


class EventHandlerBridge:

    SIGNALS = event_names.all()
    FILTER = ()
    RAISE_WHEN_NO_HANDLER = False
    DEFAULT_HANDLER_NAME = "handle_event"
    SHUTDOWN_WAITING_STEP = 0.01

    def __init__(self, app, loop=None):
        self.logger = logging.getLogger(self.__class__.__name__)
        self.logger.setLevel(logging.INFO)
        self.stop_flag = False
        self.app = app
        if loop:
            self.loop = loop
        else:
            self.loop = asyncio.get_event_loop()
        self.connections = []
        dispatcher.connect(self.handle_event, event_names.PRINTER_CONNECTED, sender=dispatcher.Any)
        self.connect_to_signals()

    def connect_to_signals(self):
        for signal in self.SIGNALS:
            handler = getattr(self, "handle_" + signal, None)
            if handler and not signal in self.FILTER:
                dispatcher.connect(handler, signal, sender=dispatcher.Any)
            elif self.RAISE_WHEN_NO_HANDLER:
                raise RuntimeError("No handler for signal %s" % signal)
            elif self.DEFAULT_HANDLER_NAME:
                default_handler = getattr(self, self.DEFAULT_HANDLER_NAME, None)
                if default_handler:
                    dispatcher.connect(default_handler, signal, sender=dispatcher.Any)
                else:
                    raise RuntimeError("No method for default handler named %s" % self.DEFAULT_HANDLER_NAME)
            else:
                raise RuntimeError("No handler(even default) for signal %s" % signal)

    def handle_event(self, sender, *args, **kwargs):
        self.logger.info("Event sender / args / kwargs: %s / %s / %s" % (sender, args, kwargs))
        kwargs['sender'] = getattr(sender, "id_string", sender.__class__.__name__)
        try:
            self.send_data_to_all_connections(kwargs)
        except Exception as e:
            self.logger.exception("Exception in event handler: " + str(e))

    def send_data_to_all_connections(self, data):
        for connection in self.connections:
            if not self.stop_flag:
                self.send_data(connection, data)

    async def send_data_async(self, connection, data):
        if isinstance(data, str):
            self.logger.debug("Sending string data: " + data)
            await connection.send_str(data)
        elif isinstance(data, bytes):
            self.logger.debug(f"Sending {len(data)} bytes")
            await connection.send_bytes(data)
        else:
            self.logger.debug("Sending json: " + pprint.pformat(data))
            await connection.send_json(data)

    def wrap_data(self, data):
        return data

    def send_data(self, connection, data):
        self.logger.info('Event: %s', data)
        asyncio.run_coroutine_threadsafe(self.send_data_async(connection, self.wrap_data(data)), self.loop)

    def close(self):
        self.stop_flag = True
        if self.loop:
            self.loop.call_soon_threadsafe(self.loop.stop)
            while self.loop.is_running():
                time.sleep(self.SHUTDOWN_WAITING_STEP)
            self.loop.close()
