package control;

import imp.*;
import graph.*;
import algorithmen.*;

import javafx.fxml.*;
import javafx.scene.control.*;
import javafx.event.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.text.*;
import javafx.geometry.Pos;
import javafx.scene.image.Image;
import javafx.stage.*;          // Dateiöffnen / Speichern-Dialog
import javafx.application.Platform;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import java.io.File;  
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.file.*;
import java.util.Collections;
import java.util.stream.Stream;
import java.util.Iterator;
import java.util.List;

import java.util.ArrayList;
import java.util.regex.Pattern;
import java.lang.reflect.InvocationTargetException;

import javafx.collections.ObservableList;

/**
 * Die Klasse SimulationTabMitController stellt einen Tab inclusive ihres Controllers
 * zur Simulation eines Algorithmus dar. Der Algorithmus kann ausgewählt und schrittweise 
 * durchgeführt werden.
 *
 * @author Thomas Schaller
 * @version 12.02.2025 (v7.4)
 * v7.4: Unterbrechen eines Algorithmus neu geregelt.
 * v7.1: Fehler bei Aktualisierung des Hilfefensters behoben, Splitpane statt HBox
 * v7.0: Mechanismus geändert, so dass die init-Methode des Algorithmus beim Wechesel eines Algorithmus 
 *       aufgerufen wird, um die für diesen Algorithmus passenden Anzeigeeinstellungen zu setzen.
 * v6.9: Hilfefenster ist in Simulationsfenster integriert
 */
public class SimulationTabMitController extends TabMitController implements Hilfe {

    @FXML
    private ComboBox<String> cbAlgorithmen;

    @FXML
    private Slider sSpeed;

    @FXML
    private Button bStep;

    @FXML
    private Button bStart;

    @FXML
    private Button bBreak;

    @FXML
    private Button bReset;

    private GraphAlgo aktAlgo = null;
    private ArrayList<String> algoNamen;
    private Hilfe hilfe;

    public SimulationTabMitController(Graph graph, GraphOptions options) {
        this.graph = graph;
        this.options = options;

    }

    public void initialize() {
        this.algoNamen = new ArrayList<String>();

        File verzeichnis = new File("algorithmen");
        if(verzeichnis != null && verzeichnis.isDirectory()) {
            String[] entries = verzeichnis.list();
            for (String name : entries) {
                if (name.startsWith("GraphAlgo_") && name.endsWith(".class")){
                    try{
                        Class c = Class.forName("algorithmen."+name.split(Pattern.quote("."))[0]);
                        GraphAlgo a = ((GraphAlgo)(c).getDeclaredConstructor().newInstance());       
                        int i = 0;
                        while(i < cbAlgorithmen.getItems().size() && cbAlgorithmen.getItems().get(i).compareTo(a.getBezeichnung())<0)
                            i++;
                        //System.out.println("Algorithmus: "+a.getBezeichnung()+" geladen.");     
                        cbAlgorithmen.getItems().add(i, a.getBezeichnung());

                        algoNamen.add(i, "algorithmen."+name.split(Pattern.quote("."))[0]);
                    }
                    catch(ExceptionInInitializerError  e)  {}
                    catch(LinkageError e){}   
                    catch(ClassNotFoundException e) {}
                    catch(NoSuchMethodException e)   {}
                    catch(InstantiationException e)   {}
                    catch(IllegalAccessException e)   {}
                    catch(InvocationTargetException e)   {}

                }
            } // end of for
        }
        verzeichnis = new File( "eigeneAlgorithmen" );
        if(verzeichnis != null  && verzeichnis.isDirectory()) {
            String[] entries = verzeichnis.list();
            for (String name : entries) {
                if (name.startsWith("GraphAlgo_") && name.endsWith(".class")){
                    try{
                        Class c = Class.forName("eigeneAlgorithmen."+name.split(Pattern.quote("."))[0]);
                        GraphAlgo a = ((GraphAlgo)(c).getDeclaredConstructor().newInstance());       
                        int i = 0;
                        while(i < cbAlgorithmen.getItems().size() && cbAlgorithmen.getItems().get(i).compareTo(a.getBezeichnung())<0)
                            i++;
                        //System.out.println("Algorithmus: "+a.getBezeichnung()+" geladen.");     
                        cbAlgorithmen.getItems().add(i, a.getBezeichnung());

                        algoNamen.add(i, "eigeneAlgorithmen."+name.split(Pattern.quote("."))[0]);
                    }
                    catch(ExceptionInInitializerError  e)  {}
                    catch(LinkageError e){}   
                    catch(ClassNotFoundException e) {}
                    catch(NoSuchMethodException e)   {}
                    catch(InstantiationException e)   {}
                    catch(IllegalAccessException e)   {}
                    catch(InvocationTargetException e)   {}

                }
            } // end of for
        }

        cbAlgorithmen.getSelectionModel().selectedItemProperty().addListener((options, oldValue, newValue) -> {
                changeAlgorithm();
            }); 

        viewer.setGraph(graph,options);
        this.hilfe = null;
        this.aktAlgo = null;
        super.initialize();

        sSpeed.valueProperty().addListener(e -> { if (aktAlgo != null) aktAlgo.setSpeed(910-(int) (sSpeed.getValue()));});
        bStart.managedProperty().bind(bStart.visibleProperty());
        bBreak.managedProperty().bind(bBreak.visibleProperty());
        bBreak.setVisible(false);

        //------------- Hilfefunktion
        loescheAlles();
        zustaende = new ArrayList<List<String>>();
        aktuell = null;
        reviewAllowed = false;
        tvAblauf.getSelectionModel().selectedIndexProperty().addListener((obs,oldValue, newValue)->showState());

    }

