package fabrik.simulator.pic16f84;

import fabrik.simulator.pic16f84.frontendspecifics.FrontendSpecificToggleButtonGroup;
import fabrik.simulator.pic16f84.frontendspecifics.ToggleButtonGroup;
import fabrik.simulator.pic16f84.interfaces.*;
import javafx.collections.ObservableList;
import javafx.scene.control.ToggleButton;
import javafx.scene.paint.Color;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;

import java.io.IOException;

public class IOPorts extends PICComponent implements IOPortInterface {
    private final int A = 0;
    private final int B = 1;
    private final int PORTA = 5;
    private final int PORTB = 6;
    private final int TRISA = 0x85;
    private final int TRISB = 0x86;

    private int [] trisLatch = {0xFF, 0xFF};
    private int [] dataLatch = new int[2];
    private boolean isLEDenabledA = false;
    private boolean isLEDenabledB = false;
    private Circle[] allLEDsA;
    private Circle[] allLEDsB;


    private  int RB4 = 0x10;
    private  int RB5 = 0x20;
    private  int RB6 = 0x40;
    private  int RB7 = 0x80;

    public IOPorts(){
        super();
    }

    public void setBit (int address, int bit){
        if (address < 7) {
            dataLatch[address - PORTA] |= (1 << bit);
        }
        else{
            trisLatch[address - TRISA] |= (1 << bit);
        }
        refreshPorts();
    }

    public void clearBit(int address, int bit) {
        if (address < 7) {
            if (((dataLatch[address-PORTA] >> bit)&1) == 1){
                dataLatch[address-PORTA] -= (int) Math.pow(2, bit);
            }
        }
        else{
            if (((trisLatch[address-TRISA] >> bit)&1) == 1){
                trisLatch[address-TRISA] -= (int) Math.pow(2, bit);
            }
        }
        refreshPorts();
    }

    public void setRegister(int address, int content) {
        if (address < 7) {
            dataLatch[address - PORTA] = content;
        }
        else{
            trisLatch[address - TRISA] = content;
        }
        refreshPorts();
    }

    private void refreshPorts() {
        dataRegister.setDirectRegister(PORTA, ((~((~dataLatch[A])&0x1F | trisLatch[A])) | (trisLatch[A] & dataRegister.getDirectRegister(PORTA))) & 0xFF);
        dataRegister.setDirectRegister(PORTB, ((~((~dataLatch[B])&0xFF | trisLatch[B])) | (trisLatch[B] & dataRegister.getDirectRegister(PORTB))) & 0xFF);
        dataRegister.setDirectRegister(TRISA, trisLatch[A]);
        dataRegister.setDirectRegister(TRISB, trisLatch[B]);
        if (((trisLatch[A] >> 4) & 1 )== 1)
            timer.incrementFromPin(dataRegister.getDirectRegister(PORTA));
        ToggleButtonGroup[] buttons = (ToggleButtonGroup[]) frontendController.getPORTbuttons();
        for (int i = 0; i < buttons.length; i++){
            int port = (i < 8) ? PORTA : PORTB;
            int bit = i % 8;
            boolean value = ((dataRegister.getDirectRegister(port) >> bit) & 1) == 1;
            buttons[i].getToggles().get(0).setSelected(!value);
            buttons[i].getToggles().get(1).setSelected(value);
        }
    }

    public void refreshUI(ToggleButtonGroup[] buttonsTRIS, ToggleButtonGroup[] buttonsPORT) {
        for (int i = 0; i< buttonsTRIS.length; i++){
            int tris = (i < 8) ? trisLatch[A] : trisLatch[B];
            boolean val = isInput(tris, i%8);
            buttonsPORT[i].setDisable(!val);
            buttonsTRIS[i].getToggles().get(0).setSelected(val);
            buttonsTRIS[i].getToggles().get(1).setSelected(!val);
            updateLEDs(true, i, val);
        }
    }

