class OutputService():
    def start(self, device):
        self._device = device

        print(f'Starting Output service... Device: {self._device.device_config["DEVICE_NAME"]}')

        # Initial config load.
        self._config = self._device.config

        self._output_queue = self._device.output_queue
        self._device_notification_queue_in = self._device.device_notification_queue_in
        self._device_notification_queue_out = self._device.device_notification_queue_out

        self.ten_seconds_counter = time.time()
        self.sec_ten_seconds_counter = time.time()
        self.start_time = time.time()

        # Init FPS Limiter.
        self._fps_limiter = FPSLimiter(self._device.device_config["FPS"])

        self._skip_output = False
        self._cancel_token = False

        self._available_outputs = {
            OutputsEnum.output_dummy: OutputDummy,
            OutputsEnum.output_raspi: OutputRaspi,
            OutputsEnum.output_udp: OutputUDP
        }

        current_output_enum = OutputsEnum[self._device.device_config["OUTPUT_TYPE"]]
        print(f"Found output: {current_output_enum}")
        self._current_output = self._available_outputs[current_output_enum](self._device)

        print(f'Output component started. Device: {self._device.device_config["DEVICE_NAME"]}')

        while not self._cancel_token:
            self.output_routine()

    def output_routine(self):
        # Limit the fps to decrease lags caused by 100 percent CPU.
        self._fps_limiter.fps_limiter()

        # Check the nofitication queue.
        if not self._device_notification_queue_in.empty():
            self._current_notification_in = self._device_notification_queue_in.get()

        if hasattr(self, "_current_notification_in"):
            if self._current_notification_in is NotificationEnum.config_refresh:
                self.refresh()
            elif self._current_notification_in is NotificationEnum.process_continue:
                self._skip_output = False
            elif self._current_notification_in is NotificationEnum.process_pause:
                self._skip_output = True
            elif self._current_notification_in is NotificationEnum.process_stop:
                self.stop()

        # Reset the current in notification, to do it only one time.
        self._current_notification_in = None

        # Skip the output sequence, for example to "pause" the process.
        if self._skip_output:
            if not self._output_queue.empty():
                skip_output_queue = self._output_queue.get()
                del skip_output_queue
            return

        # Check if the queue is empty and stop if its empty.
        if not self._output_queue.empty():
            current_output_array = self._output_queue.get()
            self._current_output.show(current_output_array)

        self.end_time = time.time()

        if time.time() - self.ten_seconds_counter > 10:
            self.ten_seconds_counter = time.time()
            self.time_dif = self.end_time - self.start_time
            self.fps = 1 / self.time_dif
            print(f'Output Service | FPS: {self.fps} | Device: {self._device.device_config["DEVICE_NAME"]}')

        self.start_time = time.time()

    def stop(self):
        self._cancel_token = True
        self._current_output.clear()

    def refresh(self):
        print("Refreshing output...")

        # Refresh the config,
        self._config = self._device.config

        # Notify the master component, that I'm finished.
        self._device_notification_queue_out.put(NotificationEnum.config_refresh_finished)

        print("Output refreshed.")
