# Copyright (c) 2010 Joshua Harlan Lifton.
# See LICENSE.txt for details.

# TODO: add options to remap keys
# TODO: look into programmatically pasting into other applications

"For use with a Microsoft Sidewinder X4 keyboard used as stenotype machine."

# TODO: Change name to NKRO Keyboard.

from plover.machine.base import StenotypeBase
from plover.oslayer import keyboardcontrol

KEYSTRING_TO_STENO_KEY = {"a": "S-",
                          "q": "S-",
                          "w": "T-",
                          "s": "K-",
                          "e": "P-",
                          "d": "W-",
                          "r": "H-",
                          "f": "R-",
                          "c": "A-",
                          "v": "O-",
                          "t": "#",
                          "g": "*",
                          "y": "-F",
                          "h": "-R",
                          "m": "-E",
                          ",": "-U",
                          "u": "-P",
                          "j": "-B",
                          "i": "-L",
                          "k": "-G",
                          "o": "-T",
                          "l": "-S",
                          "p": "-D",
                          ";": "-Z",
                          "1": "#",
                          "2": "#",
                          "3": "#",
                          "4": "#",
                          "5": "#",
                          "6": "#",
                          "7": "#",
                          "8": "#",
                          "9": "#",
                          "0": "#",
                          "-": "#",
                          "=": "#",
                         }


class Stenotype(StenotypeBase):
    """Standard stenotype interface for a Microsoft Sidewinder X4 keyboard.

    This class implements the three methods necessary for a standard
    stenotype interface: start_capture, stop_capture, and
    add_callback.

    """

    def __init__(self, params):
        """Monitor a Microsoft Sidewinder X4 keyboard via X events."""
        StenotypeBase.__init__(self)
        self._keyboard_emulation = keyboardcontrol.KeyboardEmulation()
        self._keyboard_capture = keyboardcontrol.KeyboardCapture()
        self._keyboard_capture.key_down = self._key_down
        self._keyboard_capture.key_up = self._key_up
        self.suppress_keyboard(True)
        self._down_keys = set()
        self._released_keys = set()
        self.arpeggiate = params['arpeggiate']

    def start_capture(self):
        """Begin listening for output from the stenotype machine."""
        self._keyboard_capture.start()
        self._ready()

    def stop_capture(self):
        """Stop listening for output from the stenotype machine."""
        self._keyboard_capture.cancel()
        self._stopped()

    def suppress_keyboard(self, suppress):
        self._is_keyboard_suppressed = suppress
        self._keyboard_capture.suppress_keyboard(suppress)

    def _key_down(self, event):
        """Called when a key is pressed."""
        if (self._is_keyboard_suppressed
            and event.keystring is not None
            and not self._keyboard_capture.is_keyboard_suppressed()):
            self._keyboard_emulation.send_backspaces(1)
        if event.keystring in KEYSTRING_TO_STENO_KEY:
            self._down_keys.add(event.keystring)

    def _post_suppress(self, suppress, steno_keys):
        """Backspace the last stroke since it matched a command.
        
        The suppress function is passed in to prevent threading issues with the 
        gui.
        """
        n = len(steno_keys)
        if self.arpeggiate:
            n += 1
        suppress(n)

    def _key_up(self, event):
        """Called when a key is released."""
        if event.keystring in KEYSTRING_TO_STENO_KEY:            
            # Process the newly released key.
            self._released_keys.add(event.keystring)
            # Remove invalid released keys.
            self._released_keys = self._released_keys.intersection(self._down_keys)

        # A stroke is complete if all pressed keys have been released.
        # If we are in arpeggiate mode then only send stroke when spacebar is pressed.
        send_strokes = bool(self._down_keys and 
                            self._down_keys == self._released_keys)
        if self.arpeggiate:
            send_strokes &= event.keystring == ' '
        if send_strokes:
            steno_keys = [KEYSTRING_TO_STENO_KEY[k] for k in self._down_keys
                          if k in KEYSTRING_TO_STENO_KEY]
            if steno_keys:
                self._down_keys.clear()
                self._released_keys.clear()
                self._notify(steno_keys)

    @staticmethod
    def get_option_info():
        bool_converter = lambda s: s == 'True'
        return {
            'arpeggiate': (False, bool_converter),
        }
