from machine import Pin
import keyboard
from time import sleep
import micropython
import array
import rp2
macroCapLen = 25
dictKeyMacros = {'A1':'', 'A2':'','A3':'','A4':'','B1':'', 'B2':'','B3':'','B4':''}
NeoPixelPin = 3
keys = [['A1', 'A2', 'A3', 'A4'], ['B1', 'B2', 'B3', 'B4']]
keyMapDict = {
    "a": [0x24], "A": [0xe1, 0x24]
    }

# Pin names for Pico
rows = [4,5] #Row Pin Numbers
cols = [6,7,8,9] #Column Pin Numbers
row_pins = [Pin(pin_name, mode=Pin.OUT) for pin_name in rows]
col_pins = [Pin(pin_name, mode=Pin.IN, pull=Pin.PULL_DOWN) for pin_name in cols]
BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
WHITE = (255, 255, 255)
ORANGE = (255, 20, 0)
COLORS = {'BLACK':BLACK, 'RED':RED, 'YELLOW':YELLOW,
          'ORANGE':ORANGE, 'GREEN':GREEN, 'CYAN':CYAN,
          'BLUE':BLUE, 'PURPLE':PURPLE, 'WHITE':WHITE}
neoA1 = 0
neoA2 = 1
neoA3 = 2
neoA4 = 3
neoB1 = 7
neoB2 = 6
neoB3 = 5
neoB4 = 4
dictNeoPins = {'A1': neoA1, 'A2': neoA2, 'A3': neoA3, 'A4': neoA4, 'B1': neoB1, 'B2': neoB2, 'B3': neoB3, 'B4': neoB4}
# Configure the number of WS2812 LEDs.
NUM_LEDS = 8
intBrightness = 0.5

@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    label("bitloop")
    out(x, 1)               .side(0)    [T3 - 1]
    jmp(not_x, "do_zero")   .side(1)    [T1 - 1]
    jmp("bitloop")          .side(1)    [T2 - 1]
    label("do_zero")
    nop()                   .side(0)    [T2 - 1]
    wrap()


# Create the StateMachine with the ws2812 program, outputting on pin
sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(NeoPixelPin))

# Start the StateMachine, it will wait for data on its FIFO.
sm.active(1)

# Display a pattern on the LEDs via an array of LED RGB values.
ar = array.array("I", [0 for _ in range(NUM_LEDS)])

##########################################################################
def brightness(strDirection):
    global intBrightness
    if strDirection == 'UP':
        intBrightness = intBrightness + 0.05
    else:
        intBrightness = intBrightness - 0.05
    if intBrightness <= 0.05: intBrightness = 0.05
    if intBrightness >= 0.5: intBrightness = 0.5

def pixels_show():
    dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)])
    for i,c in enumerate(ar):
        r = int(((c >> 8) & 0xFF) * intBrightness)
        g = int(((c >> 16) & 0xFF) * intBrightness)
        b = int((c & 0xFF) * intBrightness)
        dimmer_ar[i] = (g<<16) + (r<<8) + b
    sm.put(dimmer_ar, 8)
    #sleep_ms(10)

def pixels_set(i, color):
    ar[i] = (color[1]<<16) + (color[0]<<8) + color[2]


def neoTest():
    print("rainbow")
    rainbow_cycle(0)
    
class GroupSettings:
    def __init__(self, fileName, caption=None, color=None, keys=None):
        self.fileName = fileName
        self.caption = caption
        self.color = color
        self.keys = keys
        
    @property
    def caption(self):
        return self._caption
    
    @caption.setter
    def caption(self, value):
        if  value != None:
            if len(value) >= macroCapLen:
                value = value[:macroCapLen - 2] + '..'
        self._caption = value
        
    def showvalues(self):
        print("Global Settings")
        print("File Name: ", self.fileName)
        print("Caption: ", self.caption)
        print("Color: ", self.color)
        for key in self.keys:
            key.showvalues("-")

class Key:
    def __init__(self, settingclass, caption=None, color=None, macroTXT=None, macroMap=None):
        self.settingclass = settingclass
        self.caption = caption
        self.color = color
        self.macroTXT = macroTXT

    @property
    def macroTXT(self):
        return self._macroTXT
    
    @macroTXT.setter
    def macroTXT(self, value):
        self._macroTXT = value        
        self.macroMap = scanCodeMapping(value)
        if value != None:
            if "Error" in self.macroMap:
                self.caption = "Error"
                self.macroMap = [[[0xe1]]]
    
    def showvalues(self, leadingChar):
        print(f"{leadingChar}Setting Class: ", self.settingclass)
        print(f"{leadingChar}Caption: ", self.caption)
        print(f"{leadingChar}Color: ", self.color)
        print(f"{leadingChar}MacroTXT: ", self.macroTXT)
        print(f"{leadingChar}MacroMap: ", self.macroMap)
    
    def ExecMacro(self):
        print("Run Macro")