class DeviceManager():
    def start(self, config_lock, notification_queue_in, notification_queue_out,
              effect_queue, audio_queue):
        self.logger = logging.getLogger(__name__)

        self._config_lock = config_lock
        self._config = ConfigService.instance(self._config_lock).config

        self._notification_queue_in = QueueWrapper(notification_queue_in)
        self._notification_queue_out = QueueWrapper(notification_queue_out)
        self._effect_queue = QueueWrapper(effect_queue)
        self._audio_queue = QueueWrapper(audio_queue)

        # Init FPS Limiter.
        self._fps_limiter = FPSLimiter(120)

        self._skip_routine = False
        self._devices = {}
        self.init_devices()
        self.start_devices()

        self.start_time = time()
        self.ten_seconds_counter = time()

        while True:
            try:
                self.routine()
            except KeyboardInterrupt:
                break

    def routine(self):
        # Check the effect queue.
        if not self._effect_queue.empty():
            current_effect_item = self._effect_queue.get_blocking()
            self.logger.debug(
                f"Device Manager received new effect: {current_effect_item.effect_enum} {current_effect_item.device_id}"
            )
            current_device = self._devices[current_effect_item.device_id]
            current_device.effect_queue.put_blocking(current_effect_item)

        if not self._notification_queue_in.empty():
            current_notification_item = self._notification_queue_in.get_blocking(
            )
            self.logger.debug(
                f"Device Manager received new notification: {current_notification_item.notification_enum} - {current_notification_item.device_id}"
            )

            if current_notification_item.notification_enum is NotificationEnum.config_refresh:

                devices_count_before_reload = len(
                    self._config["device_configs"].keys())
                self.logger.debug(
                    f"Device count before: {devices_count_before_reload}")
                self.reload_config()
                devices_count_after_reload = len(
                    self._config["device_configs"].keys())
                self.logger.debug(
                    f"Device count after: {devices_count_after_reload}")

                if (devices_count_before_reload != devices_count_after_reload):
                    self.reinit_devices()

                if (current_notification_item.device_id == "all_devices"):
                    for key, value in self._devices.items():
                        self.restart_device(key)
                else:
                    self.restart_device(current_notification_item.device_id)
                self._notification_queue_out.put_blocking(
                    NotificationItem(NotificationEnum.config_refresh_finished,
                                     current_notification_item.device_id))

            elif current_notification_item.notification_enum is NotificationEnum.process_continue:
                self._skip_routine = False
            elif current_notification_item.notification_enum is NotificationEnum.process_pause:
                self._skip_routine = True

        # Limit the fps to decrease lags caused by 100 percent CPU.
        self._fps_limiter.fps_limiter()

        if self._skip_routine:
            return

        audio_data = self.get_audio_data()
        self.refresh_audio_queues(audio_data)

        self.end_time = time()

        if time() - self.ten_seconds_counter > 10:
            self.ten_seconds_counter = time()
            self.time_dif = self.end_time - self.start_time
            self.fps = 1 / self.time_dif
            self.logger.info(f"FPS: {self.fps:.2f}")

        self.start_time = time()

    def get_audio_data(self):
        audio_data = None
        if not self._audio_queue.empty():
            audio_data = self._audio_queue.get_blocking()
        return audio_data

    def refresh_audio_queues(self, audio_data):
        if audio_data is None:
            return
        for key, value in self._devices.items():
            audio_copy = copy.deepcopy(audio_data)
            value.audio_queue.put_none_blocking(audio_data)

    def init_devices(self):
        self.logger.debug("Entering init_devices()")
        self._color_service_global = ColorServiceGlobal(self._config)

        for key in self._config["device_configs"].keys():
            device_id = key
            self.logger.debug(f"Init device with device id: {device_id}")
            self._devices[device_id] = Device(
                self._config, self._config["device_configs"][device_id],
                self._color_service_global)
        self.logger.debug("Leaving init_devices()")

    def reinit_devices(self):
        self.logger.debug("Entering reinit_devices()")
        for key, value in self._devices.items():
            self.stop_device(key)
        self._devices = {}
        self.init_devices()
        self.start_devices()
        self.logger.debug("Leaving reinit_devices()")

    def start_devices(self):
        for key, value in self._devices.items():
            self.logger.debug(f"Starting device: {key}")
            value.start_device()

    def reload_config(self):
        self.logger.debug("Entering reload_config()")
        ConfigService.instance(self._config_lock).load_config()
        self._config = ConfigService.instance(self._config_lock).config
        self.logger.debug("Leaving reload_config()")

    def restart_device(self, device_id):
        self.logger.debug(f"Restarting {device_id}")
        self._devices[device_id].refresh_config(
            self._config, self._config["device_configs"][device_id])
        self.logger.debug(f"Restarted {device_id}")

    def stop_device(self, device_id):
        self.logger.debug(f"Stopping {device_id}")
        self._devices[device_id].stop_device()
        self.logger.debug(f"Stopped {device_id}")
