import json
import os
import tkinter as tk
from tkinter.font import Font
from time import sleep
GPIO_MODE_PATH = "/sys/class/gpio/gpio{}/direction"
GPIO_PIN_PATH = "/sys/class/gpio/gpio{}/value"
ADC_PATH = os.path.normpath('/proc')
ADC_FILENAME = 'adc'
PWM_PATH = os.path.normpath('/dev/pwmtimer')
PWM_STOP_TIME_ADDRESS = 0x102
PWM_START_TIME_ADDRESS = 0x101
PWM_FREQ_ADDRESS = 0x107
PWMTMR_FUNC_ADDRESS = 0x103
PWMTMR_TONE_ADDRESS = 0x104
PWM_CONFIG_ADDRESS = 0x105
HWPWM_DUTY_ADDRESS = 0x106
MAX_PWMTIMER_FREQ = 2000
MIN_PWMTIMER_FREQ = 126
GPIO_FILNAME = 'gpio'

class BoardType:
    Rpi4 = 0
    Rpi3 = 1
    PcDuino = 2
    Debug = 3

boardType = BoardType.Rpi4
#BOARD_DEFINITION

if boardType == BoardType.Rpi4 or boardType == BoardType.Rpi3:
    import RPi.GPIO as RPI_GPIO
    RPI_GPIO.setmode(RPI_GPIO.BCM)


class GPIO:
    HIGH = 1
    LOW = 0
    INPUT = "in"
    OUTPUT = "out"
    INPUT_PULLUP = "8"
    A0 = 0
    A1 = 1
    A2 = 2
    A3 = 3
    A4 = 4
    A5 = 5

    def __init__(self):
        pass
        # self.pinModeList = [os.path.join(
        #     GPIO_MODE_PATH, 'gpio'+str(idx)) for idx in range(24)]
        # self.pinDataList = [os.path.join(
        #     GPIO_PIN_PATH, 'gpio'+str(idx)) for idx in range(24)]
        # self.adcFiles = [os.path.join(
        #     ADC_PATH, ADC_FILENAME+str(idx)) for idx in range(6)]

    def exportPin(self, pinNumber):
        export_path = "/sys/class/gpio/export"
        with open(export_path, 'r+') as file:
            file.write(pinNumber)

    def pinMode(self, pinNumber, mode):
        print("Initialized pin ", pinNumber)

        if boardType == BoardType.PcDuino:
            pin_path = GPIO_MODE_PATH.format(pinNumber)
            try:
                self.exportPin(pinNumber)
            except:
                print("Pin already exported")
            with open(pin_path, 'r+') as file:
                file.write(mode)
        elif boardType == BoardType.Rpi4:
            RPI_GPIO.setup(int(pinNumber), RPI_GPIO.OUT if mode ==
                           GPIO.OUTPUT else RPI_GPIO.IN)
        else:
            print("Initializing pin: ", pinNumber, " mode: ", mode)

    def digitalWrite(self, pinNumber, state):
        print("Output power", state)
        if boardType == BoardType.PcDuino:
            pin_path = GPIO_PIN_PATH.format(pinNumber)
            with open(pin_path, 'r+') as file:
                file.write(state)
        elif boardType == BoardType.Rpi4:
            RPI_GPIO.output(int(pinNumber), RPI_GPIO.HIGH if state ==
                            GPIO.HIGH else RPI_GPIO.LOW)
        else:
            print("Writing pin: ", pinNumber, " state: ", state)

    def digitalRead(self, pinNumber):
        if boardType == BoardType.PcDuino:
            pin_path = GPIO_PIN_PATH.format(pinNumber)
            with open(pin_path, 'r') as file:
                state = file.read().replace("\n", "").replace(" ", "").replace("\t", "")
            return state
        elif boardType == BoardType.Rpi4:
            return RPI_GPIO.input(pinNumber)
            # pin_path = GPIO_PIN_PATH.format(pinNumber)
            # with open(pin_path, 'r') as file:
            #     state = file.read().replace("\n", "").replace(" ", "").replace("\t", "")
            # return 0 if state == GPIO.LOW else 1
        else:
            print("Reading pin: ", pinNumber)

    def setPinFrequency(self, pinNumber, frequency):
        pwm_stop_struct = struct.pack('i', pinNumber)
        pwm_freq_struct = struct.pack('iiiI', pinNumber, 0, 0, frequency)
        if boardType == BoardType.PcDuino:
            with open(PWM_PATH, 'r+') as file:
                if pinNumber in [3, 9, 10, 11]:
                    ioctl(file, PWM_STOP_TIME_ADDRESS, pwm_stop_struct)
                ioctl(file, PWM_FREQ_ADDRESS, pwm_freq_struct)
        else:
            print("Setting frequency pin: ", pinNumber, " to: ", frequency)

    def analogWrite(self, pinNumber, value):
        pwm_config = struct.pack('ii', pinNumber, value)
        pwm_start_struct = struct.pack('i', 0)
        if boardType == BoardType.PcDuino:
            with open(PWM_PATH, 'r+') as file:
                if pinNumber in [3, 9, 10, 11]:
                    ioctl(file, PWM_CONFIG_ADDRESS, pwm_config)
                    ioctl(file, PWM_START_TIME_ADDRESS, pwm_start_struct)
                elif pinNumber in [5, 6]:
                    print("HW PWM {}".format(pinNumber))
                    ioctl(file, HWPWM_DUTY_ADDRESS, pwm_config)
        else:
            print("Writing analog pin: ", pinNumber, " value: ", value)

    def analogRead(self, pinNumber):
        if boardType == BoardType.PcDuino:
            with open(self.adcFiles[pinNumber], 'r') as file:
                value = file.read()
            realVal = int(value.split(":")[1])
            return realVal
        else:
            print("Reading analog pin: ", pinNumber)

