package algorithmen;

import java.lang.Thread;
import java.nio.file.*;
import graph.*;
import java.util.List;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.application.Platform;

/**
 *
 * Abstrakte Oberklasse für alle zu simulierende Algorithmen
 * Diese müssen die Methode getBezeichnung(): String und fuehreAlgorithmusAus() überschreiben.
 *
 * @version 7.1 12.02.2025
 * @author Thomas Schaller
 */

public abstract class GraphAlgo extends Thread {

    // Anfang Attribute
    private boolean stepping;           // Einzelschrittmodus
    private boolean waitforrepaint;     // wird gerade gezeichnet (=> nicht nochmal zeichnen beauftragen)
    private boolean waitforclick;       // wird auf Klick für nächsten Step gewartet
    protected boolean inArbeit; 
    protected GraphPlotter gp;        
    private Knoten startKnoten;
    private int speed =100;
    private Hilfe hilfe;
    protected Graph g;
    private List<String> aktuellerZustand;
    // Ende Attribute

    // Anfang Methoden
    /**
     * Erzeugt neues Algorithmus-Objekt
     */
    public GraphAlgo() {
        stepping = true;    
        waitforrepaint = false;
        waitforclick = false;
        aktuellerZustand = null;
        setDaemon(true);
    }

    /**
     * Setzt die Referenz auf die GraphAnzeige und das Hilfefenster
     * @param graphPlotter
     * @param hilfe
     */
    public void setGUIElemente(GraphPlotter graphPlotter, Hilfe hilfe) {
        gp = graphPlotter;
        g = gp.getGraph();
        this.hilfe = hilfe;
        if (hilfe != null) hilfe.setGraphPlotter(gp);
    }

    /**
     * Setzt Referenz auf den Graphen
     * @param g Graph auf den der Algorithmus angewendet wird
     */
    public void setGraph(Graph g) {
        this.g = g;
        gp = null;
        hilfe = null;
    }

    /**
     * Gibt Referenz auf den Graphen zurück
     * @return g Graph, auf den der Algorithmus angewendet wird
     */
    public Graph getGraph() {
        return g;
    }

    /**
     * Muss vom Algorithmus aufgerufen werden, um einen Haltepunkt zu setzen
     */
    public void step() throws InterruptedException {
        if(gp == null) return;
        try{
            gp.updateImage();
            aktuellerZustand = g.getStatus();
            waitforclick = true;
            if (hilfe != null) hilfe.setReviewAllowed(true);
            int i = 0;
            while((waitforclick && (stepping || i*10 < speed)) && !isInterrupted()){
                Thread.sleep(10);
                i++;
            }

            if (hilfe != null) hilfe.setReviewAllowed(false);
            g.setStatus(aktuellerZustand);
            aktuellerZustand = null;
            if(Thread.interrupted()){
                throw new InterruptedException();
            }
        }catch(Exception e) {
            // Erneutes Stop, damit nicht stop während des Sleeps hier abgefangen wird.
            //System.out.println("Step wurde unterbrochen");
            throw e;
        }
    }

    /**
     * Wird gerade auf einen Buttonklick für den nächsten Step gewartet?
     * @return true/false
     */
    public boolean getWaitforclick() {
        return waitforclick;
    }

    /**
     * Setzt, ob gewartet wird. Damit kann übermittelt werden, dass der Button gedrückt wurde
     * @param wairforclickNeu Soll weiter gewartet werden?
     */
    public void setWaitforclick(boolean waitforclickNeu) {
        waitforclick = waitforclickNeu;
    }

    /**
     * Setzt, ob im Einzelschrittmodus ausgeführt werden soll
     * @param stepping true/false
     */
    public void setStepping(boolean stepping) {
        this.stepping = stepping;
    }

    /**
     * Setzt die Wartezeit im automatischen Modus
     * @param delay Wartezeit
     */
    public void setSpeed(int delay) {
        this.speed = delay;
    }

    /**
     * Ausführung des Algorithmus
     */
    public void run()
    {
        if(!inArbeit && gp != null)
        {
            // System.out.println("Algorithmus gestartet");
            inArbeit = true;
            try{
                if (hilfe != null) hilfe.setReviewAllowed(false);
                fuehreAlgorithmusAus();
                gp.updateImage();
                 //System.out.println("Algorithmus beendet");
            } catch( InterruptedException e){
                 //System.out.println("Algorithmus vorzeitig beendet."+e); 
            } finally {
                if (hilfe != null) hilfe.setReviewAllowed(true);
                inArbeit = false;
            }
        }
    }
    // Ende Methoden

    /**
     * Setzen des Startknotens
     * @param k Startknoten
     */
    public void setStartKnoten(Knoten k) {
        startKnoten = k;
    }

    /**
     * Abfragen des Startknotens für den Algorithmus
     * @return gesetzter Startknoten oder Knoten Nr. 0
     */
    public Knoten getStartKnoten() {
        if (startKnoten != null) {
            return startKnoten;
        } else {
            return g.getKnoten(0);
        } // end of if-else
    }

    /**
     * Methode für den eigentlichen Algorithmus
     */
    public abstract void fuehreAlgorithmusAus() throws InterruptedException;

    /**
     * Name des Algorithmus für die Dropdown-Auswahl
     */
    public abstract String getBezeichnung();

    /**
     * Öffnet ein Anzeigefenster mit einer Meldung. Die 
     * Meldung wird ggf. auch im Hilfefenster angezeigt.
     * Ist für die Verwendung im Algorithmus gedacht.
     * @param s Meldung
     */
    public void melde(String s) {
        info(s);
        Platform.runLater(() -> {
                    Alert meldung = new Alert(AlertType.INFORMATION, s, ButtonType.OK);
                    meldung.setTitle("Information");
                    meldung.setHeaderText(null);
                    meldung.showAndWait();
            });
    }

    /**
     * Text in das Hilfefenster einfügen
     * Ist für die Verwendung im Algorithmus gedacht.
     * @param s Hilfetext
     */
    public void info(String s) {
        if(hilfe != null) hilfe.append(s+"\n");
    }

    /**
     * Löscht das Hilfefenster
     */
    public void resetInfo() {
        if(hilfe != null) hilfe.loescheAlles();
    }

    /**
     * Rückt im Hilfefenster eine Ebene tiefer ein.
     * Ist für die Verwendung im Algorithmus gedacht.
     */
    public void infoIndentMore() {
        if(hilfe != null) hilfe.indentMore();

    }

    /**
     * Rückt im Hilfefenster eine Ebene aus.
     * Ist für die Verwendung im Algorithmus gedacht.
     */
    public void infoIndentLess() {
        if(hilfe != null) hilfe.indentLess();

    }

    /**
     * Initialisiert den Graphen
     */
    public void init() {
        gp.getGraphOptions().knotenKurztext = new String[]{"Wert"};
        gp.getGraphOptions().knotenLangtext = new String[]{"Infotext","Wert","Markiert","Besucht"};
        if(g.isGewichtet()) {
            gp.getGraphOptions().kanteKurztext = new String[]{"Gewicht"};
            gp.getGraphOptions().kanteLangtext = new String[]{"Gewicht","Markiert","Gelöscht"};
        } else {
            gp.getGraphOptions().kanteKurztext = new String[]{};
            gp.getGraphOptions().kanteLangtext = new String[]{"Markiert","Gelöscht"};
        }
    }

}

