from time import sleep_ms
from machine import Pin


class keypad_api:
    ENTER_CHAR = '#'
    BACKSPACE_CHAR = '*'
    CHAR_ROW0 = [0, ' ']
    CHAR_ROW1 = [1, 'A', 'B', 'C']
    CHAR_ROW2 = [2, 'D', 'E', 'F']
    CHAR_ROW3 = [3, 'G', 'H', 'I']
    CHAR_ROW4 = [4, 'J', 'K', 'L']
    CHAR_ROW5 = [5, 'L', 'M', 'N']
    CHAR_ROW6 = [6, 'O', 'P', 'R']
    CHAR_ROW7 = [7, 'S', 'T', 'U']
    CHAR_ROW8 = [8, 'V', 'W', 'X']
    CHAR_ROW9 = [9, 'Y', 'Z']
    NULL_ROW = ['']
    def __init__(self, char_callback, update = print):
        self.char_callback = char_callback
        self.update = update
    def exclude_values(self, input_char, values):
        values = list(values) + [None]
        if input_char in values:
            input_char = ''
        return input_char
    def limit_char(self, input_char, input_string, target_char, n = 1):
        if target_char == '' or target_char is None:
            return input_char
        elif input_string.count(target_char) < n and input_char == target_char:
            return input_char
        elif input_char != target_char:
            return input_char
        return ''
    def no_leading_0 (self, input_str):
        while input_str[0] == '0':
            input_str = input_str[1:]
        return input_str
    def get_str(self, forbidden_chars = [], limit_char = ''):
        out_str = ''
        char = ''
        while char is not('#'):
            char = self.exclude_values(self.char_callback(), forbidden_chars)
            char = str(char)
            char = self.limit_char(char, out_str, limit_char)
            if char == '*': #uses '*' as a backspace function
                out_str = out_str[:-1]
                char = ''
            out_str = out_str + char
            self.update(out_str)
        out_str = out_str[:-1]
        return out_str
    def get_int(self):
        int_output = (self.get_str(forbidden_chars = ['A', 'B', 'C', 'D']))
        if int_output == '':
            return 0
        else:
            int_output = int(self.no_leading_0(int_output))
            return int_output
    def get_float(self):
        float_input = self.get_str(forbidden_chars = ['A', 'B', 'C'], limit_char = 'D')
        if float_input == '':
            return 0
        else:
            float_input = float_input.replace('D', '.')
            float_input = self.no_leading_0(float_input)
            return float(float_input)
    def incremental_selector(self, input_list):
        char = ''
        position = 0
        max_position = len(input_list) - 1
        while char is not('#'):
            char =  self.exclude_values(self.char_callback(), [1,2,3,4,5,6,7,8,9,'*','C','D'])
            if char == 'A':
                position -= 1
                if position < 0:
                    position = max_position
            elif char == 'B':
                position += 1
                if position > max_position:
                    position = 0
            if char == 'A' or char == 'B':
                self.update(input_list[position])
                char = ''
            sleep_ms(100)
        return input_list[position]
    def get_char_row(self, value):
        if value == 0:
            return keypad_api.CHAR_ROW0
        elif value == 1:
            return keypad_api.CHAR_ROW1
        elif value == 2:
            return keypad_api.CHAR_ROW2
        elif value == 3:
            return keypad_api.CHAR_ROW3
        elif value == 4:
            return keypad_api.CHAR_ROW4
        elif value == 5:
            return keypad_api.CHAR_ROW5
        elif value == 6:
            return keypad_api.CHAR_ROW6
        elif value == 7:
            return keypad_api.CHAR_ROW7
        elif value == 8:
            return keypad_api.CHAR_ROW8
        elif value == 9:
            return keypad_api.CHAR_ROW9
        else:
            return keypad_api.NULL_ROW
    def cycle_char_row(self, update_text = ''):
        #fix need to press buttons multiple times to cycle inputs
        starting_char = self.exclude_values(self.char_callback(), ['A', 'B', 'D'])
        position = 0
        char_select = self.get_char_row(starting_char)
        output_char = ''
        if starting_char == '*' or starting_char == '#':
            return starting_char
        char = self.exclude_values(self.char_callback(), ['A', 'B', 'D'])
        while char == starting_char or char == '':
            char = self.exclude_values(self.char_callback(), ['A', 'B', 'D'])
            if char == starting_char:
                position += 1
                if position >= len(char_select):
                    position = 0
                self.update(update_text + str(char_select[position]))
                output_char = char_select[position]
        return output_char
    def get_alphanum(self):
        char = ''
        output = ''
        while char is not('#'):
            char = self.cycle_char_row(output)
            if char == '*':
                output = output[:-1]
                char = ''
                self.update(output)
            output += str(char)
            self.update(output)
        output = output[:-1]
        return output


class keypad:
    MAP_16X = [[1,2,3,'A'],
    [4,5,6,'B'],
    [7,8,9,'C'],
    ['*',0,'#','D']]
    def __init__(self, rows, collumns, key_map, debounce_time = 200):
        self.rows = [Pin(pin, Pin.OUT) for pin in rows]
        self.collumns = [Pin(pin, Pin.IN, Pin.PULL_DOWN) for pin in collumns]
        self.key_map = key_map
        self.debounce_time = debounce_time
    def get_key(self, row, collumn):
        state = None
        self.rows[row].value(1)
        if self.collumns[collumn].value() == 1:
            state = [row, collumn]
        self.rows[row].value(0)
        return state
    def get_keys(self):
        key = None
        for r in range(len(self.rows)):
            for c in range(len(self.collumns)):
                state = self.get_key(r, c)
                if state is not(None):
                    key = state
        return key
    def get_char(self):
        char = None
        state = self.get_keys()
        while state is None:
            state = self.get_keys()
            sleep_ms(self.debounce_time)
        char = self.key_map[state[0]][state[1]]
        return char


if __name__ == '__main__':
    ROWS = [14, 27, 26, 25]
    COLLUMNS = [33, 32, 12, 13]
    kp = keypad(ROWS, COLLUMNS, keypad.MAP_16X)
    kp_str = keypad_api(kp.get_char)
    '''
    for x in range(5):
        print(kp.get_char())
    print(kp_str.get_str())
    '''
    #int_math_test = kp_str.get_int() + 3
    #print(int_math_test)
    #print(kp_str.get_float())
    #print(kp_str.incremental_selector(['a', 'b', 'c']))
    #print(kp_str.cycle_char_row())
    print(kp_str.get_alphanum())