package graph;

import imp.*;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import java.awt.Graphics2D;

import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.MouseButton;
import java.nio.file.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javafx.scene.control.Tooltip;
import javafx.util.Duration;
import javafx.animation.AnimationTimer;
import java.awt.image.*;

import org.apache.commons.io.FileUtils;
/**
 * Der GraphPlotter ist das Herzstueck der Visualisierung und dient als Schnittstelle zur GUI.
 * 
 * @author Thomas Schaller
 * @version 07.02.2023 (v7.0)
 * v6.9: Context-Menü schließt, wenn an andere Stelle geklickt wird
 * v7.0: MouseOver - Infos für Knoten und Kanten, Infos können ausgewählt werden.
 * 
 */
public class GraphPlotter extends PictureViewer {
    // Anfang Attribute
    private Graph graph;
    private GraphOptions options;

    private boolean multiselect = false;
    private boolean editable = false;
    private ArrayList<GraphElement> selected=new ArrayList<GraphElement>();
    private Knoten dragKnoten = null;
    private int dragMode = 0;

    // Tooltip 
    private double mouseX, mouseY;
    private Tooltip t;

    private GraphElement restrictTo = null;
    private Point2D offset = new Point2D(0,0);

    private ObjectProperty<Point2D> mouseLocation = new SimpleObjectProperty<Point2D>(new Point2D(0, 0));
    private BooleanProperty mouseMoving = new SimpleBooleanProperty();

    //    private JTextArea jTAMeldungen = new JTextArea("");
    //    private JScrollPane jTAMeldungenScrollPane = new JScrollPane(jTAMeldungen);

    // Ende Attribute

    /**
     * Der Konstruktor legt sowohl Einstellungen des mxGraphen (Drag&Drop, Editable, ...) als auch des Graphen (gewichtet, gerichtet, ...) fest.
     *
     * @param  boolean isDirected  Gibt an, ob der Graph gerichtet oder ungerichtet ist
     * @param  boolean isWeighted  Gibt an, ob der Graph gewichtet oder ungewichtet ist
     * @param  String hintergrundBild   Gibt den Namen eines Hintergrundbildes an
     */
    public GraphPlotter() {
        graph = new Graph();
        options = new GraphOptions(graph);

        this.setStyle("-fx-background:#FFFFE8");

        //        add(jTAMeldungenScrollPane, BorderLayout.SOUTH);
        setOnMouseClicked(mouseEvent ->  mouseClicked(mouseEvent));
        this.widthProperty().addListener((value, oldWidth, newWidth) -> updateImage());
        this.heightProperty().addListener((value, oldWidth, newWidth) -> updateImage());

        setOnMouseMoved(e -> mouseLocation.set(new Point2D(e.getSceneX(), e.getSceneY())));
        mouseMoving.addListener((obs, wasMoving, isNowMoving) -> {
                    updateImage();
            });

        AnimationTimer timer = new AnimationTimer() {
                private double lastMouseX ;
                private double lastMouseY ;
                long lastMouseMovement ;
                long MIN_STATIONARY_TIME = 2000;
                @Override
                public void handle(long timestamp) {
                    double x = mouseLocation.get().getX();
                    double y = mouseLocation.get().getY();
                    if (Math.abs(lastMouseX-x) > 5 || Math.abs(lastMouseY-y)>5) {
                        lastMouseMovement = timestamp ;
                        lastMouseX = x;
                        lastMouseY = y;
                    }

                    mouseMoving.set(timestamp - lastMouseMovement < MIN_STATIONARY_TIME);

                }
            };
        timer.start();

    }

    public void setEditable() {
        editable = true;
        //setOnMousePressed(mouseEvent -> mouseDown(mouseEvent)); // wird durch MousePressed in EditTab realisiert und dann hier aufgerufen
        setOnMouseReleased(mouseEvent -> mouseUp(mouseEvent));
        setOnDragDetected(mouseEvent -> startFullDrag());
        setOnMouseDragOver(mouseEvent-> mouseDragged(mouseEvent));
        setOnMouseClicked(null);
    }

    public void setGraph(Graph graph, GraphOptions options) {
        this.graph = graph;
        this.options = options;
        updateImage();
    }

    public void setRestrictTo(GraphElement k) {
        if(restrictTo != k) {
            restrictTo = k; 
            selected.clear();
            if (k!= null) selected.add(k);
            updateImage();
        }
    }