def scanCodeMapping(strMacroTXT):
    bracketfound = False
    tmpString = ""
    arrMappings = []
    arrFinal = []
    dictScanCodes = {'a':[0x04], 'A':[0xe1,0x04],'b':[0x05], 'B':[0xe1,0x05],'c':[0x06], 'C':[0xe1,0x06],'d':[0x07], 'B':[0xe1,0x07],'e':[0x08], 'E':[0xe1,0x08],'f':[0x09], 'F':[0xe1,0x09],'g':[0x0a], 'G':[0xe1,0x0a],'h':[0x0b], 'H':[0xe1,0x0b],'i':[0x0c], 'I':[0xe1,0x0c],'j':[0x0d], 'J':[0xe1,0x0d],'k':[0x0e], 'K':[0xe1,0x0e],'l':[0x0f], 'L':[0xe1,0x0f],'m':[0x10], 'M':[0xe1,0x10],'n':[0x11], 'N':[0xe1,0x11],'o':[0x12], 'O':[0xe1,0x12],'p':[0x13], 'P':[0xe1,0x13],'q':[0x14], 'Q':[0xe1,0x14],'r':[0x15], 'R':[0xe1,0x15],'s':[0x16], 'S':[0xe1,0x16],'t':[0x17], 'T':[0xe1,0x17],'u':[0x18], 'U':[0xe1,0x18],'v':[0x19], 'V':[0xe1,0x19],'w':[0x1a], 'W':[0xe1,0x1a],'x':[0x1b], 'X':[0xe1,0x1b],'y':[0x1c], 'Y':[0xe1,0x1c],'z':[0x1d], 'Z':[0xe1,0x1d],
                     '1':[0x1e], '!':[0xe1,0x1e],'2':[0x1f], '@':[0xe1,0x1f],'3':[0x20], '#':[0xe1,0x20],'4':[0x21], '$':[0xe1,0x21],'5':[0x22], '%':[0xe1,0x22],'6':[0x23], '^':[0xe1,0x23],'7':[0x24], '&':[0xe1,0x24],'8':[0x25], '*':[0xe1,0x25],'9':[0x26], '(':[0xe1,0x26],'0':[0x27], ')':[0xe1,0x27],
                     'ENTER':[0x28],'ESC':[0x29],'BACKSPACE':[0x2a],'TAB':[0x2b],'SPACE':[0x2c],
                     '-':[0x2d], '_':[0xe1,0x2d],'=':[0x2e], 'PLUS':[0xe1,0x2e],'[':[0x2f], '{':[0xe1,0x2f],']':[0x30], '}':[0xe1,0x30],'\\':[0x31], '|':[0xe1,0x31],';':[0x33], ':':[0xe1,0x33],"'":[0x34], '"':[0xe1,0x34],'`':[0x35], '~':[0xe1,0x35],',':[0x36], '<':[0xe1,0x36],'.':[0x37], '>':[0xe1,0x37],'/':[0x38], '?':[0xe1,0x38],
                     'F1':[0x3a],'F2':[0x3b],'F3':[0x3c],'F4':[0x3d],'F5':[0x3e],'F6':[0x3f],'F7':[0x40],'F8':[0x41],'F9':[0x42],'F10':[0x43],'F11':[0x44],'F12':[0x45],
                     'PRINTSCREEN':[0x46],'SCROLLLOCK':[0x47],'PAUSE':[0x48],'INSERT':[0x49],'HOME':[0x4a],'PAGEUP':[0x4b],'DELETE':[0x4c],'END':[0x4d],'PAGEDOWN':[0x4e],'RIGHTARROW':[0x4f],'LEFTARROW':[0x50],'DOWNARROW':[0x51],'UPARROW':[0x52],
                     'MUTE':[0x7f],'VOLUMEUP':[0x80],'VOLUMEDOWN':[0x81],'CTRL':[0xe0],'SHIFT':[0xe1],'ALT':[0xe2],'WINDOWS':[0xe3]}
    if strMacroTXT == "" or strMacroTXT == None:
        return strMacroTXT
    strMacroTXT = strMacroTXT.replace(" ","")
    for charactor in strMacroTXT:
        if charactor == ']':
            tmpString = tmpString.upper()
            arrMappings.append(tmpString)
            bracketfound = False
            tmpString = ""
        elif charactor == '[':
            bracketfound = True
        elif bracketfound:
            tmpString = tmpString + charactor
        else:
            arrMappings.append(charactor)
    if arrMappings[0] == '+': arrMappings.pop(0)
    if arrMappings[len(arrMappings)-1] == '+': arrMappings.pop(len(arrMappings)-1)
    for index in range(0, len(arrMappings)):
        if arrMappings[index] == '+' and arrMappings[index - 2] != '+':
            tmp = arrMappings[index - 1] + arrMappings[index] + arrMappings[index + 1]
            i = 2
            while (i != 0):
                if index + i >= len(arrMappings):
                    i = 0
                elif arrMappings[index + i] == '+':
                    tmp = tmp + arrMappings[index + i] + arrMappings[index + 1 + i]
                    i = i + 2
                else:
                    i = 0
            arrFinal.append(tmp)
        elif index == len(arrMappings) - 1:
            if arrMappings[index - 1] != '+': arrFinal.append(arrMappings[index])
        elif arrMappings[index] != '+' and arrMappings[index + 1] != '+' and arrMappings[index - 1] != '+':
            arrFinal.append(arrMappings[index])
    arrKeys = []    
    for export in arrFinal:
        keyStrokes = export.split("+")
        if len(keyStrokes) >= 2: #Check if there are multiple Keys
            for stroke in range(0, len(keyStrokes)):
                if len(keyStrokes[stroke]) <= 1:
                    keyStrokes[stroke] = keyStrokes[stroke].lower()
                
                if keyStrokes[stroke] in dictScanCodes:
                    keyStrokes[stroke] = dictScanCodes[keyStrokes[stroke]]
                else:
                    arrKeys.append("Error")
                ++stroke
        else:
            keyStrokes[0] = dictScanCodes[keyStrokes[0]]
        arrKeys.append(keyStrokes)
    return arrKeys

