from .Fader import Fader
import rtmidi


class MidiPlus:
    midi_in = rtmidi.MidiIn()
    midi_out = rtmidi.MidiOut()

    faders: 'list[Fader]' = [None] * 17
    callbacks = {}

    button_ids = {
        "LAYER_1": 0x40,
        "LAYER_2": 0x41,
        "LAYER_3": 0x42,
        "LAYER_4": 0x43,
        "LAYER_5": 0x44,
        "LAYER_6": 0x45,
        "LAYER_7": 0x46,
        "LAYER_8": 0x47,
        "UTIL_1": 0x50,
        "UTIL_2": 0x51,
        "UTIL_3": 0x52,
        "UTIL_4": 0x53,
        "UTIL_5": 0x54,
        "UTIL_6": 0x55,
        "UTIL_7": 0x56,
        "UTIL_8": 0x57,
        "TEMPO": 0x58
    }

    def __init__(self, in_port_num: int, out_port_num: int):
        self.midi_in.open_port(in_port_num)
        self.midi_out.open_port(out_port_num)
        for i in range(1, 18):
            self.faders[i-1] = Fader(self, i)
        self.midi_in.set_callback(self.__on_recv, self)

    def __gen_display_header(_):
        return [0xf0, 0x00, 0x00, 0x74, 0x3c, 0x1a, 0x01]

    def __gen_rgb_header(_):
        return [0xf0, 0x00, 0x00, 0x74, 0x3c, 0x1a, 0x03, 0x01, 0x40]

    def raw_write(self, message):
        # print("[" + (" ".join("0x%02x" % b for b in message) + "]"))
        self.midi_out.send_message(message)

    def __on_recv(self, midi_data, _):
        message = midi_data[0]
        command = message[0]
        # print("cmd 0x%02x \t data %s" % (command, (" ".join(
        #     "0x%02x" % b for b in message[1:]))))
        if command == 0x90:  # Button press
            self.__on_btn(message[1:])
        elif (command >= 0xE0 and command <= 0xEF) or command == 0xAF:  # Fader move
            self.__on_fader_move(command, message[1:])
        elif command == 0xb0:
            self.__on_knob_turn(message[1:])

    def __on_btn(self, data):
        button_id = data[0]
        pressed = (data[1] == 0x7f)
        # global buttons (right side)
        if (button_id >= self.button_ids["LAYER_1"] and button_id <= self.button_ids["TEMPO"]):
            if button_id >= self.button_ids["LAYER_1"] and button_id <= self.button_ids["LAYER_8"]:
                layer = button_id - (self.button_ids["LAYER_1"] - 1)
                if pressed:
                    self.fire_callback("layer_press", layer)
                else:
                    self.fire_callback("layer_release", layer)
            if button_id >= self.button_ids["UTIL_1"] and button_id <= self.button_ids["UTIL_8"]:
                layer = button_id - (self.button_ids["UTIL_1"] - 1)
                if pressed:
                    self.fire_callback("util_press", layer)
                else:
                    self.fire_callback("util_release", layer)
            if button_id == self.button_ids["TEMPO"]:
                if pressed:
                    self.fire_callback("tempo_press")
                else:
                    self.fire_callback("tempo_release")
        else:
            for fader in self.faders:
                if button_id == fader.select_btn_id:
                    if pressed:
                        fader.fire_callback("select_press")
                    else:
                        fader.fire_callback("select_release")
                elif button_id == fader.solo_btn_id:
                    if pressed:
                        fader.fire_callback("solo_press")
                    else:
                        fader.fire_callback("solo_release")
                elif button_id == fader.mute_btn_id:
                    if pressed:
                        fader.fire_callback("mute_press")
                    else:
                        fader.fire_callback("mute_release")
                elif button_id == fader.fader_touch_id:
                    if pressed:
                        fader.fire_callback("fader_touch")
                    else:
                        fader.fire_callback("fader_release")
                elif button_id == fader.knob_id:
                    if pressed:
                        fader.fire_callback("knob_press")
                    else:
                        fader.fire_callback("knob_release")
        pass

    def __on_fader_move(self, command, data):
        for fader in self.faders:
            if command == fader.fader_id:
                fader.fire_callback(
                    "fader_move", int.from_bytes(data, 'little'))

    def __on_knob_turn(self, data):
        knob_id = data[0]
        speed = data[1]
        for fader in self.faders:
            if knob_id == fader.knob_id:
                if (speed > 0x40):
                    speed = -(speed - 0x40)
                fader.fire_callback("knob_turn", speed)

    def register_callback(self, name: str, callback):
        self.callbacks[name] = callback

    def fire_callback(self, name, *args):
        if name in self.callbacks:
            self.callbacks[name](self, *args)

    def write_single_row_single_screen(self, screen: int, row: int, text: str):
        # map right-most display to nicer value
        if screen == 17:
            screen = 127
        message = self.__gen_display_header()
        message.append(0x00)
        message.append(0x07)
        message.append(screen)
        message.append(row)
        max_len = 7
        # make sure str ends up as `max_len` chars
        if (len(text) > max_len):
            text = text[0:max_len]
        text = text.ljust(max_len, " ")
        for char in text:
            message.append(ord(char))
        message.append(0xf7)
        self.raw_write(message)

    def write_all_rows_single_screen(self, screen: int, text: str):
        # map right-most display to nicer value
        if screen == 17:
            screen = 127
        message = self.__gen_display_header()
        message.append(0x00)
        message.append(0x07)
        message.append(screen)
        message.append(0x00)
        max_len = 7 * 5
        # make sure str ends up as `max_len` chars
        if (len(text) > max_len):
            text = text[0:max_len]
        text = text.ljust(max_len, " ")
        for char in text:
            message.append(ord(char))
        message.append(0xf7)
        self.raw_write(message)

    def write_single_row_all_screens(self, row: int, text: str):
        message = self.__gen_display_header()
        message.append(0x00)
        message.append(0x07)
        message.append(0x00)
        message.append(row)
        max_len = 7 * 17
        # make sure str ends up as `max_len` chars
        if (len(text) > max_len):
            text = text[0:max_len]
        text = text.ljust(max_len, " ")
        for char in text:
            message.append(ord(char))
        message.append(0xf7)
        self.raw_write(message)

    def write_all_rows_all_screens(self, text: str):
        message = self.__gen_display_header()
        message.append(0x00)
        message.append(0x07)
        message.append(0x00)
        message.append(0x00)
        max_len = 7 * 5 * 17
        # make sure str ends up as `max_len` chars
        if (len(text) > max_len):
            text = text[0:max_len]
        text = text.ljust(max_len, " ")
        for char in text:
            message.append(ord(char))
        message.append(0xf7)
        self.raw_write(message)

    def clear_screens(self):
        self.write_all_rows_all_screens(" " * (7 * 5 * 17))

    def set_led_channel_select(self, channel_num: int, state: bool):
        if channel_num <= 17:  # limit it to prevent setting other LEDs with this function
            note = channel_num - 1
            # map right-most btn to nicer value
            if channel_num == 17:
                note = 0x70
            self.raw_write(
                [0x90, note, (0x7F if state else 0x00)])

    def set_led_channel_solo(self, channel_num: int, state: bool):
        if channel_num <= 17:  # limit it to prevent setting other LEDs with this function
            note = 0x20 + (channel_num - 1)
            # map right-most btn to nicer value
            if channel_num == 17:
                note = 0x72
            self.raw_write(
                [0x90, note, (0x7F if state else 0x00)])

    def set_led_channel_mute(self, channel_num: int, state: bool):
        if channel_num <= 17:  # limit it to prevent setting other LEDs with this function
            note = 0x30 + (channel_num - 1)
            # map right-most btn to nicer value
            if channel_num == 17:
                note = 0x73
            self.raw_write(
                [0x90, note, (0x7F if state else 0x00)])

    def set_led_layer(self, layer_num: int, state: bool):
        note = 0x40 + (layer_num - 1)
        self.raw_write(
            [0x90, note, (0x7F if state else 0x00)])

    def set_led_util(self, util_num: int, state: bool):
        note = 0x50 + (util_num - 1)
        self.raw_write(
            [0x90, note, (0x7F if state else 0x00)])

    def set_led_tempo(self, state: bool):
        self.raw_write(
            [0x90, 0x58, (0x7F if state else 0x00)])

    def set_fader(self, channel_num: int, val: int):
        note = 0xE0 + (channel_num - 1)
        if channel_num == 17:
            note = 0xAF
        message = [note]
        for b in val.to_bytes(2, 'little'):
            message.append(b)
        self.raw_write(message)

    # def set_color_table(self, color_table)