gpio = GPIO()

class PinTest(tk.Label):
    def __init__(self, win, x, y, name, number, _type, mode, enabled):
        super(PinTest, self).__init__(win, background='#f23', text=name)
        self.win = win
        self.name = name
        self.number = number
        self.type = _type
        if type(mode) == type([]):
          self.mode = mode[0]["name"]
        else:
          self.mode = mode["name"]
        self.enabled = enabled
        self.x = x
        self.y = y
        self.place(x=x, y=y,height=24, width=100)
        self.init_components()
    
    def btn_pressed(self, event):
        gpio.digitalWrite(self.number, GPIO.HIGH)
        sleep(2)
        gpio.digitalWrite(self.number, GPIO.LOW)
        sleep(2)

    def digital_interrupt(self, channel=None):
        state = gpio.digitalRead(self.number)
        if state == GPIO.HIGH:
          self.config(text='{} {}'.format(self.name, ' ON'), background='#0f0')
        else:
          self.config(text='{} {}'.format(self.name, ' OFF'), background='#f00')

    def init_components(self):
        # self.label = tk.Label(self.win, text=self.name, width=100, height=30, background='#fff')
        if self.enabled:
          gpio.pinMode(self.number, GPIO.OUTPUT if self.mode == "Output" else GPIO.INPUT_PULLUP)
          if self.mode == "Output":
              self.bind('<ButtonPress-1>', self.btn_pressed)
          else:
              self.digital_interrupt()
            #   RPI_GPIO.add_event_detect(self.number, RPI_GPIO.BOTH, callback=self.digital_interrupt, bouncetime=100)
        else:
            self.config(background='#888888', fg='#555555')
        # self.label.place(x=self.x, y=self.y)
        # self.component_canvas = tk.Canvas(self.win, width=100, height=30)
        # self.component_canvas.place(x=0, y=0)
        # text_font = Font(self.component_canvas, family='Helvetica', size=12)
        # self.component = self.component_canvas.create_text(0, self.y, text=self.name, fill='#000', font=text_font)