class DeviceManager:
    def start(self, config_lock, notification_queue_in, notification_queue_out,
              effect_queue, audio_queue):

        self._config_lock = config_lock
        self._config = ConfigService.instance(self._config_lock).config

        self._notification_queue_in = notification_queue_in
        self._notification_queue_out = notification_queue_out
        self._effect_queue = effect_queue
        print("Effect queue id DeviceManager " + str(id(self._effect_queue)))
        self._audio_queue = audio_queue

        #Init FPS Limiter
        self._fps_limiter = FPSLimiter(200)

        self._skip_routine = False
        self._devices = {}
        self.init_devices()
        self.start_devices()

        self.start_time = time.time()
        self.ten_seconds_counter = time.time()

        while True:
            self.routine()

    def routine(self):
        # Check the effect queue
        if not self._effect_queue.empty():
            current_effect_item = self._effect_queue.get()
            print("Device Manager received new effect: " +
                  str(current_effect_item.effect_enum) +
                  current_effect_item.device_id)
            current_device = self._devices[current_effect_item.device_id]
            current_device.effect_queue.put(current_effect_item)

        if not self._notification_queue_in.empty():
            current_notification_item = self._notification_queue_in.get()
            print("Device Manager received new notification: " +
                  str(current_notification_item.notification_enum) + " - " +
                  str(current_notification_item.device_id))

            if current_notification_item.notification_enum is NotificationEnum.config_refresh:

                devices_count_before_reload = len(
                    self._config["device_configs"].keys())
                print("Device count before: " +
                      str(devices_count_before_reload))
                self.reload_config()
                devices_count_after_reload = len(
                    self._config["device_configs"].keys())
                print("Device count after: " + str(devices_count_after_reload))

                if (devices_count_before_reload != devices_count_after_reload):
                    self.reinit_devices()

                if (current_notification_item.device_id == "all_devices"):
                    for key, value in self._devices.items():
                        self.restart_device(key)
                else:
                    self.restart_device(current_notification_item.device_id)
                self._notification_queue_out.put(
                    NotificationItem(NotificationEnum.config_refresh_finished,
                                     current_notification_item.device_id))

            elif current_notification_item.notification_enum is NotificationEnum.process_continue:
                self._skip_routine = False
            elif current_notification_item.notification_enum is NotificationEnum.process_pause:
                self._skip_routine = True

        # Limit the fps to decrease laggs caused by 100 percent cpu
        self._fps_limiter.fps_limiter()

        if self._skip_routine:
            return

        audio_data = self.get_audio_data()
        self.refresh_audio_queues(audio_data)

        self.end_time = time.time()

        if time.time() - self.ten_seconds_counter > 10:
            self.ten_seconds_counter = time.time()
            self.time_dif = self.end_time - self.start_time
            self.fps = 1 / self.time_dif
            print("Device Manager | FPS: " + str(self.fps))

        self.start_time = time.time()

    def get_audio_data(self):
        audio_data = None
        if not self._audio_queue.empty():
            audio_data = self._audio_queue.get()

        return audio_data

    def refresh_audio_queues(self, audio_data):
        if audio_data is None:
            return
        for key, value in self._devices.items():
            if value.audio_queue.full():
                try:
                    pre_audio_data = value.audio_queue.get(False)
                    del pre_audio_data
                except:
                    #print("Empty audio queue of devices.")
                    pass

            audio_copy = copy.deepcopy(audio_data)
            try:
                value.audio_queue.put(audio_copy, block=True, timeout=0.33)
            except:
                pass

    def init_devices(self):
        print("Enter init_devices()")

        self._color_service_global = ColorServiceGlobal(self._config)

        for key in self._config["device_configs"].keys():
            device_id = key
            print("Init device with device id:" + device_id)
            self._devices[device_id] = Device(
                self._config, self._config["device_configs"][device_id],
                self._color_service_global)
        print("Leave init_devices()")

    def reinit_devices(self):
        print("Enter reinit_devices()")
        for key, value in self._devices.items():
            self.stop_device(key)
        self._devices = {}
        self.init_devices()
        self.start_devices()
        print("Leave reinit_devices()")

    def start_devices(self):
        for key, value in self._devices.items():
            print("Start device:" + key)
            value.start_device()

    def reload_config(self):
        print("Enter reload_config()")
        ConfigService.instance(self._config_lock).load_config()
        self._config = ConfigService.instance(self._config_lock).config
        print("Leave reload_config()")

    def restart_device(self, device_id):
        print("Restart " + device_id)
        self._devices[device_id].refresh_config(
            self._config, self._config["device_configs"][device_id])
        print("Restarted " + device_id)

    def stop_device(self, device_id):
        print("Stop " + device_id)
        self._devices[device_id].stop_device()
        print("Stopped " + device_id)
