package fabrik.simulator.pic16f84;


import fabrik.simulator.pic16f84.frontendspecifics.FrontendSpecificToggleButtonGroup;
import fabrik.simulator.pic16f84.frontendspecifics.ToggleButtonGroup;

import fabrik.simulator.pic16f84.interfaces.*;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;

import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
import javafx.stage.FileChooser;

import java.io.File;
import java.io.IOException;

import java.io.BufferedReader;

import java.io.FileReader;

import javafx.scene.control.Label;
import javafx.scene.control.TableView;

import javafx.stage.Stage;
import javafx.util.Callback;


import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static java.lang.Math.max;

public class Controller_Frontend extends PICComponent implements FrontendControllerInterface, ExecutionTimeObserver {

    private int [] prog;
    private int [][] read;
    private int [] ind;
    private PICComponentLocator picComponents;

    private boolean isBreakpointReached = false;

    private boolean continueExecutionAfterBreakpoint = false;

    @FXML
    private ComboBox<String> executionTimeComboBox;


    @FXML
    private Button stepintoButton;

    @FXML
    private TableView<Table.DataEntry> tableView;

    @FXML
    private ListView<String> lstContentListView;

    @FXML
    private Button autoRunGUI;

    @FXML
    private Button stopButton;

    @FXML
    private Label totalExecutionTimeLabel;

    @FXML
    private CheckBox wdtCheck;



    public Controller_Frontend() {
        super();
        System.out.println(this);
    }





    //Refactoring

    public void stopRunFromBackend(String message) {
        ExecutionState.setAutoRunActive(false);

        handleSleepOrReset();
        showStopDialog(message);

    }
    private  void handleSleepOrReset() {
        if (ExecutionState.isSleeping()) {
            ExecutionState.wakeUp();
        } else {
            dataRegister.resetPC();
        }
    }

    private static void showStopDialog(String message) {
        Stage stoppedStage = new Stage();
        stoppedStage.setTitle("Programm unterbrochen!");

        VBox vbox = new VBox();
        vbox.setAlignment(javafx.geometry.Pos.CENTER);

        Label grundlabel = new Label("Grund: " + message);
        grundlabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");

        Label ueberlabel = new Label("Programm unterbrochen!");
        vbox.getChildren().addAll(ueberlabel, grundlabel);
        VBox.setMargin(grundlabel, new javafx.geometry.Insets(10, 10, 10, 10));

        Scene scene = new Scene(vbox, 300, 90);
        stoppedStage.setAlwaysOnTop(true);
        stoppedStage.setScene(scene);
        stoppedStage.show();
    }



    //Refactoring Ende



    @FXML
    private void stopAutoRun(ActionEvent event) {
        ExecutionState.setAutoRunActive(false);
    }