class BoardTest(tk.Tk):
    def __init__(self, general_gpios, pins_defition):
        super(BoardTest, self).__init__()
        self.width = 640
        self.height = 640
        self.__geom = '{0}x{1}+0+0'.format(self.width, self.height)
        self.geometry(self.__geom)
        self.wm_title(string="GPIO Test")
        self.resizable(False, False)
        self.pins = {str(gpio['gpio']['number']): gpio for gpio in pins_defition}
        self.general_gpios = {str(idx): gpio for idx, gpio in enumerate(general_gpios)}
        self.init_components()
        tk.mainloop()

    def init_components(self):
        radius = 20
        back_color = '#464646'
        pin_numbers_display = tk.Canvas(self, width=100, height=526)
        pin_numbers_display.place(x=(self.width/2)-100/2, y=10)
        padding = 2
        btn_tl = pin_numbers_display.create_arc((padding, padding+radius, padding+radius, padding),
                                          start=90, extent=90, fill=back_color, style=tk.CHORD)
        btn_tr = pin_numbers_display.create_arc((100-padding-radius, padding, 100-padding,
                                           padding+radius), start=0, extent=90, fill=back_color, style=tk.CHORD)
        btn_br = pin_numbers_display.create_arc((100-padding, 526-radius -
                                           padding, 100-padding-radius,
                                           526-padding), start=270, extent=90, fill=back_color, style=tk.CHORD)
        btn_bl = pin_numbers_display.create_arc((padding+radius, 526 -
                                           padding,
                                           padding, 526-padding-radius), start=180, extent=90, fill=back_color, style=tk.CHORD)
        points = (
            #
            padding, padding+radius/2, padding+radius/2, padding,
            #
            100-padding-radius/2, padding, 100-padding/2,
            padding+radius/2,
            #
            100-padding, 526-radius/2 - \
            padding, 100-padding-radius/2,
            526-padding/2,
            #
            padding+radius/2, 526 -
            padding/2,
            padding, 526-padding-radius/2,
        )
        self.btn_center = pin_numbers_display.create_polygon(
            points,
            fill=back_color,
            # outline=self.border_color
        )
        pins_frame = tk.Frame(pin_numbers_display, width=100-11*2, height=526-11*2, background=back_color, highlightbackground=back_color, highlightcolor=back_color)
        pins_frame.place(x=11, y=11)
        pins_canvas = tk.Canvas(pins_frame, background=back_color,width=100-11*2, height=526-11*2)
        pins_canvas.place(x=0, y=0)
        white_rad = 10
        black_rad = 12
        y = int((526-11*2)/20)-10
        x = int((100-11*2)/4)
        text_font = Font(pins_canvas, family='Helvetica', size=10)
        left_pins_frame = tk.Frame(self, width=100, height=526)
        left_pins_frame.place(x=(self.width/2)-100/2-100, y=10)
        right_pins_frame = tk.Frame(self, width=100, height=526)
        right_pins_frame.place(x=(self.width/2)+100/2, y=10)
        pin_test_x = 0
        pin_test_y = 16
        for idx in range(1, 41):
            if idx % 2==1:
                x = int((100-11*2)/4)
                pins_canvas.create_oval(x - black_rad,
                                                y -
                                                black_rad,
                                                x +
                                                black_rad,
                                                y +
                                                black_rad,
                                                fill='#000')
                pins_canvas.create_oval(x - white_rad,
                                                y -
                                                white_rad,
                                                x +
                                                white_rad,
                                                y +
                                                white_rad,
                                                fill='#fff')
                pins_canvas.create_text(x, y, text=str(idx), fill='#000', font=text_font)
            else:
                x = int(100-(100-11*2)/2)
                pins_canvas.create_oval(x - black_rad,
                                                y -
                                                black_rad,
                                                x +
                                                black_rad,
                                                y +
                                                black_rad,
                                                fill='#000')
                pins_canvas.create_oval(x - white_rad,
                                                y -
                                                white_rad,
                                                x +
                                                white_rad,
                                                y +
                                                white_rad,
                                                fill='#fff')
                pins_canvas.create_text(x, y, text=str(idx), fill='#000', font=text_font)
                y += int((526-11*2)/20)
            pin_key = str(idx-1)
            user_pin = str(self.general_gpios[pin_key]['number'])
            if user_pin in self.pins.keys():
                if idx % 2==1:
                    PinTest(left_pins_frame, pin_test_x, pin_test_y,self.pins[user_pin]['name'], self.pins[user_pin]['gpio']['number'], self.pins[user_pin]['gpioType'], self.pins[user_pin]['gpioMode'], True)
                else:
                    PinTest(right_pins_frame, pin_test_x, pin_test_y,self.pins[user_pin]['name'], self.pins[user_pin]['gpio']['number'], self.pins[user_pin]['gpioType'], self.pins[user_pin]['gpioMode'], True)
                    
            else:
                # print(self.general_gpios[pin_key])
                if idx % 2==1:
                    PinTest(left_pins_frame, pin_test_x, pin_test_y,
                        self.general_gpios[pin_key]['name'], 
                        self.general_gpios[pin_key]['number'], 
                        self.general_gpios[pin_key]['types'], 
                        self.general_gpios[pin_key]['modes'], 
                        False)
                else:
                    PinTest(right_pins_frame, pin_test_x, pin_test_y,
                        self.general_gpios[pin_key]['name'], 
                        self.general_gpios[pin_key]['number'], 
                        self.general_gpios[pin_key]['types'], 
                        self.general_gpios[pin_key]['modes'], 
                        False)
            if idx %2==0:
                pin_test_y += 25


if __name__ == '__main__':
    with open('config.json', 'r') as file:
        data = json.load(file)
        BoardTest(data["boardGpios"], data["userGpios"])