    public GraphElement getRestrictTo() {
        return restrictTo; 
    }

    public void mouseClicked(MouseEvent mouseEvent) {

        Point2D local =  this.getContent().sceneToLocal(mouseEvent.getSceneX(), mouseEvent.getSceneY());
        GraphElement k = getKnotenAt((int) local.getX(), (int) local.getY());
        if(k!=null && mouseEvent.isShiftDown() && restrictTo != null && restrictTo instanceof Knoten) { // mit Shift-Knotenklick Kante auswählen, damit unsichtbare Kanten gewählt werden können (für TSP)
            k = graph.getKante((Knoten) restrictTo, (Knoten) k);
        }
        if(k!=null && mouseEvent.isShiftDown() && selected.size() == 1 && selected.get(0) instanceof Knoten) { // mit Shift-Knotenklick Kante auswählen, damit unsichtbare Kanten gewählt werden können (für TSP)
            k = graph.getKante((Knoten) (selected.get(0)), (Knoten) k);
        }
        if(k==null)  k = getKanteAt((int) local.getX(), (int) local.getY());
        if(!multiselect) {
            selected.clear(); 
        }
        if(k != null) {
            if(selected.contains(k)) selected.remove(k);
            else selected.add(k);
        } 
        updateImage();
    }

    public void mouseDown(MouseEvent mouseEvent) {

        if(mouseEvent.isPrimaryButtonDown()) {
            Point2D local =  this.getContent().sceneToLocal(mouseEvent.getSceneX(), mouseEvent.getSceneY());
            dragKnoten = getKnotenAt((int) local.getX(), (int) local.getY());
            dragMode = 3;  // Linksclick aber nicht auf Knoten
            if(dragKnoten != null) {
                mouseEvent.setDragDetect(true);
                double distance = Math.sqrt(Math.pow(local.getX()-dragKnoten.getX(),2)+Math.pow(local.getY()-dragKnoten.getY(),2));
                if(distance < options.vertexSize/4) 
                    dragMode = 1;  // Knoten verschieben
                else 
                    dragMode = 2;  // Kante ziehen
            }
        } 
    }

    public void mouseDragged(MouseEvent mouseEvent) {

        if(dragKnoten != null) {
            Point2D local =  this.getContent().sceneToLocal(mouseEvent.getSceneX(), mouseEvent.getSceneY());
            if(dragMode == 1) {
                dragKnoten.setX((int) (local.getX()));
                dragKnoten.setY((int) (local.getY()));
            }
            updateImage();
            if(dragMode == 2) {
                Picture p = getImage();
                p.stroke(0);
                p.strokeWeight(3);
                //p.line(dragKnoten.getX(), dragKnoten.getY(), (int) local.getX(), (int) local.getY());
                drawArrow(p,dragKnoten.getX(), dragKnoten.getY(), (int) local.getX(), (int) local.getY());
                setImage(p,false);
            }
        }
    }

    public void mouseUp(MouseEvent mouseEvent) {

        Point2D local =  this.getContent().sceneToLocal(mouseEvent.getSceneX(), mouseEvent.getSceneY());
        Knoten k = getKnotenAt((int) local.getX(), (int) local.getY());
        if(dragMode == 3 && k==null && getKanteAt((int) local.getX(), (int) local.getY())==null) { // neuer Knoten
            if(getContextMenu() == null) {
                graph.neuerKnoten(new Knoten((int)local.getX(), (int) local.getY())) ;
            } else { setContextMenu(null); }
        } else {
            if(dragMode == 2 && k != null && k != dragKnoten) {
                graph.neueKante(dragKnoten, k, 0.0);
            }
        }
        dragKnoten = null;
        dragMode = 0;
        mouseEvent.setDragDetect(false);
        updateImage();

    }

    private Knoten getKnotenAt(int x, int y) {
        List<Knoten> knoten = graph.getAlleKnoten();
        if(restrictTo != null) {
            knoten.clear();
            if(restrictTo instanceof Knoten) {
                knoten = graph.getNachbarknoten((Knoten) restrictTo);
                knoten.add((Knoten) restrictTo);
            }
            if(restrictTo instanceof Kante) {
                knoten.add(((Kante) restrictTo).getStart());
                knoten.add(((Kante) restrictTo).getZiel());
            }

        }

        for(Knoten k : knoten) {
            if(Math.sqrt(Math.pow(k.getX()-x,2)+Math.pow(k.getY()-y,2)) < options.vertexSize/2+1) {
                return k;
            }
        }
        return null;
    }

