import pytest

from plover import system
from plover.machine.keyboard import Keyboard
from plover.machine.keymap import Keymap
from plover.oslayer.keyboardcontrol import KeyboardCapture

from .py37compat import mock


def send_input(capture, key_events):
    for evt in key_events.strip().split():
        if evt.startswith('+'):
            capture.key_down(evt[1:])
        elif evt.startswith('-'):
            capture.key_up(evt[1:])
        else:
            capture.key_down(evt)
            capture.key_up(evt)


@pytest.fixture
def capture():
    capture = mock.MagicMock(spec=KeyboardCapture)
    with mock.patch('plover.machine.keyboard.KeyboardCapture', new=lambda: capture):
        yield capture

@pytest.fixture(params=[{'arpeggiate': False, 'first_up_chord_send': False}])
def machine(request, capture):
    machine = Keyboard(request.param)
    keymap = Keymap(Keyboard.KEYS_LAYOUT.split(),
                    system.KEYS + Keyboard.ACTIONS)
    keymap.set_mappings(system.KEYMAPS['Keyboard'])
    machine.set_keymap(keymap)
    return machine

arpeggiate = pytest.mark.parametrize('machine', [{'arpeggiate': True, 'first_up_chord_send': False}], indirect=True)
first_up_chord_send = pytest.mark.parametrize('machine', [{'arpeggiate': False, 'first_up_chord_send': True}], indirect=True)
"""
These are decorators to be applied on test functions to modify the machine configuration.
Note that at the moment it's not possible to apply both at the same time.
"""

@pytest.fixture
def strokes(machine):
    strokes = []
    machine.add_stroke_callback(strokes.append)
    return strokes


def test_lifecycle(capture, machine, strokes):
    # Start machine.
    machine.start_capture()
    assert capture.mock_calls == [
        mock.call.start(),
        mock.call.suppress(()),
    ]
    capture.reset_mock()
    machine.set_suppression(True)
    suppressed_keys = dict(machine.keymap.get_bindings())
    del suppressed_keys['space']
    assert strokes == []
    assert capture.mock_calls == [
        mock.call.suppress(suppressed_keys.keys()),
    ]
    # Trigger some strokes.
    capture.reset_mock()
    send_input(capture, '+a +h -a -h space w')
    assert strokes == [
        {'S-', '*'},
        {'T-'},
    ]
    assert capture.mock_calls == []
    # Stop machine.
    del strokes[:]
    machine.stop_capture()
    assert strokes == []
    assert capture.mock_calls == [
        mock.call.suppress(()),
        mock.call.cancel(),
    ]

def test_unfinished_stroke_1(capture, machine, strokes):
    machine.start_capture()
    send_input(capture, '+a +q -a')
    assert strokes == []

def test_unfinished_stroke_2(capture, machine, strokes):
    machine.start_capture()
    send_input(capture, '+a +r -a +a -r')
    assert strokes == []

@arpeggiate
def test_arpeggiate_1(capture, machine, strokes):
    machine.start_capture()
    send_input(capture, 'a h space w')
    assert strokes == [{'S-', '*'}]

@arpeggiate
def test_arpeggiate_2(capture, machine, strokes):
    machine.start_capture()
    send_input(capture, 'a +h +space -space -h w')
    assert strokes == [{'S-', '*'}]

@first_up_chord_send
def test_first_up_chord_send(capture, machine, strokes):
    machine.start_capture()
    send_input(capture, '+a +w +l -l +l')
    assert strokes == [{'S-', 'T-', '-G'}]
    send_input(capture, '-l')
    assert strokes == [{'S-', 'T-', '-G'}, {'S-', 'T-', '-G'}]
    send_input(capture, '-a -w')
    assert strokes == [{'S-', 'T-', '-G'}, {'S-', 'T-', '-G'}]