class EffectService():
    def start(self, device):
        """
        Start the effect service process.
        You can change the effect by adding a new effect enum inside the enum_queue.
        """
        self.logger = logging.getLogger(__name__)

        self._device = device
        self.logger.info(
            f'Starting Effect Service component from device: {self._device.device_config["DEVICE_NAME"]}'
        )

        self.ten_seconds_counter = time()
        self.start_time = time()

        self._fps_limiter = FPSLimiter(self._device.device_config["FPS"])

        self._available_effects = {
            EffectsEnum.effect_off: EffectOff,
            EffectsEnum.effect_single: EffectSingle,
            EffectsEnum.effect_gradient: EffectGradient,
            EffectsEnum.effect_fade: EffectFade,
            EffectsEnum.effect_sync_fade: EffectSyncFade,
            EffectsEnum.effect_slide: EffectSlide,
            EffectsEnum.effect_bubble: EffectBubble,
            EffectsEnum.effect_twinkle: EffectTwinkle,
            EffectsEnum.effect_pendulum: EffectPendulum,
            EffectsEnum.effect_rods: EffectRods,
            EffectsEnum.effect_advanced_scroll: EffectAdvancedScroll,
            EffectsEnum.effect_scroll: EffectScroll,
            EffectsEnum.effect_energy: EffectEnergy,
            EffectsEnum.effect_wavelength: EffectWavelength,
            EffectsEnum.effect_bars: EffectBars,
            EffectsEnum.effect_power: EffectPower,
            EffectsEnum.effect_beat: EffectBeat,
            EffectsEnum.effect_wave: EffectWave,
            EffectsEnum.effect_beat_slide: EffectBeatSlide,
            EffectsEnum.effect_spectrum_analyzer: EffectSpectrumAnalyzer,
            EffectsEnum.effect_vu_meter: EffectVuMeter,
            EffectsEnum.effect_wiggle: EffectWiggle,
            EffectsEnum.effect_direction_changer: EffectDirectionChanger,
            EffectsEnum.effect_beat_twinkle: EffectBeatTwinkle,
            EffectsEnum.effect_segment_color: EffectSegmentColor
        }

        self._initialized_effects = {}
        self._current_effect = {}

        try:
            # Get the last effect and set it.
            last_effect_string = self._device.device_config["effects"][
                "last_effect"]
            self._current_effect = EffectsEnum[last_effect_string]
        except Exception:
            self.logger.exception(
                "Could not parse last effect. Set effect to off.")
            self._current_effect = EffectsEnum.effect_off

        # A token to cancel the while loop.
        self._cancel_token = False
        self._skip_effect = False
        self.logger.info(
            f'Effects component started. Device: {self._device.device_config["DEVICE_NAME"]}'
        )

        while not self._cancel_token:
            try:
                self.effect_routine()
            except KeyboardInterrupt:
                break

        self.logger.info(
            f'Effects component stopped. Device: {self._device.device_config["DEVICE_NAME"]}'
        )

    def effect_routine(self):
        # Limit the fps to decrease lags caused by 100 percent CPU.
        self._fps_limiter.fps_limiter()

        # Check the notification queue.
        if not self._device.device_notification_queue_in.empty():
            self._current_notification_in = self._device.device_notification_queue_in.get(
            )
            self.logger.debug(
                f'Effects Service has a new notification in. Notification: {self._current_notification_in} | Device: {self._device.device_config["DEVICE_NAME"]}'
            )

        if hasattr(self, "_current_notification_in"):
            if self._current_notification_in is NotificationEnum.config_refresh:
                self.refresh()
            elif self._current_notification_in is NotificationEnum.process_continue:
                self._skip_effect = False
            elif self._current_notification_in is NotificationEnum.process_pause:
                self._skip_effect = True
            elif self._current_notification_in is NotificationEnum.process_stop:
                self.stop()

        # Reset the current in notification, to do it only one time.
        self._current_notification_in = None

        # Skip the effect sequence, for example, to "pause" the process.
        if self._skip_effect:
            return

        # Check if the effect changed.
        if not self._device.effect_queue.empty():
            new_effect_item = self._device.effect_queue.get()
            self._current_effect = new_effect_item.effect_enum
            self.logger.debug(
                f"New effect found: {new_effect_item.effect_enum}")

        # Something is wrong here, no effect set. So skip until we get new information.
        if self._current_effect is None:
            self.logger.error("Effect Service | Could not find effect.")
            return

        if (not (self._current_effect in self._initialized_effects.keys())):
            if self._current_effect in self._available_effects.keys():
                self._initialized_effects[
                    self._current_effect] = self._available_effects[
                        self._current_effect](self._device)
            else:
                self.logger.error(
                    f"Could not find effect: {self._current_effect}")

        self.end_time = time()
        if time() - self.ten_seconds_counter > 10:
            self.ten_seconds_counter = time()
            self.time_dif = self.end_time - self.start_time
            self.fps = 1 / self.time_dif
            self.logger.info(
                f'FPS: {self.fps:.2f} | Device: {self._device.device_config["DEVICE_NAME"]}'
            )

        self.start_time = time()

        self._initialized_effects[self._current_effect].run()

    def stop(self):
        self.logger.info("Stopping effect component...")
        self.cancel_token = True

    def refresh(self):
        self.logger.debug("Refreshing effects...")
        self._initialized_effects = {}

        self._fps_limiter = FPSLimiter(self._device.device_config["FPS"])

        # Notify the master component, that I'm finished.
        self._device.device_notification_queue_out.put(
            NotificationEnum.config_refresh_finished)
        self.logger.debug("Effects refreshed.")