    private Kante getKanteAt(int x, int y) {
        List<Kante> kanten = graph.getAlleKanten();
        if(restrictTo != null) {
            kanten.clear();
            if(restrictTo instanceof Knoten) {
                kanten = graph.getAusgehendeKanten((Knoten) restrictTo);
            }
            if(restrictTo instanceof Kante) {
                kanten.add(((Kante) restrictTo));
            }

        }       

        for (Kante k: kanten) {
            if (x>=Math.min(k.getStart().getX(), k.getZiel().getX())-2 &&
            x<=Math.max(k.getStart().getX(), k.getZiel().getX())+2 &&
            y>=Math.min(k.getStart().getY(), k.getZiel().getY())-2 &&
            y<=Math.max(k.getStart().getY(), k.getZiel().getY())+2) {
                double startX = k.getStart().getX();
                double startY = k.getStart().getY();
                double endX = k.getZiel().getX();
                double endY = k.getZiel().getY();
                double dy = (endY-startY);
                double dx = (endX-startX);
                double l = Math.sqrt(dx*dx+dy*dy);
                dy = dy /l; dx = dx / l;
                startX += dx * (options.vertexSize/2+1);
                startY += dy * (options.vertexSize/2+1);
                endX -= dx * (options.vertexSize/2+1);
                endY -= dy * (options.vertexSize/2+1);
                double dx2 = dy*5;
                double dy2 = -dx*5;
                if (graph.isGerichtet() && graph.getKante(k.getZiel(), k.getStart())!=null){
                    startX += dx2;
                    startY += dy2;
                    endX += dx2;
                    endY += dy2;
                }   
                double nx = dy;
                double ny = -dx;
                double abx = x - startX;
                double aby = y - startY;
                double abs = Math.abs(abx*nx+aby*ny);
                if (abs < 3) return k;
            }
        }
        return null;
    }

    private void drawArrow(Picture p, int startx,  int starty, int endx, int endy) {

        double deltax = startx - endx;
        double result;

        if (deltax == 0.0d) {
            result = (starty > endy  ? Math.PI / 2 : -Math.PI / 2);
        }
        else {
            result = Math.atan((starty - endy) / deltax) + (startx < endx ? Math.PI : 0);
        }

        double angle = result;
        double arrowAngle = Math.PI / 8.0d;
        double arrowSize = 10;
        double x1 = arrowSize * Math.cos(angle - arrowAngle);
        double y1 = arrowSize * Math.sin(angle - arrowAngle);
        double x2 = arrowSize * Math.cos(angle + arrowAngle);
        double y2 = arrowSize * Math.sin(angle + arrowAngle);
        double cx = (arrowSize / 2.0f) * Math.cos(angle);
        double cy = (arrowSize / 2.0f) * Math.sin(angle);
        int x[] = {(int) endx, (int) (endx+x1), (int) (endx+x2), (int) endx};
        int y[] = {(int) endy, (int) (endy+y1), (int) (endy+y2), (int) endy};
        p.line(startx, starty, endx, endy);
        p.polygon(x,y);

    }

    private String darker(String color) {
        String red = color.substring(0, 2);
        String green = color.substring(2,4);
        String blue = color.substring(4,6);
        long r = Long.parseLong(red,16)/2;
        long g = Long.parseLong(green,16) /2;
        long b = Long.parseLong(blue,16) / 2;
        String sr = "0"+Long.toHexString(r);
        String sg = "0"+Long.toHexString(g);
        String sb = "0"+Long.toHexString(b);
        return sr.substring(sr.length()-2)+sg.substring(sg.length()-2)+sb.substring(sb.length()-2);
    }

    private String brighter(String color) {
        String red = color.substring(0, 2);
        String green = color.substring(2,4);
        String blue = color.substring(4,6);
        long r = (Long.parseLong(red,16)+255)/2;
        long g = (Long.parseLong(green,16)) /2;
        long b = (Long.parseLong(blue,16)) / 2;
        String sr = "0"+Long.toHexString(r);
        String sg = "0"+Long.toHexString(g);
        String sb = "0"+Long.toHexString(b);
        return sr.substring(sr.length()-2)+sg.substring(sg.length()-2)+sb.substring(sb.length()-2);
    }