    public void showHilfe(boolean b) {
        if(b) {
            lAblauf.setVisible(true);
            tvAblauf.setVisible(true);
            bClipboard.setVisible(true);

            hilfe = this;
            if(aktAlgo != null ) {
                aktAlgo.setGUIElemente(viewer,hilfe); 
                if(aktAlgo.isAlive())
                    hilfe.append("Unvollständiger Ablauf");
            }
        }
        else {
            lAblauf.setVisible(false);
            tvAblauf.setVisible(false);
            bClipboard.setVisible(false);
            if(aktAlgo != null && aktAlgo.isAlive()) aktAlgo.setGUIElemente(viewer, null);
            hilfe = null;
            loescheAlles();
            zustaende = new ArrayList<List<String>>();
            aktuell = null;
            reviewAllowed = false;          
        }
    }

    public void setGraph(Graph graph, GraphOptions options) {
        super.setGraph(graph,options);
        mReset(null);
    }

    public void changeAlgorithm() {
        if(aktAlgo != null && aktAlgo.isAlive() && !aktAlgo.isInterrupted()) aktAlgo.interrupt();  
        graph.initialisiereAlleKnoten();
        graph.initialisiereAlleKanten();

        bStep.setDisable(false);
        bStart.setDisable(false);
        bStart.setVisible(true);
        bBreak.setVisible(false);
        if (hilfe != null) hilfe.loescheAlles();
        this.aktAlgo = null;

        if(cbAlgorithmen.getSelectionModel().getSelectedIndex() >= 0) {

            try{
                ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
                MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
                Class c = classLoader.loadClass(algoNamen.get(cbAlgorithmen.getSelectionModel().getSelectedIndex()));

                aktAlgo = ((GraphAlgo)(c).getDeclaredConstructor().newInstance()); 
                aktAlgo.setStartKnoten(viewer.getSelectedKnoten());
                aktAlgo.setGUIElemente(viewer, hilfe);
                aktAlgo.setSpeed(910-(int) (sSpeed.getValue()));
                aktAlgo.init();
            } catch( Exception e) {
                System.out.println(e);
            }
        }

        update();

    }

    @FXML
    void mReset(ActionEvent event) {

        changeAlgorithm();
    }

    @FXML 
    void mStep(ActionEvent event) {
        if (aktAlgo == null) return;
        if (aktAlgo.getState() == Thread.State.NEW ) {
            aktAlgo.setStartKnoten(viewer.getSelectedKnoten());
            aktAlgo.start();
        } else {
            if(aktAlgo.isAlive()) {
                aktAlgo.setSpeed(910-(int) (sSpeed.getValue()));
                aktAlgo.setWaitforclick(false);
            } else {
                bStep.setDisable(true);
                bStart.setDisable(true);
                bBreak.setDisable(true);
            } // end of if-else
        } // end of if-else
        try{
            Thread.sleep(100);
        } catch(Exception e) {}

        if (!aktAlgo.isAlive()) {
            bStep.setDisable(true);
            bStart.setDisable(true);
            bBreak.setDisable(true);
        }
    }

