import pytest

from midi_controller_launchpad_mini import MidiController
from solo_tool import SoloTool
from player_mock import Player as PlayerMock

class MidiWrapperMock:
    def __init__(self):
        self.callback = None
        self.connectedDevice = None
        self.lastMessageSent = None

    def setCallback(self, callback):
        self.callback = callback

    def connect(self, deviceName):
        self.connectedDevice = deviceName
    
    def sendMessage(self, note, velocity=127, channel=0):
        pass
    
    def simulateInput(self, note, velocity=127, channel=0):
        if self.callback is not None:
            from mido import Message
            msg = Message("note_on", note=note, velocity=velocity, channel=channel)
            self.callback(msg)

@pytest.fixture
def playerMock():
    return PlayerMock()

@pytest.fixture
def soloTool(playerMock):
    return SoloTool(playerMock)

@pytest.fixture
def midiWrapperMock():
    return MidiWrapperMock()

@pytest.fixture
def uut(soloTool, midiWrapperMock):
    return MidiController(soloTool, midiWrapperMock)

def test_connect(uut, midiWrapperMock):
    expectedDevice = "Launchpad Mini MIDI 1"
    uut.connect()

    assert midiWrapperMock.connectedDevice == expectedDevice

def test_startStopAndPauseButtons(uut, midiWrapperMock, playerMock):
    playPauseButton = 112
    stopButton = 96
    uut.connect()

    assert playerMock.state == PlayerMock.STOPPED

    midiWrapperMock.simulateInput(playPauseButton)
    assert playerMock.state == PlayerMock.PLAYING

    midiWrapperMock.simulateInput(stopButton)
    assert playerMock.state == PlayerMock.STOPPED

    midiWrapperMock.simulateInput(playPauseButton)
    midiWrapperMock.simulateInput(playPauseButton)
    assert playerMock.state == PlayerMock.PAUSED

    midiWrapperMock.simulateInput(playPauseButton)
    assert playerMock.state == PlayerMock.PLAYING

    midiWrapperMock.simulateInput(playPauseButton)
    midiWrapperMock.simulateInput(stopButton)
    assert playerMock.state == PlayerMock.STOPPED

def test_jumpToAButton(uut, midiWrapperMock, soloTool, playerMock):
    ab = (0.5, 0.6)
    uut.connect()
    
    soloTool.setAbLimits(ab[0], ab[1])
    assert playerMock.position == 0.0

    midiWrapperMock.simulateInput(101)
    assert playerMock.position == ab[0]

def test_previousAndNextSongButtons(uut, midiWrapperMock, soloTool, playerMock):
    nextSongButton = 119
    previousSongButton = 118
    songs = [
        "test.flac",
        "test.mp3"
    ]
    for s in songs:
        soloTool.addSong(s)
    uut.connect()

    assert playerMock.currentSong == None
    midiWrapperMock.simulateInput(nextSongButton)
    assert playerMock.currentSong == songs[0]

    midiWrapperMock.simulateInput(nextSongButton)
    assert playerMock.currentSong == songs[1]

    midiWrapperMock.simulateInput(previousSongButton)
    assert playerMock.currentSong == songs[0]

    midiWrapperMock.simulateInput(previousSongButton)
    assert playerMock.currentSong == songs[0]

def test_previousAndNextAbButtons(uut, midiWrapperMock, soloTool, playerMock):
    nextLimitButton = 103
    previousLimitButton = 102
    song = "test.flac"
    abLimits = [
        [0.2, 0.4],
        [0.1, 0.3]
    ]

    soloTool.addSong(song)
    soloTool.setSong(0)
    soloTool.setAbLimitEnable(True)

    for ab in abLimits:
        soloTool.storeAbLimits(ab[0], ab[1])

    uut.connect()

    def checkLimit(aLimit, bLimit):
        playerMock.position = bLimit - 0.1
        soloTool.tick()
        assert playerMock.position == bLimit - 0.1

        playerMock.position = bLimit + 0.1
        soloTool.tick()
        assert playerMock.position == aLimit

    checkLimit(0.0, 0.0)

    midiWrapperMock.simulateInput(nextLimitButton)
    checkLimit(abLimits[0][0], abLimits[0][1])

    midiWrapperMock.simulateInput(nextLimitButton)
    checkLimit(abLimits[1][0], abLimits[1][1])

    midiWrapperMock.simulateInput(nextLimitButton)
    checkLimit(abLimits[1][0], abLimits[1][1])

    midiWrapperMock.simulateInput(previousLimitButton)
    checkLimit(abLimits[0][0], abLimits[0][1])

    midiWrapperMock.simulateInput(previousLimitButton)
    checkLimit(abLimits[0][0], abLimits[0][1])

def test_playbackRateButtons(uut, midiWrapperMock, soloTool, playerMock):
    playbackRateOptions = {
        16 : 0.5,
        17 : 0.6,
        18 : 0.7,
        19 : 0.8,
        20 : 0.9,
        21 : 1.0,
        22 : 1.1,
        23 : 1.2
    }
    uut.connect()
    assert playerMock.rate == 1.0

    for button in playbackRateOptions:
        midiWrapperMock.simulateInput(button)
        assert playerMock.rate == playbackRateOptions[button]

def test_playbackVolumeButtons(uut, midiWrapperMock, soloTool, playerMock):
    playbackVolumeOptions = {
        0 : 0.125,
        1 : 0.250,
        2 : 0.375,
        3 : 0.500,
        4 : 0.625,
        5 : 0.750,
        6 : 0.875,
        7 : 1.000
    }
    uut.connect()
    assert playerMock.volume == 1.0

    for button in playbackVolumeOptions:
        midiWrapperMock.simulateInput(button)
        assert playerMock.volume == playbackVolumeOptions[button]

def test_unassignedButton(uut, midiWrapperMock):
    unassignedButton = 48
    uut.connect()

    # expect no crash
    midiWrapperMock.simulateInput(unassignedButton)
    # XXX would be better to assert that nothing changed in the solo tool