    private String format(double d) {
        if((int) d == d) {
            return ""+(int) d;
        } else {
            return ""+d;
        }
    }

    public Picture  updateImage() {
        Picture p = new Picture(2000,2000,"FFFFE8");
        Graphics2D g = (Graphics2D) p.getImage().getGraphics();

        Knoten restrictToKnoten = null;
        Kante restrictToKante = null;
        if(restrictTo != null && restrictTo instanceof Knoten) restrictToKnoten = (Knoten) restrictTo;
        if(restrictTo != null && restrictTo instanceof Kante)  restrictToKante = (Kante) restrictTo;

        List<Knoten> knoten = graph.getAlleKnoten();
        List<Kante>  kanten = graph.getAlleKanten();

        int maxx = Integer.MIN_VALUE; int maxy = Integer.MIN_VALUE;
        int minx = Integer.MAX_VALUE; int miny = Integer.MAX_VALUE;
        for(Knoten k: knoten) {
            maxx = Math.max(maxx,k.getX());
            minx = Math.min(minx,k.getX());
            maxy = Math.max(maxy,k.getY());
            miny = Math.min(miny,k.getY());
        }

        if(restrictToKnoten != null) {
            knoten = graph.getNachbarknoten(restrictToKnoten);
            kanten = graph.getAusgehendeKanten(restrictToKnoten);
            knoten.add(restrictToKnoten);
            //          this.getContent().setTranslateY(this.getHeight()/2-restrictToKnoten.getY());
            //          this.getContent().setTranslateX(this.getWidth()/2-restrictToKnoten.getX());
            this.getContent().setTranslateY(1000-(restrictToKnoten.getY()));
            this.getContent().setTranslateX(1000-(restrictToKnoten.getX()));
        } else if (restrictToKante != null ) {
            kanten.clear();
            kanten.add(restrictToKante);
            knoten.clear();
            knoten.add(restrictToKante.getStart());
            knoten.add(restrictToKante.getZiel());
            //          this.getContent().setTranslateY(this.getHeight()/2-restrictToKnoten.getY());
            //          this.getContent().setTranslateX(this.getWidth()/2-restrictToKnoten.getX());
            this.getContent().setTranslateX(1000-(restrictToKante.getStart().getX()+restrictToKante.getZiel().getX())/2);
            this.getContent().setTranslateY(1000-(restrictToKante.getStart().getY()+restrictToKante.getZiel().getY())/2);
        }    
        else
        {
            /*this.getContent().setTranslateX(1000-(minx+maxx)/2);
            this.getContent().setTranslateY(1000-(miny+maxy)/2);*/
        }
        p.textMode(Picture.CENTER);

        if(options.bildAnzeigen && !options.bildDatei.isEmpty()) {
            Picture p2 = new Picture("./images/"+options.bildDatei);
            p.getImage().getGraphics().drawImage(p2.getImage(), 0, 0, null);
            minx = options.vertexSize;
            miny = options.vertexSize;
            maxx = p2.getWidth()-options.vertexSize;
            maxy = p2.getHeight()-options.vertexSize;
        } 

        // Zone in der Mitte markieren
        if(restrictToKnoten!=null) {
            p.fill("FFE8E8");
            p.stroke("FFE8E8");
            p.ellipse(restrictToKnoten.getX(), restrictToKnoten.getY(), options.vertexSize*2, options.vertexSize*2);
        }
        if(restrictToKante!=null) {
            p.fill("FFE8E8");
            p.stroke("FFE8E8");
            p.strokeWeight(30);
            p.line(restrictToKante.getStart().getX(), restrictToKante.getStart().getY(), restrictToKante.getZiel().getX(),restrictToKante.getZiel().getY());
        }

        for (Kante k : kanten) {
            if (!options.farbenKanten[k.getFarbe()].equals("invisible") || selected.contains(k)) {

                double startX = k.getStart().getX();
                double startY = k.getStart().getY();
                double endX = k.getZiel().getX();
                double endY = k.getZiel().getY();
                double dy = (endY-startY);
                double dx = (endX-startX);
                double l = Math.sqrt(dx*dx+dy*dy);
                dy = dy /l; dx = dx / l;
                startX += dx * (options.vertexSize/2+1);
                startY += dy * (options.vertexSize/2+1);
                endX -= dx * (options.vertexSize/2+1);
                endY -= dy * (options.vertexSize/2+1);
                double dx2 = dy*5;
                double dy2 = -dx*5;
                if (graph.isGerichtet() && graph.getKante(k.getZiel(), k.getStart())!=null){
                    startX += dx2;
                    startY += dy2;
                    endX += dx2;
                    endY += dy2;
                }     

                if(selected.contains(k)) {
                    p.stroke("FF0000");
                    p.strokeWeight(4);
                    p.line((int) startX, (int) startY, (int) endX, (int) endY);
                } 
                if (!options.farbenKanten[k.getFarbe()].equals("invisible")){
                    p.stroke(options.farbenKanten[k.getFarbe()]);
                    p.fill(options.farbenKanten[k.getFarbe()]);
                }
                else
                {
                    p.stroke("505050");
                    p.fill("505050");
                }

                p.strokeWeight(2);

                if(graph.isGerichtet()) {
                    drawArrow(p, (int) startX, (int) startY, (int) endX, (int) endY);
                } else {
                    p.line((int) startX, (int) startY, (int) endX, (int) endY);
                }
            }
        }
        for (Kante k : kanten) {
            if (!options.farbenKanten[k.getFarbe()].equals("invisible") || selected.contains(k)) {
                double startX = k.getStart().getX();
                double startY = k.getStart().getY();
                double endX = k.getZiel().getX();
                double endY = k.getZiel().getY();
                double dy = (endY-startY);
                double dx = (endX-startX);
                double l = Math.sqrt(dx*dx+dy*dy);
                dy = dy /l; dx = dx / l;
                startX += dx * (options.vertexSize/2+1);
                startY += dy * (options.vertexSize/2+1);
                endX -= dx * (options.vertexSize/2+1);
                endY -= dy * (options.vertexSize/2+1);
                double dx2 = dy*5;
                double dy2 = -dx*5;

                if(graph.isGerichtet()) {
                    if (graph.getKante(k.getZiel(), k.getStart())!=null){
                        startX += dx2;
                        startY += dy2;
                        endX += dx2;
                        endY += dy2;
                    }     

                } 
                if(options.showEdgeWeights) {

                    double my = (startY+startY+endY)/3;
                    double mx = (startX+startX+endX)/3;

                    p.fill(255);
                    p.stroke(0);
                    p.strokeWeight(1);

                    int lh = g.getFontMetrics().getAscent();
                    List<String> t = k.getKurztext(options.kanteKurztext);
                    if(t.size() == 1) {
                        p.rect((int) mx-15, (int) my-(lh+4)/2, 30, lh+4);
                        p.fill(0);
                        p.text(t.get(0), (int) mx, (int) my);
                    } 
                    if(t.size() > 1) {
                        p.rect((int) mx-15, (int) my-(lh+2), 30, lh*2+4);
                        p.fill(0);
                        p.text(t.get(0), (int) mx, (int) my-lh/2);
                        p.text(t.get(1), (int) mx, (int) my+lh/2);
                    }
                }
            }
        }

        for (Knoten k : knoten) {

            p.fill(options.farbenKnoten[k.getFarbe()]);

            p.stroke(darker(options.farbenKnoten[k.getFarbe()]));
            p.strokeWeight(3);
            p.ellipse(k.getX(), k.getY(), options.vertexSize, options.vertexSize);
            if(selected.contains(k)) {
                p.noFill();
                p.strokeWeight(2);
                p.stroke(255,0,0);
                p.ellipse(k.getX(), k.getY(), options.vertexSize+2, options.vertexSize+2);
            }

            p.fill(0);
            p.stroke(0);
            p.strokeWeight(0);
            p.textMode(Picture.CENTER);
            if (options.showVertexText) {
                p.text(""+graph.getNummer(k), k.getX(), k.getY());
            } else {
                if (options.showVertexValue) {
                    List<String> t = k.getKurztext(options.knotenKurztext);
                    if(t.size() == 1) {
                        p.text(t.get(0), k.getX(), k.getY());
                    } else {
                        int lh = g.getFontMetrics().getAscent();
                        p.text(t.get(0), k.getX(), k.getY()-lh/2);
                        p.text(t.get(1), k.getX(), k.getY()+lh/2);
                    }
                } 
            }

            // Knotenbezeichnung
            p.textMode(Picture.CORNER);
            p.fill("000080");
            if(options.showVertexInfo && !k.getInfotext().equals("")) {
                p.text(k.getInfotext(), k.getX()+options.vertexSize/2+5,k.getY());
            }

        }
        // Tooltip anzeigen, aber nicht wenn im Editiermodus
        if(!mouseMoving.get() && !editable) {
            Point2D local =  this.getContent().sceneToLocal(mouseLocation.get().getX(), mouseLocation.get().getY());

            int x = (int) local.getX();
            int y = (int) local.getY();

            // sowohl bei Kante wie auch Knoten 
            GraphElement k = getKnotenAt(x,y);
            if(k == null) { k = getKanteAt(x,y);}
            if(k != null) {
                p.fill(200);
                p.stroke(0);
                p.strokeWeight(2);

                List<String> t;
                if(k instanceof Knoten)  t = k.getLangtext(options.knotenLangtext);
                else t = k.getLangtext(options.kanteLangtext);

                // Größe des Kastens berechnen
                int w = 0;
                int lh = g.getFontMetrics().getAscent();
                int h = t.size() * lh;
                for(int i = 0; i<t.size(); i++) {
                    int w2 = g.getFontMetrics().stringWidth(t.get(i));
                    if(w2 > w) w = w2;
                }

                // Rechteck mit Text ausgeben
                p.rect(x, y, w+16, h+10);
                p.fill("303030");
                for(int i = 0; i<t.size(); i++) {
                    p.text(t.get(i), x+8, y+(i+1)*lh+3);
                }

            }
        }

        this.setImage(p, false);
        Picture zugeschnitten = new Picture(maxx-minx+2*options.vertexSize,maxy-miny+2*options.vertexSize);
        zugeschnitten.getImage().getGraphics().drawImage(p.getImage(), 0, 0, maxx-minx+2*options.vertexSize, maxy-miny+2*options.vertexSize, minx-options.vertexSize, miny-options.vertexSize, maxx+options.vertexSize, maxy+options.vertexSize, null);
        return zugeschnitten;
    }