    @FXML
    void mStart(ActionEvent event) {
        if (aktAlgo == null) return;
        if (aktAlgo.getState() == Thread.State.NEW ) {
            aktAlgo.setStartKnoten(viewer.getSelectedKnoten());
            aktAlgo.setStepping(false);
            aktAlgo.start();
        } else {
            if(aktAlgo.isAlive()) {
                aktAlgo.setSpeed(910-(int) (sSpeed.getValue()));
                aktAlgo.setStepping(false);
                aktAlgo.setWaitforclick(false);
            } 
        } // end of if-else

        bStep.setDisable(true);
        bStart.setVisible(false);
        bBreak.setVisible(true);
        bBreak.setDisable(false);

    }

    public void mBreak(ActionEvent event) {
        if(aktAlgo != null && aktAlgo.isAlive()) {
            aktAlgo.setStepping(true);
            bStart.setVisible(true);
            bBreak.setVisible(false);
            bStep.setDisable(false);
        }
    }

    // --------- Hilfefenster --------------------------------------------
    @FXML
    private TreeView<String> tvAblauf;

    @FXML
    private Label lAblauf;

    @FXML
    private Button bClipboard;

    private List<TreeItem<String>> stufen;
    private List<List<String>> zustaende;
    private TreeItem<String> last;
    private GraphPlotter gp;
    private List<String> aktuell;
    private boolean reviewAllowed;

    public void setGraphPlotter(GraphPlotter gp) {
        this.gp = gp;
    }

    public void loescheAlles() {
        Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    stufen = new ArrayList<TreeItem<String>>();
                    zustaende = new ArrayList<List<String>>();
                    TreeItem<String> root =  new TreeItem<String>("Algorithmus");
                    root.setExpanded(true);
                    last = root;
                    tvAblauf.setRoot(root);
                    tvAblauf.setShowRoot(false);
                    stufen.add(root);                    
                }
            });

    }

    public void append(String text) {
        List<String> status = gp.getGraph().getStatus();
        Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    last = new TreeItem<String>(text);
                    stufen.get(stufen.size()-1).getChildren().add(last);
                    zustaende.add(status);
                }
            });

    }

    public void indentMore() {
        Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    if(stufen.size() == 1) { // Hauptknoten
                        TreeItem parent = stufen.get(0);
                        List<TreeItem> children = parent.getChildren();
                        for(int i=children.size()-1; i >= 0; i--) {
                            TreeItem t = children.get(i);
                            if(t.isExpanded()) {
                                t.setExpanded(false);
                                break;
                            }
                        }
                    }
                    stufen.add(last);
                    last.setExpanded(true);
                    last.expandedProperty().addListener((b, o, n) -> showState());
                }
            });

    }

    public void indentLess() {
        Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    if(stufen.size() > 1) stufen.remove(stufen.size()-1);
                }
            });
    }

    public void setReviewAllowed(boolean a) {
        this.reviewAllowed = a;
        if(!reviewAllowed) tvAblauf.getSelectionModel().clearSelection();
    }

    public void showState() {
        Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    if(reviewAllowed && tvAblauf.getSelectionModel().getSelectedIndex()>=0) {
                        TreeItem s = tvAblauf.getSelectionModel().getSelectedItem();
                        if(!s.isExpanded()) { // suche das letzte Kind
                            while(s.getChildren().size()>0){
                                List<TreeItem> c = s.getChildren();
                                s = c.get(c.size()-1);
                            }
                        }
                        gp.getGraph().setStatus(zustaende.get(calculateIndex(tvAblauf.getRoot(), s ,0)-1));
                        gp.updateImage();
                    }
                }
            });

    }

    private int calculateIndex(TreeItem t, TreeItem search, int nr) {
        if(t == search) return nr;
        nr++;
        List<TreeItem> children = t.getChildren();  
        for(TreeItem c : children) {
            int i = calculateIndex(c, search, nr);
            if(i>0) return i;
            nr = -i;
        }
        return -nr;
    }

    @FXML
    void bCopyClicked(ActionEvent event) {
        final Clipboard clipboard = Clipboard.getSystemClipboard();
        final ClipboardContent content = new ClipboardContent();
        String s = "";
        for(Object c : tvAblauf.getRoot().getChildren()) {
            if(c instanceof TreeItem) {
                s += generateClipboardContent((TreeItem) c, "");
            }
        }

        content.putString(s);

        clipboard.setContent(content);
    }

    private String generateClipboardContent(TreeItem t, String tab) {
        String s = tab+t.getValue();
        for(Object c : t.getChildren()) {
            if(c instanceof TreeItem) {
                s += generateClipboardContent((TreeItem) c, tab+"  ");
            }
        }
        return s;
    }

}