def processKeySettings(listKeys):
    for obj in listKeys:
        if obj.settingclass == "GLOBAL":
            objglobal = obj
    for obj in listKeys:
        if obj.settingclass != "GLOBAL" and obj.color == "":
            obj.color = objglobal.color
    for obj in list(listKeys):
        if obj.settingclass != "GLOBAL" and obj.macroTXT == "":
            listKeys.remove(obj)
    return listKeys

def checkKeyPress():
    global keypad
    print(f"A: {keyRowA.value()} B: {keyRowB.value()} 4: {keyColm4.value()} ")
    key = keypad.get_key()
    if key :
        print( "keypad: got key:", key )
    sleep(1)

def setNeoLED(intLED, color):
    #print(f"LED ID {intLED} Color {color}")
    pixels_set(intLED, color)
    pixels_show()

def setKeysForProfileSelect():
    clearKeyColors()
    setNeoLED(neoA4, GREEN)
    setNeoLED(neoB4, RED)
    setNeoLED(neoA1, WHITE)
    setNeoLED(neoB1, WHITE)
    setNeoLED(neoA2, WHITE)
    setNeoLED(neoB2, WHITE)

def setKeysForMacro(dictProfile):
    clearKeyColors()
    for objKey in dictProfile:
        pass
    
def clearKeyColors():
    setNeoLED(neoA1, BLACK)
    setNeoLED(neoA2, BLACK)
    setNeoLED(neoA3, BLACK)
    setNeoLED(neoA4, BLACK)
    setNeoLED(neoB1, BLACK)
    setNeoLED(neoB2, BLACK)
    setNeoLED(neoB3, BLACK)
    setNeoLED(neoB4, BLACK)

def loadProfile(profileList):
    global dictNeoPins, COLORS, dictKeyMacros
    dictKeyMacros = dictKeyMacros.fromkeys(dictKeyMacros,"")
    for obj in profileList.keys:
        if obj.macroTXT != None and obj.macroTXT != "":
            #print(f"Key: {obj.settingclass} Caption: {obj.caption} Color: {obj.color} Macro: {obj.macroTXT}")
            setNeoLED(dictNeoPins[obj.settingclass], COLORS[obj.color.upper()])
            #print(f"Setting NEO: {obj.color.upper()}")
            dictKeyMacros[obj.settingclass] = obj.macroMap
    #print(f"dictKeyMacro: {dictKeyMacros}")

def keyPadSetup():
    for row in range(0,2):
        for col in range(0,4):
            row_pins[row].low()

def checkKeyPad():
    for row in range(2):
        for col in range(4):
            row_pins[row].high()
            if col_pins[col].value() == 1:
                while col_pins[col].value() == 1: sleep(0.1) #pass
                row_pins[row].low()
                return keys[row][col]
        row_pins[row].low()
    return None

def checkMacoKeys():
    global dictKeyMacros
    keypressed = checkKeyPad()
    if keypressed != None:
        #print(f"Key pressed: {keypressed} Macro: {dictKeyMacros.get(keypressed)}")
        exeMacro(dictKeyMacros.get(keypressed))
        
def exeMacro(arrMacro):
    k = keyboard.Keyboard()
    for kGroup in arrMacro:
        for keys in kGroup:
            for key in keys:
                k.press(key)
        k.release_all()
    k.release_all()
    
keyPadSetup()