    @FXML
    public void autoRunGUI(ActionEvent event) {
        if (!ExecutionState.isAutoRunActive()) {
            ExecutionState.setAutoRunActive(true) ;
        }

        Thread autoRunThread = new Thread(() -> {
            try {
                while (dataRegister.getPC() < prog.length && ExecutionState.isAutoRunActive()){

                    Platform.runLater(() -> {
                        try {
                            stepintoButton(null);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    });
                    Thread.sleep(80); //Verzögerungszeit in Millisekunden
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        autoRunThread.setDaemon(true);
        autoRunThread.start();
    }



    private void updateExecutionTimeMultiplier() {
        String selectedOption = executionTimeComboBox.getValue();
        executionTime.setExecutionTimeMultiplier(selectedOption);
    }

    @FXML
    public void stepintoButton(ActionEvent event) throws IOException {
        if (lstContentListView.getItems().isEmpty()) {
            // Datei ist nicht geladen oder leer
            return;
        }

        int currentIndex;
        // Aktuelle Zeile abrufen
        if (!ExecutionState.isSleeping())
            currentIndex = ind[dataRegister.getPC()];
        else
            currentIndex = ind[dataRegister.getPC()]-1;

        // Überprüfung ob ein Breakpoint gesetzt ist testte

        if (breakpoints.contains(currentIndex)) {
            if (!continueExecutionAfterBreakpoint){
                isBreakpointReached = true;
                stopAutoRun(null);
                System.out.println("Breakpoint erreicht bei Zeile: " + currentIndex);
                continueExecutionAfterBreakpoint = true;
                return;
            }
        }
        else {
            isBreakpointReached = false;
            continueExecutionAfterBreakpoint = false;
        }

        // Scrollen zur ausgewählten Zeile
        lstContentListView.scrollTo(currentIndex -2);

        // Zeile auswählen
        lstContentListView.getSelectionModel().clearSelection();
        lstContentListView.getSelectionModel().select(currentIndex);

        String selectedRowStyle;
        if (!ExecutionState.isSleeping())
            selectedRowStyle = "-fx-background-color: red; -fx-text-fill: white;";
        else
            selectedRowStyle = "-fx-background-color: teal; -fx-text-fill: white;";

        markSelectedRow(currentIndex, selectedRowStyle);

        if (!ExecutionState.isSleeping()) {
            commands.decode(prog[dataRegister.getPC()]);
            dataRegister.increasePC();
        }
        else {
            commands.decode(0);
        }
        watchdogTimer.testAndTrigger();
        table.refresh();
        CreateWindow.refreshTable();
        ioPorts.refreshUI(getTRISbuttons(), getPORTbuttons());
        updateStack();
        updateWatchdog();
    }


    private void markSelectedRow(int currentIndex, String selectedRowStyle) {
        lstContentListView.setCellFactory(column -> new ListCell<String>() {
            @Override
            protected void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                setText(item);
                if (getIndex() == currentIndex) {
                    setStyle(selectedRowStyle);
                } else {
                    setStyle("");
                }
                CheckBox breakpointCheckBox = new CheckBox();
                breakpointCheckBox.setOnAction(event -> {
                    int index = getIndex();
                    if (breakpointCheckBox.isSelected()) {
                        breakpoints.add(index);
                    } else {
                        breakpoints.remove(index);
                    }
                });
                breakpointCheckBox.setSelected(breakpoints.contains(getIndex()));
                setGraphic(breakpointCheckBox);
            }
        });
    }

    private Set<Integer> breakpoints = new HashSet<>();



    @FXML
    protected void selectFileLST(ActionEvent event) throws IOException {
        File selectedFile = chooseLSTFile();
        stopAutoRun(null);
        if(selectedFile != null){
            String fileAddress = selectedFile.getAbsolutePath();
            System.out.println(dataRegister);
            if (null == dataRegister) dataRegister = (DataRegisterInterface) picComponents.getComponent(DataRegisterInterface.class);
            dataRegister.initDataRegister();
            dataRegister.resetPC();
            toggleLEDs(null);
            ioPorts.reset();

            for (ToggleButtonGroup toggleButtonGroup : allPORTbuttons) {
                try {
                    toggleButtonGroup.getToggles().get(0).setSelected(true);
                    toggleButtonGroup.getToggles().get(1).setSelected(false);
                    ioPorts.setPORTfromUI(toggleButtonGroup);
                } catch (NullPointerException ignored) {}
            }
            programStack.reset();
            ExecutionState.wakeUp();
            breakpoints.clear();
            ioPorts.refreshUI(getTRISbuttons(), getPORTbuttons());
            executionTime.resetTotalExecutionTime();
            watchdogTimer.reset();
            wdtCheck.setSelected(false);
            table.refresh();
            read = ParseFile.parseDatei(fileAddress);
            prog = read [0];
            ind = read[1];
            updateStack();
            System.out.println(Arrays.toString(Arrays.stream(prog).toArray()));
            displayLSTFileContent(selectedFile);
            markSelectedRow(0, "-fx-background-color: red; -fx-text-fill: white;");
            initializeBreakpoints();
            int lastSlash = max (fileAddress.lastIndexOf('/'), fileAddress.lastIndexOf('\\'));
            int lastDot = fileAddress.lastIndexOf('.');
            String stageTitle;
            if (lastDot > lastSlash)
                stageTitle = "Simulator － " + fileAddress.substring(lastSlash + 1, lastDot);
            else {
                stageTitle = "Simulator － " + fileAddress.substring(lastSlash + 1);
            }
            Stage stage = (Stage) stepintoButton.getScene().getWindow();
            stage.setTitle(stageTitle);
        }
    }

    private void initializeBreakpoints() {
        lstContentListView.setCellFactory(createBreakpointCell());
    }

    private boolean checkBreakpoint(int pc) {
        // Hier wird überprüft, ob ein Breakpoint am angegebenen Programmzähler gesetzt ist
        return breakpoints.contains(pc);
    }


    private Callback<ListView<String>, ListCell<String>> createBreakpointCell() {
        return listView -> new ListCell<String>() {
            private final CheckBox breakpointCheckBox = new CheckBox();
            {
                breakpointCheckBox.setOnAction(event -> {
                    int index = getIndex();
                    if (breakpointCheckBox.isSelected()) {
                        breakpoints.add(index);
                    } else {
                        breakpoints.remove(index);
                    }
                });
            }

            @Override
            protected void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                if (empty || item == null) {
                    setGraphic(null);
                } else {
                    setText(item);
                    setGraphic(breakpointCheckBox);
                    breakpointCheckBox.setSelected(breakpoints.contains(getIndex()));
                }
            }
        };
    }

    @FXML
    protected void loadLSTFileContent(String fileContent) {
        ObservableList<String> items = FXCollections.observableArrayList(fileContent.split("\n"));
        lstContentListView.setItems(items);
    }


    private File chooseLSTFile() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("LST Files", "*.lst", "*.LST"),
                new FileChooser.ExtensionFilter("All Files", "*.*"));
        return fileChooser.showOpenDialog(null);
    }

    @FXML
    private void displayLSTFileContent(File selectedFile) {
        try (BufferedReader reader = new BufferedReader(new FileReader(selectedFile))) {
            ObservableList<String> contentList = FXCollections.observableArrayList();
            String line;
            while ((line = reader.readLine()) != null) {
                contentList.add(line);
            }
            lstContentListView.setItems(contentList);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @FXML
    public void toggleWatchdog(ActionEvent actionEvent) {
        if (wdtCheck.isSelected()){
            watchdogTimer.enable();

        }
        else{
            watchdogTimer.disable();
       }
    }

    @FXML
    private ToggleButtonGroup bgTRISA0;
    @FXML
    private ToggleButtonGroup bgTRISA1;
    @FXML
    private ToggleButtonGroup bgTRISA2;
    @FXML
    private ToggleButtonGroup bgTRISA3;
    @FXML
    private ToggleButtonGroup bgTRISA4;
    @FXML
    private ToggleButtonGroup bgTRISB0;
    @FXML
    private ToggleButtonGroup bgTRISB1;
    @FXML
    private ToggleButtonGroup bgTRISB2;
    @FXML
    private ToggleButtonGroup bgTRISB3;
    @FXML
    private ToggleButtonGroup bgTRISB4;
    @FXML
    private ToggleButtonGroup bgTRISB5;
    @FXML
    private ToggleButtonGroup bgTRISB6;
    @FXML
    private ToggleButtonGroup bgTRISB7;
    @FXML
    private ToggleButtonGroup bgPORTA0;
    @FXML
    private ToggleButtonGroup bgPORTA1;
    @FXML
    private ToggleButtonGroup bgPORTA2;
    @FXML
    private ToggleButtonGroup bgPORTA3;
    @FXML
    private ToggleButtonGroup bgPORTA4;
    @FXML
    private ToggleButtonGroup bgPORTB0;
    @FXML
    private ToggleButtonGroup bgPORTB1;
    @FXML
    private ToggleButtonGroup bgPORTB2;
    @FXML
    private ToggleButtonGroup bgPORTB3;
    @FXML
    private ToggleButtonGroup bgPORTB4;
    @FXML
    private ToggleButtonGroup bgPORTB5;
    @FXML
    private ToggleButtonGroup bgPORTB6;
    @FXML
    private ToggleButtonGroup bgPORTB7;

    private static ToggleButtonGroup[] allTRISbuttons;
    private static ToggleButtonGroup[] allPORTbuttons;
    private static Circle[] allLEDsA;
    private static Circle[] allLEDsB;
    private static ToggleButtonGroup dummy = new ToggleButtonGroup(new ToggleButton(), new ToggleButton());

    public void initialize() {
        ToggleButtonGroup [] allTRISButtons = {bgTRISA0, bgTRISA1, bgTRISA2, bgTRISA3, bgTRISA4, dummy, dummy, dummy, bgTRISB0, bgTRISB1, bgTRISB2, bgTRISB3, bgTRISB4, bgTRISB5, bgTRISB6, bgTRISB7};
        ToggleButtonGroup [] allPORTButtons = {bgPORTA0, bgPORTA1, bgPORTA2, bgPORTA3, bgPORTA4, dummy, dummy, dummy, bgPORTB0, bgPORTB1, bgPORTB2, bgPORTB3, bgPORTB4, bgPORTB5, bgPORTB6, bgPORTB7};
        Circle[] allLEDsA = {ledA0, ledA1, ledA2, ledA3, ledA4};
        Circle[] allLEDsB = {ledB0, ledB1, ledB2, ledB3, ledB4, ledB5, ledB6, ledB7};
        for (int i = 0; i<allPORTButtons.length; i++) {
            allTRISButtons[i].getToggles().get(0).setSelected(true);
            allPORTButtons[i].getToggles().get(0).setSelected(true);
            allTRISButtons[i].getToggles().get(1).setSelected(false);
            allPORTButtons[i].getToggles().get(1).setSelected(false);
            toggleButtonExt.addAlwaysOneSelectedSupport(allTRISButtons[i]);
            toggleButtonExt.addAlwaysOneSelectedSupport(allPORTButtons[i]);
        }

        ledCheckA.setSelected(false);
        ledCheckB.setSelected(false);
        setTRISbuttons(allTRISButtons);
        setPORTbuttons(allPORTButtons);
        ioPorts.setLEDs(allLEDsA, allLEDsB);

        lstContentListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        autoRunGUI.setOnAction(this::autoRunGUI);
        stopButton.setOnAction(this::stopAutoRun);

        initializeBreakpoints();
        executionTimeComboBox.setItems(FXCollections.observableArrayList("8 MHZ", "4 MHZ", "1 MHZ", "500 HZ", "100 HZ", "32 HZ"));
        executionTimeComboBox.setValue("4 MHZ");
        executionTimeComboBox.setOnAction(event -> updateExecutionTimeMultiplier());
    }



    private void setTRISbuttons(ToggleButtonGroup[] allButtons) {
        allTRISbuttons = allButtons;
    }

    public FrontendSpecificToggleButtonGroup [] getTRISbuttons() {
        return allTRISbuttons;
    }

    private void setPORTbuttons(ToggleButtonGroup[] allButtons) {
        allPORTbuttons = allButtons;
    }

    public ToggleButtonGroup [] getPORTbuttons() {
        return allPORTbuttons;
    }

    public Circle[] getLEDsA() {
        return allLEDsA;
    }

    public Circle[] getLEDsB() {
        return allLEDsB;
    }


    @FXML
    private CheckBox ledCheckA;
    @FXML
    private CheckBox ledCheckB;
    @FXML
    private Circle ledA0;
    @FXML
    private Circle ledA1;
    @FXML
    private Circle ledA2;
    @FXML
    private Circle ledA3;
    @FXML
    private Circle ledA4;
    @FXML
    private Circle ledB0;
    @FXML
    private Circle ledB1;
    @FXML
    private Circle ledB2;
    @FXML
    private Circle ledB3;
    @FXML
    private Circle ledB4;
    @FXML
    private Circle ledB5;
    @FXML
    private Circle ledB6;
    @FXML
    private Circle ledB7;

    @FXML
    public void toggleLEDs (ActionEvent actionEvent) {
        ioPorts.setLEDs(new boolean[]{ledCheckA.isSelected(), ledCheckB.isSelected()});
    }

    @FXML
    private Label stackIndex;

    @FXML
    private ListView<String> stackVisual;

    private static String format (String s) {
        return (s.length() == 1) ? "0" + s : s;
    }

    private void updateStack (){
        stackIndex.setText("Stack-Pointer: " + programStack.getStackPointer());
        ObservableList<String> observedList = FXCollections.observableArrayList();
        List<Integer> stackList = programStack.getStack();
        for (Integer integer : stackList) {
            observedList.add("0x" + format(Integer.toHexString(integer).toUpperCase()));
        }
        stackVisual.setItems(observedList);
    }

    private void updateWatchdog (){
        wdtCheck.setText("Watchdog-Timer: " + watchdogTimer.get() + "µs");
    }

    @Override
    public void initialize(PICComponentLocator locator) {
        super.initialize(locator);
        executionTime.registerObserver(this);
        this.picComponents = locator;
        System.out.println("Frontend");
    }

    @Override
    public void executionTimeChanged() {
        totalExecutionTimeLabel.setText("Total Execution Time: " + executionTime.getTotalExecutionTime() + "µs");
    }
}

