#
# Read data in the json format from the serial connexion.
# The json shall hold in a single line, and the following commands are
# recognized :
# {"layer": layer_name} : change the keyboard to the given layer
# {"stack": layer_name} : stack a new layer on top on an existing one
# {"get_layer"}
#

from usb_cdc import data
import json

from kmk.modules import Module
from kmk.keys import Key, KC
#from kmk.handlers.sequences import simple_key_sequence
from kmk.modules.macros import Delay, Macros, Tap

# This dictionnary is a sort of point of function.
#
# It gives a way to apply a specific function in the json
#
# There is mutually recursive calls between
#   - operators
#   - key_of_json
#   - sequence
#
# I do not define it yet, only declare it.
operators = {}

def key_of_json(element, inside=False):
    """ Create the key from the key name.
        The value can be either a string or a list. If the parameter is a
        string, the associated key will be returned, otherwise this will be the
        chain of all of them.
    """
    if isinstance(element, int):
        return element
    if isinstance(element, str):
        return KC[element]
    if isinstance(element, list):
        hd, *tl = element
        key = key_of_json(hd)
        if len(tl) != 0:
            # Chainable key / recursive function
            return key( key_of_json(tl) )
        else:
            return key
    if isinstance(element, dict):
        for key, value in element.items():
            return operators[key](value)
    pass

def encode_macro(element):
    """ Encode the result of the function key_of_json in order to build a macro
        of this
    """
    key = key_of_json(element)
    if isinstance(key, int):
        return Delay(key)
    elif isinstance(key, Key):
        return Tap(key)
    return key

def sequence(element):
    """ Convert a list of keys into a sequence of them.
    """
    # Convert each element in the list as Tap function
    seq = list(map(encode_macro, element))
    # Send the list as arguments to the function
    return KC.MACRO(*seq)

def no_release(element):
    key = key_of_json(element)
    return key(no_release=True)

def no_press(element):
    key = key_of_json(element)
    return key(no_press=True)

# Now I can define the dictionnary
operators["key"]        = key_of_json
operators["chain"]      = key_of_json
operators["seq"]        = sequence
operators["no_release"] = no_release
operators["no_press"]   = no_press

class Layer(object):
    """ Layer as an object.
        This class gives the property name in addition of the keymap list
    """

    def __init__(self, jdata):
        json_data = json.loads(jdata.strip())
        self.load(json_data)

    def load(self, json_data):
        """ Load the json dictionnary into the layer. The dictionnary shall be
            either
            - a string:   The key named will be sent: "A"
            - a list: All the keys will be chained in a single stroke:
              ["^", "A"]
            - a dictionnary:  Used to create custom sequences:
              {"seq": ["/", "W", "C", "ENTER"]}
            - null:   The key will do nothing.

            The dictionnary must be one of the operators declared above.
        """
        for name, keys in json_data.items():
            self.name = name
            self.keys = list(map(key_of_json, keys))

    def __getitem__(self, idx):
        """ When the layer is indexed, return the keymap associated with
        """
        return self.keys[idx]

    def __eq__(self, other):
        """ Compare two layer by the name only
        """
        return self.name == other.name

class JsonLayer(Module):

    def during_bootup(self, keyboard):
        try:
            # Do not set any timeout, we check before reading a string if there
            # is any content to read, but block to be sure to read everything.
            data.timeout = None
        except AttributeError:
            pass

    def before_matrix_scan(self, keyboard):
        pass

    def after_matrix_scan(self, keyboard):
        pass

    def process_key(self, keyboard, key, is_pressed, int_coord):
        return key

    def before_hid_send(self, keyboard):
        # Serial.data isn't initialized.
        if not data:
            return

        if not data.connected:
            keyboard.keymap[0].name = "Disconnected"
            return

        # Nothing to parse.
        if data.in_waiting <= 0:
            return
        line = data.readline()
        if not line:
            return

        try:
            jdata = json.loads(line.decode().strip())
            keyboard.keymap[0].load(jdata)
        except Exception as err:
            print(err)

    def after_hid_send(self, keyboard):
        pass

    def on_powersave_enable(self, keyboard):
        pass

    def on_powersave_disable(self, keyboard):
        pass