import blinkenbase.blinkenfoo as blinkenfoo
from blinkenbase.Color import Color

class Blinkenroom:

    def __init__(self, p_device_list, p_fps = 42):
        self.fps = p_fps
        self.device_list = p_device_list
        self._calc_total_length()
        self._generate_index_map()
        self.previous_device = None
        self._remove_unreachable_devices()

    #####################
    ###private methods###
    #####################

    def _remove_unreachable_devices(self):
        """
        Removes devices that are not reachable from the devices list
        """
        for device in self.device_list:
            if not device.is_reachable():
                print(device.name + " is not available")
                del self.device_list[self.device_list.index(device)]

    def _calc_total_length(self):
        self.total_length = 0
        for device in self.device_list:
            self.total_length += device.length
        return self.total_length

    def _generate_index_map(self):
        self.global_map = [0 for _ in range(self.total_length)]

        current_index = 0
        for device in self.device_list:
            for i in range(device.length):
                self.global_map[current_index + i] = device
            current_index += device.length

    ####################
    ###public methods###
    ####################

    def set_brightness(self, p_level=100):
        """
        Sets the brightness of every blinkendevice in this blinkenroom.
        Max is 100, min 0
        """
        for device in self.device_list:
            device.brightness_factor = p_level

    def send_frame(self, p_frame, p_offset = 0):
        """
        Sends an whole frame to all blinkenfoo devices. The frames length has to
        match all blinkenfoos length combined.
        """
        frame_length = len(p_frame)

        #TODO Make this more efficient -> use blinkenfoo's send_frame, dont send
        #a frame pixel wise, as every single pixel has to be mapped to a
        #blinkenfoo
        if not frame_length == self.total_length:
            raise ValueError("Frame length does not match total length: Given %d, expected %d" % (frame_length, self.total_length))
        for i in range(frame_length):
            current_index = (i + p_offset) % frame_length
            pixel = p_frame[current_index]
            self.set_pixel(i, pixel)
        self.flush()

    def set_full_color(self, p_color):
        """
        Sets all blinkenfoo to the given color.
        """
        for device in self.device_list:
            device.set_full_color(p_color)

    def set_pixel(self, p_index, p_color):
        """
        Sets a single pixel. The index is mapped to a blinkenfoo in the
        blinkenroom.
        """
        device = self.global_map[p_index]
        if device == None:
            raise ValueError("Index is not mapped to any device (out of range)")

        # Mapping index to device
        # (Substracting foregone device length)
        for i in range(len(self.device_list)):
            current_device = self.device_list[i]
            if current_device == device:
                break
            p_index -= current_device.length
        device.set_pixel(p_index, p_color)

    def flush(self, p_reset=False):
        """
        Flushes the changes made in the buffer (blinkefoo buffers, a
        blinkenroom has no buffer).
        """
        for device in self.device_list:
            device.flush(p_reset)

    def get_device_by_index(self, p_index):
        """
        Returns the blinkenfoo that the given index is mapped on
        """
        if p_index > self.total_length:
            raise ValueError("Index out of range:%d > %d" % (p_index, self.total_length))
        return self.global_map[p_index]

    def is_overlapping(self, p_blinkeroom):
        """
        Checks wether a blinkenroom's devices overlap with this's devices
        """
        return len(set(self.device_list).intersection(p_blinkeroom.device_list)) is not 0

    def get_last_frame(self):
        """
        Returns the buffer of all blinkenfoos concatenated. Does not work if
        flush(True) is invoked, since this will clear the buffer after sending.
        """
        frame = []
        for device in self.device_list:
            frame += device.buffer
        return frame

    def clear(self):
        for device in self.device_list:
            device.clear()

BLINKENROOM_LOUNGE = Blinkenroom(blinkenfoo.DEVICE_LIST_LOUNGE)