from .midi_wrapper_mido import MidiWrapper
from . import handlers

class MidiController:
    DEVICE_NAME = "Launchpad Mini MIDI 1"
    LIGHT_CONTROL_CHANNEL = 0
    LED_GREEN  = 124
    LED_YELLOW = 126
    LED_RED    = 3
    LED_OFF    = 0
    BUTTON_MATRIX = [[x for x in range(y * 16, y * 16 + 8)] for y in range(0,8)] # address as [row][col]

    MIN_PLAYBACK_RATE = 0.5
    MAX_PLAYBACK_RATE = 1.2
    PLAYBACK_RATE_STEP = 0.1

    MIN_PLAYBACK_VOLUME = 0.5
    MAX_PLAYBACK_VOLUME = 1.2
    PLAYBACK_VOLUME_STEP = 0.1

    def __init__(self, soloTool, midiWrapperOverride=None):
        self._soloTool = soloTool
        if midiWrapperOverride is not None:
            self._midiWrapper = midiWrapperOverride
        else:
            self._midiWrapper = MidiWrapper()

        self._registerHandlers()
        self._soloTool.registerPlayingStateCallback(self._updatePlayPauseButton)
        self._soloTool.registerVolumeCallback(self._updateVolumeRow)
        self._soloTool.registerRateCallback(self._updateRateRow)

    def _registerHandlers(self):
        self._handlers = {
            96  : self._soloTool.stop,
            114 : self._soloTool.jump,
            112 : handlers.playPause(self._soloTool),
            118 : handlers.changeKeyPoint(self._soloTool, -1),
            119 : handlers.changeKeyPoint(self._soloTool, 1),
            117 : handlers.positionToKeyPoint(self._soloTool),
            48  : handlers.changeSong(self._soloTool, -1), 
            49  : handlers.seekRelative(self._soloTool, -0.25),
            50  : handlers.seekRelative(self._soloTool, -0.05),
            51  : handlers.seekRelative(self._soloTool, -0.01),
            52  : handlers.seekRelative(self._soloTool, 0.01),
            53  : handlers.seekRelative(self._soloTool, 0.05),
            54  : handlers.seekRelative(self._soloTool, 0.25),
            55  : handlers.changeSong(self._soloTool, 1), 
        }
        
        for i in range(0, 8):
            volume = round(MidiController.MIN_PLAYBACK_VOLUME + MidiController.PLAYBACK_VOLUME_STEP * i, 1)
            self._handlers[i] = handlers.volumeAbsolute(self._soloTool, volume)

        for i, button in enumerate(range(16, 24)):
            rate = round(MidiController.MIN_PLAYBACK_RATE + MidiController.PLAYBACK_RATE_STEP * i, 1)
            self._handlers[button] = handlers.rateAbsolute(self._soloTool, rate)

    def connect(self):
        self._midiWrapper.connect(MidiController.DEVICE_NAME, self._callback)
        self._initialiseButtonLEDs()

    def disconnect(self):
        self._setAllLEDs(MidiController.LED_OFF)
        self._midiWrapper.disconnect()

    def _callback(self, msg):
        if msg.type != "note_on" or msg.velocity < 127:
            return

        if msg.note in self._handlers:
            handler = self._handlers[msg.note]()

    def _updatePlayPauseButton(self, playing):
        if playing:
            self._setButtonLED(7, 0, MidiController.LED_GREEN)
        else:
            self._setButtonLED(7, 0, MidiController.LED_YELLOW)

    def _updateVolumeRow(self, volume):
        t1 = int(round(volume / MidiController.PLAYBACK_VOLUME_STEP, 1))
        t2 = int(round(MidiController.MIN_PLAYBACK_VOLUME / MidiController.PLAYBACK_VOLUME_STEP, 1))
        lastColumnLit = t1 - t2 + 1
        self._lightRowUntilColumn(0, lastColumnLit, MidiController.LED_GREEN)

    def _updateRateRow(self, rate):
        t1 = int(round(rate / MidiController.PLAYBACK_RATE_STEP, 1))
        t2 = int(round(MidiController.MIN_PLAYBACK_RATE / MidiController.PLAYBACK_RATE_STEP, 1))
        lastColumnLit = t1 - t2 + 1
        self._lightRowUntilColumn(1, lastColumnLit, MidiController.LED_YELLOW)

    def _setButtonLED(self, row, col, colour):
        self._midiWrapper.sendMessage(MidiController.BUTTON_MATRIX[row][col], colour, MidiController.LIGHT_CONTROL_CHANNEL)

    def _lightRowUntilColumn(self, row, column, litColour):
        colours = [litColour] * column + [MidiController.LED_OFF] * (8 - column)
        for col in range(0, 8):
            self._setButtonLED(row, col, colours[col])

    def _setAllLEDs(self, colour):
        for row in range(0, 8):
            for col in range(0, 8):
                self._setButtonLED(row, col, colour)

    def _initialiseButtonLEDs(self):
        self._setAllLEDs(MidiController.LED_OFF)

        # volume buttons
        self._updateVolumeRow(self._soloTool.volume)

        # playback rate buttons
        self._updateRateRow(self._soloTool.rate)

        # playback control
        self._setButtonLED(6, 0, MidiController.LED_RED)
        self._updatePlayPauseButton(self._soloTool.playing)

        # Key point control
        self._setButtonLED(7, 2, MidiController.LED_YELLOW)
        self._setButtonLED(7, 6, MidiController.LED_RED)
        self._setButtonLED(7, 7, MidiController.LED_GREEN)
        self._setButtonLED(7, 5, MidiController.LED_YELLOW)

        # Song control
        self._setButtonLED(3, 0, MidiController.LED_RED)
        self._setButtonLED(3, 1, MidiController.LED_RED)
        self._setButtonLED(3, 2, MidiController.LED_RED)
        self._setButtonLED(3, 3, MidiController.LED_RED)
        self._setButtonLED(3, 4, MidiController.LED_GREEN)
        self._setButtonLED(3, 5, MidiController.LED_GREEN)
        self._setButtonLED(3, 6, MidiController.LED_GREEN)
        self._setButtonLED(3, 7, MidiController.LED_GREEN)