class OutputService():
    def start(self, device):
        self.logger = logging.getLogger(__name__)

        self._device = device
        self._led_strip = self._device.device_config["led_strip"]

        self.logger.info(
            f'Starting Output service... Device: {self._device.device_config["device_name"]}'
        )

        # Initial config load.
        self._config = self._device.config

        self._output_queue = self._device.output_queue
        self._device_notification_queue_in = self._device.device_notification_queue_in
        self._device_notification_queue_out = self._device.device_notification_queue_out

        self.ten_seconds_counter = time()
        self.sec_ten_seconds_counter = time()
        self.start_time = time()

        # Init FPS Limiter.
        self._fps_limiter = FPSLimiter(self._device.device_config["fps"])

        self._skip_output = False
        self._cancel_token = False

        self._available_outputs = {
            OutputsEnum.output_dummy: OutputDummy,
            OutputsEnum.output_raspi: OutputRaspi,
            OutputsEnum.output_udp: OutputUDP
        }

        current_output_enum = OutputsEnum[
            self._device.device_config["output_type"]]
        self.logger.debug(f"Found output: {current_output_enum}")
        self._current_output = self._available_outputs[current_output_enum](
            self._device)

        self.logger.debug(
            f'Output component started. Device: {self._device.device_config["device_name"]}'
        )

        while not self._cancel_token:
            try:
                self.output_routine()
            except KeyboardInterrupt:
                break

    def output_routine(self):
        # Limit the fps to decrease lags caused by 100 percent CPU.
        self._fps_limiter.fps_limiter()

        # Check the notification queue.
        if not self._device_notification_queue_in.empty():
            self._current_notification_in = self._device_notification_queue_in.get_blocking(
            )

        if hasattr(self, "_current_notification_in"):
            if self._current_notification_in is NotificationEnum.config_refresh:
                self.refresh()
            elif self._current_notification_in is NotificationEnum.process_continue:
                self._skip_output = False
            elif self._current_notification_in is NotificationEnum.process_pause:
                self._skip_output = True
            elif self._current_notification_in is NotificationEnum.process_stop:
                self.stop()

        # Reset the current in notification, to do it only one time.
        self._current_notification_in = None

        # Skip the output sequence, for example to "pause" the process.
        if self._skip_output:
            if not self._output_queue.empty():
                skip_output_queue = self._output_queue.get_blocking()
                del skip_output_queue
            return

        # Check if the queue is empty and stop if its empty.
        if not self._output_queue.empty():
            current_output_array = self._output_queue.get_blocking()
            # Add another Array of LEDS for White Channel
            if "SK6812" in self._led_strip and len(current_output_array) == 3:
                current_output_array = np.vstack(
                    (current_output_array,
                     np.zeros(self._device.device_config["led_count"])))

            self._current_output.show(current_output_array)

        self.end_time = time()

        if time() - self.ten_seconds_counter > 10:
            self.ten_seconds_counter = time()
            self.time_dif = self.end_time - self.start_time
            self.fps = 1 / self.time_dif
            self.logger.info(
                f'FPS: {self.fps:.2f} | Device: {self._device.device_config["device_name"]}'
            )

        self.start_time = time()

    def stop(self):
        self._cancel_token = True
        self._current_output.clear()

    def refresh(self):
        self.logger.debug("Refreshing output...")

        # Refresh the config,
        self._config = self._device.config

        # Notify the master component, that I'm finished.
        self._device_notification_queue_out.put_blocking(
            NotificationEnum.config_refresh_finished)

        self.logger.debug("Output refreshed.")