    public GraphOptions getGraphOptions() {
        return options;
    }

    /**
     * Gibt den Graphen zurueck.
     * 
     * @return  Graph
     */
    public Graph getGraph() {
        return graph;
    }

    /**
     * Gibt das selektierte Knotenobjekt zurueck.
     * 
     * @return  Object
     */
    public Knoten getSelectedKnoten() {
        if(selected.size()==1 && selected.get(0) instanceof Knoten)
            return ((Knoten) (selected.get(0)));
        else
            return null;
    }

    /**
     * Gibt die selektierte KnotenobjektListe (als Array) zurueck.
     * 
     * @return  Object[]
     */
    public List<Knoten> getSelectedKnotenListe() {
        List<Knoten> l = new ArrayList<Knoten>();
        for(GraphElement g : selected) {
            if(g instanceof Knoten) l.add((Knoten) g);
        }
        return l;
    }

    /**
     * Gibt das selektierte Kantenobjekt zurueck.
     * 
     * @return  Object
     */
    public Kante getSelectedKante() {
        if(selected.size()==1 && selected.get(0) instanceof Kante)
            return ((Kante) (selected.get(0)));
        else
            return null;
    }

    /**
     * Ueberschreibt die Methode toString. Eine String-Repraesentation des GraphPlotters wird ausgegeben.
     *
     * @return  String  Die String-Repraesentation des GraphPlotters
     */
    public String toString() {
        String s = "";
        if(graph.isGerichtet())
            s += "Gerichteter, ";
        else
            s += "Ungerichteter, ";
        if(graph.isGewichtet())
            s += "gewichteter Graph mit Hintergrundbild: ";
        else
            s += "ungewichteter Graph mit Hintergrundbild: ";
        if(options.bildDatei.equals(""))
            s += " kein Bild!";
        else
            s += options.bildDatei + "!";
        return s;
    }

    /**
     * Gibt die String-Repraesentation des GraphPlotters auf der Konsole aus.
     */
    public void ausgabe() {
        System.out.println(toString() + "\n"+graph.toString());
    }
    // Ende Methoden
}