    private void updateLEDs(boolean updateAll, int index, boolean val) {
        if (updateAll) {
            if (index < 8) {
                if (isLEDenabledA && !val) {
                    if (dataRegister.getDirectBit(PORTA, index) == 1)
                        allLEDsA[index].setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.7, true, null, new Stop(0, Color.BLACK), new Stop(1, Color.DARKGRAY)));
                    else
                        allLEDsA[index].setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.7, true, null, new Stop(0, Color.BLACK), new Stop(1, Color.DARKGRAY)));
                }
            } else {
                if (isLEDenabledB && !val) {
                    if (dataRegister.getDirectBit(PORTB, index - 8) == 1)
                        allLEDsB[index - 8].setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.7, true, null, new Stop(0, Color.RED), new Stop(1, Color.DARKGRAY)));
                    else
                        allLEDsB[index - 8].setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.7, true, null, new Stop(0, Color.BLACK), new Stop(1, Color.DARKGRAY)));
                }
            }
        }
        else {
            if (index < 8)
                if (val || dataRegister.getDirectBit(PORTA, index) == 0)
                    allLEDsA[index].setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.7, true, null, new Stop(0, Color.BLACK), new Stop(1, Color.DARKGRAY)));
                else
                    allLEDsA[index].setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.7, true, null, new Stop(0, Color.RED), new Stop(1, Color.DARKGRAY)));
            else
            if (val || dataRegister.getDirectBit(PORTB, index - 8) == 0)
                allLEDsB[index - 8].setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.7, true, null, new Stop(0, Color.BLACK), new Stop(1, Color.DARKGRAY)));
            else
                allLEDsB[index - 8].setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.7, true, null, new Stop(0, Color.RED), new Stop(1, Color.DARKGRAY)));
        }
    }

    private boolean isInput(int i, int bit) {
        return (i >> bit & 1) == 1;
    }

    public void setTRISfromUI(ToggleButtonGroup parent) throws IOException {
        int [] params = getToggleParams(parent);
        int tris = params[0];
        int bit = params[1];
        int value = params[2];
        ToggleButtonGroup [] buttonsPORT = (ToggleButtonGroup[]) frontendController.getPORTbuttons();
        if (value == 1){
            setBit(tris, bit);
            buttonsPORT[(tris-TRISA)*8 + bit].setDisable(false);
        }
        else{
            clearBit(tris, bit);
            buttonsPORT[(tris-TRISA)*8 + bit].setDisable(true);
        }
        updateLEDs(false, (tris-TRISA)*8 + bit, value == 1);
        refreshPorts();
        refreshTable(parent);
    }

    public void setPORTfromUI(ToggleButtonGroup parent) throws IOException {
        int [] params = getToggleParams(parent);
        int port = params[0];
        int bit = params[1];
        int value = params [2];
        value = (value == 1) ? 0 : 1;
        int oldValue = dataRegister.getDirectBit(port, bit);
        dataRegister.setDirectBit(port, bit, value);
        refreshPorts();
        refreshTable(parent);
        if (port == PORTB && bit >= 4)
            interrupts.triggerRBInterrupt(oldValue, value);
        else if (port == PORTB && bit == 0)
            interrupts.triggerRB0Interrupt(oldValue, value);
    }

    public void setLEDs (boolean[] leds) {
        boolean isAnowDisabled = isLEDenabledA && !leds[0];
        isLEDenabledA = leds[0];
        boolean isBnowDisabled = isLEDenabledB && !leds[1];
        isLEDenabledB = leds[1];
        if (isAnowDisabled){
            for (Circle circle : allLEDsA) {
                circle.setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.7, true, null, new Stop(0, Color.BLACK), new Stop(1, Color.DARKGRAY)));
            }
        }
        if (isBnowDisabled){
            for (Circle circle : allLEDsB) {
                circle.setFill(new RadialGradient(0, 0, 0.5, 0.5, 0.7, true, null, new Stop(0, Color.BLACK), new Stop(1, Color.DARKGRAY)));
            }
        }
    }

    public void setLEDs (Circle[] a, Circle[] b){
        allLEDsA = a;
        allLEDsB = b;
    }

    public void refreshTable(ToggleButtonGroup parent) {
        table.refresh();
        CreateWindow.refreshTable();
    }

    private int[] getToggleParams(ToggleButtonGroup parent) {
        String group = parent.getId();
        ObservableList<ToggleButton> toggles = parent.getToggles();
        int fileAddress;
        if (group.contains("TRIS"))
            if (group.contains("A"))
                fileAddress = TRISA;
            else
                fileAddress = TRISB;
        else
        if (group.contains("A"))
            fileAddress = PORTA;
        else
            fileAddress = PORTB;
        char index =group.charAt(group.length()-1);
        int bit = Integer.parseInt(String.valueOf(index));
        int value = (toggles.get(0).isSelected()) ? 1 : 0;
        return new int[]{fileAddress, bit, value};
    }

    public void reset() {
        trisLatch = new int[]{0xFF, 0xFF};
        dataLatch = new int[2];
        refreshPorts();
    }

    public void resetTRIS (){ // Only Backend
        trisLatch = new int[] {0xFF, 0xFF};
    }

    @Override
    public void initialize(PICComponentLocator locator) {
        super.initialize(locator);
        System.out.println("IOPorts.\n");
    }

    @Override
    public void setPORTfromUI(FrontendSpecificToggleButtonGroup parent) throws IOException {
        setPORTfromUI((ToggleButtonGroup) parent);
    }

    @Override
    public void setTRISfromUI(FrontendSpecificToggleButtonGroup parent) throws IOException {
        setTRISfromUI((ToggleButtonGroup) parent);
    }

    @Override
    public void refreshUI(FrontendSpecificToggleButtonGroup[] trisButtons,
            FrontendSpecificToggleButtonGroup[] portButtons) {
        refreshUI((ToggleButtonGroup[]) trisButtons, (ToggleButtonGroup[]) portButtons);
    }
}
