package ui.view;

import classes.*;
import ui.controller.Control;
import ui.model.Model;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.LinkedList;

/**
 * This Class represents a Graph where the User can model the behavior of
 * elements and switches over time.
 *
 * @author Gruppe14
 */
public class UnitGraph extends JPanel implements MouseListener, MouseMotionListener, ComponentListener {

    private static final long serialVersionUID = 1L;
    private GeneralPath graphCurve = new GeneralPath();
    private float maximum = 0;
    // Information shown when a Point is Dragged
    private String dragInformation = "";
    // Points
    private Point recSize = new Point(8, 8); // Point Size
    private Graphics2D g2;
    private CubicCurve2D c = new CubicCurve2D.Double();
    private CubicCurve2D cr = new CubicCurve2D.Double();
    private CubicCurve2D cl = new CubicCurve2D.Double();
    private LinkedList<Point> pointList;
    // Scale for the Graph
    private double scaleX;
    private double scaleY;
    private float[] arrayOfFloats = null;
    private boolean[] arrayOfBooleans = null;
    private double width = -1;
    private double height = -1;
    private boolean isElement = false;
    private boolean isSwitch = false;
    private ArrayList<HolonElement> tempElements = new ArrayList<>();
    private Model model;
    private Control controller;
    private Line2D.Double line = null;
    private boolean pointDrag = false;
    private boolean init = true;
    private Point tempP = null;
    private double x = 0, y = 0;
    private int x1, x2, y1, y2, ctrlx1, ctrly1, ctrlx2, ctrly2;

    private int border = 4;
    private int textWidth = 0;

    /**
     * Constructor.
     *
     * @param model   the Model
     * @param control the Controller
     */
    public UnitGraph(final Model model, Control control) {
        setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        this.controller = control;
        this.model = model;

        this.pointList = new LinkedList<>();
        this.setBackground(Color.WHITE);

        this.addMouseListener(this);
        this.addMouseMotionListener(this);
        this.addComponentListener(this);
    }

    /**
     * Paints all Components on the Canvas.
     *
     * @param g Graphics
     */
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g2 = (Graphics2D) g;
        RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHints(rh);
        g2.setStroke(new BasicStroke(0));

        graphCurve.reset();

        border = (int) recSize.getX() >> 1;
        // Draw the Vertical Lines
        g2.setColor(Color.BLACK);
        for (int i = border; i < this.getWidth() - border; i += border * 2) {
            g2.drawLine(i, border, i, this.getHeight() - border);
        }
        g2.drawLine(this.getWidth() - border, border, this.getWidth() - border, this.getHeight() - border);

        for (int i = border; i < this.getHeight() - border; i += border * 2) {
            g2.drawLine(border, i, this.getWidth() - border, i);
        }
        g2.drawLine(border, this.getHeight() - border, this.getWidth() - border, this.getHeight() - border);

        if (isElement) {
            // fill array with values from the pointList in a HolonElement
            fillArrayofValue();

            if (arrayOfFloats != null) {
                // Draw the Lines
                g2.setStroke(new BasicStroke(2));
                g2.setColor(Color.BLACK);
                for (int i = 0; i < pointList.size() - 1; i++) {
                    c = buildCurve(pointList.get(i), pointList.get(i + 1));
                    c.setCurve((x1 * scaleX) + border, (y1 * scaleY) + border, (ctrlx1 * scaleX) + border,
                            (ctrly1 * scaleY) + border, (ctrlx2 * scaleX) + border, (ctrly2 * scaleY) + border,
                            (x2 * scaleX) + border, (y2 * scaleY) + border);
                    graphCurve.append(c, true);
                }
                g2.draw(graphCurve);

                // Draw the Points
                g2.setColor(Color.BLUE);
                for (Point aPointList : pointList) {
                    g2.fillOval((int) (aPointList.getX() * scaleX - recSize.getX() / 2) + border,
                            (int) (aPointList.getY() * scaleY - recSize.getY() / 2) + border,
                            (int) recSize.getX(), (int) recSize.getY());
                }

                // Iteration Value
                textWidth = g.getFontMetrics().stringWidth("" + arrayOfFloats[model.getCurIteration()]) + 2;
                if (textWidth
                        + (model.getCurIteration()) * (this.getWidth() - (border * 2)) / (model.getIterations() - 1) + 2
                        + border <= this.getWidth()) {
                    g2.drawString("" + arrayOfFloats[model.getCurIteration()],
                            (model.getCurIteration()) * (this.getWidth() - (border * 2)) / (model.getIterations() - 1)
                                    + 2 + border,
                            this.getHeight() - 10);
                } else {
                    g2.drawString("" + arrayOfFloats[model.getCurIteration()],
                            (model.getCurIteration()) * (this.getWidth() - (border * 2)) / (model.getIterations() - 1)
                                    + border - textWidth,
                            this.getHeight() - 10);
                }

            }
            // drag Information
            if (tempP != null && pointDrag) {
                dragInformation = "" + convertToValueY(getYValueAt((int) tempP.getX()));
                textWidth = g.getFontMetrics().stringWidth("" + convertToValueY(getYValueAt((int) tempP.getX()))) + 2;
                if (textWidth + (tempP.getX() * scaleX) + 10 + border <= this.getWidth()) {
                    g2.drawString(dragInformation, (int) (tempP.getX() * scaleX) + 10 + border,
                            (int) (tempP.getY() * scaleY) + 10);
                } else {
                    g2.drawString(dragInformation, (int) (tempP.getX() * scaleX) - textWidth,
                            (int) (tempP.getY() * scaleY) + 10);
                }
            }

			/*
             * // Actual Iteration Point Visualization g2.setColor(Color.RED);
			 * if (arrayOfFloats != null) { for (int i = 0; i <
			 * arrayOfFloats.length; i++) { g2.fillOval((int) (i * width /
			 * (model.getIterations() - 1) * scaleX - recSize.getX() /
			 * 2)+border, (int) (convertToCanvasY((int) arrayOfFloats[i]) *
			 * scaleY - recSize.getY() / 2)+border, (int) recSize.getX(), (int)
			 * recSize.getY()); } }
			 */

        } else if (isSwitch) {
            if (arrayOfBooleans != null) {
                // array fillen
                fillArrayofBooleans();

                // Draw the Lines
                g2.setStroke(new BasicStroke(2));
                g2.setColor(Color.BLACK);
                for (int i = 0; i < pointList.size() - 1; i++) {
                    // Left out of bounce
                    if ((i == 1 || i == 2) && pointList.get(i).getX() < 0) {
                        line = new Line2D.Double(border, pointList.get(i).getY() * scaleY, border,
                                pointList.get(i + 1).getY() * scaleY);
                    }
                    // Right out of bounce
                    else if (i == pointList.size() - 4 && pointList.get(pointList.size() - 3).getX() > width) {
                        line = new Line2D.Double(pointList.get(i).getX() * scaleX + border,
                                pointList.get(i).getY() * scaleY, this.getWidth() - border,
                                pointList.get(i + 1).getY() * scaleY);
                    } else if (i == pointList.size() - 3 && pointList.get(pointList.size() - 3).getX() > width) {
                        line = new Line2D.Double(this.getWidth() - border, pointList.get(i).getY() * scaleY,
                                this.getWidth() - border, pointList.get(i + 1).getY() * scaleY);
                    } else if (i == pointList.size() - 2 && pointList.get(pointList.size() - 2).getX() > width) {
                        line = new Line2D.Double(this.getWidth() - border, pointList.get(i).getY() * scaleY,
                                pointList.get(i + 1).getX() * scaleX + border, pointList.get(i + 1).getY() * scaleY);
                    } else {
                        line = new Line2D.Double(pointList.get(i).getX() * scaleX + border,
                                pointList.get(i).getY() * scaleY, pointList.get(i + 1).getX() * scaleX + border,
                                pointList.get(i + 1).getY() * scaleY);
                    }
                    graphCurve.append(line, true);
                }
                g2.draw(graphCurve);

				/*
				 * // Draw the Points g2.setColor(Color.BLUE); for (int i = 0; i
				 * < pointList.size() - 0; i++) { g2.fillOval((int)
				 * (pointList.get(i).getX() * scaleX - recSize.getX() / 2) +
				 * border, (int) (pointList.get(i).getY() * scaleY -
				 * recSize.getY() / 2), (int) recSize.getX(), (int)
				 * recSize.getY()); }
				 */

                // Iteration Value
                g2.setColor(Color.BLUE);
                textWidth = g.getFontMetrics().stringWidth("" + arrayOfBooleans[model.getCurIteration()]) + 2;
                if (textWidth
                        + (model.getCurIteration()) * (this.getWidth() - (border * 2)) / (model.getIterations() - 1) + 2
                        + border <= this.getWidth()) {
                    g2.drawString("" + arrayOfBooleans[model.getCurIteration()],
                            (model.getCurIteration()) * (this.getWidth() - (border * 2)) / (model.getIterations() - 1)
                                    + 2 + border,
                            this.getHeight() - 10);
                } else {
                    g2.drawString("" + arrayOfBooleans[model.getCurIteration()],
                            (model.getCurIteration()) * (this.getWidth() - (border * 2)) / (model.getIterations() - 1)
                                    + border - textWidth,
                            this.getHeight() - 10);
                }

            }
            // When the switch graph is dragged
            if (tempP != null && pointDrag)

            {
                try {
                    int i;
                    for (i = 0; (i * (this.getWidth() - (border * 2)) / (model.getIterations() - 1)
                            + border < getMousePosition().getX()); i++) {
                    }
                    dragInformation = "" + i;
                    textWidth = g.getFontMetrics().stringWidth("" + convertToValueY(getYValueAt((int) tempP.getX())))
                            + 2;
                    if (textWidth + (tempP.getX() * scaleX) + 10 + border <= this.getWidth()) {
                        g2.drawString(dragInformation, (int) (getMousePosition().getX()) + 10 + border,
                                (int) (getMousePosition().getY() * scaleY) + 10);
                    } else {
                        g2.drawString(dragInformation, (int) (getMousePosition().getX()) - textWidth,
                                (int) (getMousePosition().getY() * scaleY) + 10);
                    }
                } catch (Exception e) {
                }
            }
        }

        // Iteration Line
        g2.setColor(Color.BLUE);
        g2.setStroke(new BasicStroke(1));
        g2.drawLine(border + (model.getCurIteration()) * (this.getWidth() - border * 2) / (model.getIterations() - 1),
                0, border + (model.getCurIteration()) * (this.getWidth() - border * 2) / (model.getIterations() - 1),
                this.getHeight());

        // algorithmus
        controller.calculateStateForTimeStep(model.getCurIteration());
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (isElement) {
            elementDragged(e);
        } else if (isSwitch) {
            switchDragged(e);
        }
    }

    /**
     * When a Point of a Holon Element is dragged.
     *
     * @param e MouseEvent
     */
    public void elementDragged(MouseEvent e) {
        if (pointDrag && tempP != null) {
            // Out of Bounds verhindern
            int i = pointList.indexOf(tempP);
            x = (e.getX() - border) / scaleX;
            y = (e.getY() - border) / scaleY;
            // y
            if (e.getY() <= border) {
                y = 0;
            } else if (this.getHeight() - border <= e.getY()) {
                y = (this.getHeight() - border * 2) / scaleY;
            }
            // x
            if (tempP == pointList.getFirst() || tempP == pointList.getLast() || pointList.get(i + 1).getX() < x + 2
                    || pointList.get(i - 1).getX() > x - 2 || pointList.getFirst().getX() > x - 2
                    || pointList.getLast().getX() < x + 2) {
                x = tempP.getX();
            }
            tempP.setLocation(x, y);

            repaint();
        }
    }

    /**
     * When a Point of a switch is dragged.
     *
     * @param e MouseEvent
     */
    public void switchDragged(MouseEvent e) {
        if (pointDrag && tempP != null && tempP != pointList.getFirst() && tempP != pointList.getLast()) {
            int i = pointList.indexOf(tempP);
            x = (e.getX() - border) / scaleX;

            if (pointList.get(i + 1).getY() == tempP.getY()) {
                // x
                if (pointList.get(i + 1).getX() <= x + 1 || pointList.get(i - 2).getX() >= x - 1) {
                    x = tempP.getX();
                }
                pointList.get(i - 1).setLocation(x, pointList.get(i - 1).getY());
            } else {
                // x
                if (pointList.get(i + 2).getX() <= x + 1 || pointList.get(i - 1).getX() >= x - 1) {
                    x = tempP.getX();
                }
                pointList.get(i + 1).setLocation(x, pointList.get(i + 1).getY());
            }
            tempP.setLocation(x, tempP.getY());
            repaint();
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    @Override
    public void mousePressed(MouseEvent e) {
        if (isElement) {
            elementPressed(e);
        } else if (isSwitch) {
            switchPressed(e);
        }

    }

    /**
     * When a point of a Holon Element is pressed.
     *
     * @param e MouseEvent
     */
    public void elementPressed(MouseEvent e) {
        boolean added = false;
        boolean deletePoint = false;

        int x = (int) ((e.getX() - border) / scaleX);
        double y = (e.getY() - border) / scaleY;

        // Click on Point
        tempP = null;
        if (pointList != null) {
            // look if a point was clicked
            for (Point p : pointList) {
                if (x >= p.getX() - recSize.getX() / 2 && y >= p.getY() - recSize.getY() / 2
                        && x <= p.getX() + recSize.getX() / 2 && y <= p.getY() * scaleY + recSize.getY() / 2) {
                    if (e.getButton() == MouseEvent.BUTTON3) {
                        tempP = p;
                        deletePoint = true;
                    } else {
                        pointDrag = true;
                        tempP = p;
                    }
                }
            }
            // New Point
            if (!pointDrag && e.getButton() != MouseEvent.BUTTON3 && e.getX() != 0
                    && e.getX() != this.getWidth() / scaleX) {
                for (int i = 0; i < pointList.size(); i++) {
                    // When a point already exist on this x position
                    if (x == pointList.get(i).getX() || x == width || x == 0) {
                        break;
                    }
                    if (x < pointList.get(i).getX() && !added) {
                        if (e.getY() <= border) {
                            pointList.add(i, new Point((int) (x), 0));
                        } else {
                            pointList.add(i, new Point((int) (x), (int) y));
                        }
                        added = true;
                        pointDrag = true;
                        tempP = pointList.get(i);
                    }
                }
            }
            // Delete a Point
            if (deletePoint && tempP.getX() != 0
                    && (tempP.getX() != this.getWidth() / scaleX || tempP != pointList.getLast())) {
                pointList.remove(tempP);
            }

            repaint();
        }
    }

    /**
     * When a point of a Switch is pressed.
     *
     * @param e MouseEvent
     */
    public void switchPressed(MouseEvent e) {
        boolean added = false;
        boolean deletePoint = false;

        double x = (e.getX() - border) / scaleX;
        e.getY();

        // Halbe Iterations Distanz
        double dist = (width / (model.getIterations() - 1)) / 2;

        // Click on Point
        tempP = null;
        if (pointList != null) {
            for (Point p : pointList) {
                if (x >= p.getX() - dist * 2 && x <= p.getX() + dist * 2) {
                    if (e.getButton() == MouseEvent.BUTTON3) {
                        tempP = p;
                        deletePoint = true;
                    } else {
                        pointDrag = true;
                        tempP = p;
                    }
                }
            }
            // New Point
            if (!pointDrag && e.getButton() != MouseEvent.BUTTON3 && x != 0 && x != width) {
                for (int i = 0; i < pointList.size() && !added; i++) {
                    if (x < pointList.get(i).getX() - dist) {
                        // double p1, p2 um location der points zu bestimmen
                        double p1 = pointList.get(i - 1).getX();
                        double p2 = pointList.get(i).getX();
                        // Punkte hinzufügen, je nachdem ob true oder false
                        if (pointList.get(i - 1).getY() != (int) (height / 6)
                                && pointList.get(i).getY() != (int) (height / 6)) {
                            pointList.add(i, new Point((int) ((x + p2) / 2 + dist), (int) (height - height / 6)));
                            pointList.add(i, new Point((int) ((x + p2) / 2 + dist), (int) (height / 6)));
                            pointList.add(i, new Point((int) ((x + p1) / 2 - dist), (int) (height / 6)));
                            pointList.add(i, new Point((int) ((x + p1) / 2 - dist), (int) (height - height / 6)));
                            added = true;
                        } else if (pointList.get(i - 1).getY() == (int) (height / 6)
                                && pointList.get(i).getY() == (int) (height / 6)) {
                            pointList.add(i, new Point((int) ((x + p2) / 2 + dist), (int) (height / 6)));
                            pointList.add(i, new Point((int) ((x + p2) / 2 + dist), (int) (height - height / 6)));
                            pointList.add(i, new Point((int) ((x + p1) / 2 - dist), (int) (height - height / 6)));
                            pointList.add(i, new Point((int) ((x + p1) / 2 - dist), (int) (height / 6)));
                            added = true;
                        }
                    }
                }
            }

            // Delete a Point
            if (deletePoint && tempP != pointList.getFirst() && tempP != pointList.getLast()) {
                int i = pointList.indexOf(tempP);
                // If Right, else if Left
                if (tempP.getY() == (int) (height / 6) && i < pointList.size() - 1 && i > 0) {
                    pointList.remove(i);
                    pointList.remove(i - 1);
                    pointList.remove(i - 2);
                    pointList.remove(i - 3);
                } else if (tempP.getY() == (int) (height - height / 6)) {
                    pointList.remove(i + 2);
                    pointList.remove(i + 1);
                    pointList.remove(i);
                    pointList.remove(i - 1);
                }
            }

            repaint();
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (pointDrag) {
            pointDrag = false;
            tempP = null;
        }
        /**
         * reset the dragInformation.
         */
        dragInformation = "";
        repaint();
    }

    /**
     * When the Component is Resized.
     *
     * @param e ComponentEvent
     */
    public void componentResized(ComponentEvent e) {
        // Wenn ein anderes Element genommen wird
        if (init) {
            init = false;
            // for scale on the first initialisation
            if (width == -1 && height == -1) {
                width = this.getWidth() - (border * 2);
                height = this.getHeight() - (border * 2);
            }
            // Scale
            scaleX = (this.getWidth() - (border * 2)) / width;
            scaleY = (this.getHeight() - (border * 2)) / height;

            // set the scroll graph invisible
            this.getParent().getParent().setVisible(false);
        }

        // Scale
        scaleX = (this.getWidth() - (border * 2)) / width;
        scaleY = (this.getHeight() - (border * 2)) / height;
        repaint();
    }

    @Override
    public void componentHidden(ComponentEvent e) {
    }

    @Override
    public void componentMoved(ComponentEvent e) {

    }

    @Override
    public void componentShown(ComponentEvent e) {
    }

    /**
     * Empty the Graph.
     */
    public void empty() {
        pointList = null;
        tempElements = null;
        arrayOfFloats = null;
        arrayOfBooleans = null;
        isSwitch = false;
        isElement = false;
        repaint();
    }

    /**
     * Resets the Points for the Element.
     */
    public void reset() {
        pointList.removeAll(pointList);
        if (isSwitch) {
            pointList.addFirst(new Point(-border, (int) (height / 6)));
            pointList.addLast(new Point((int) ((this.getWidth()) / scaleX), (int) (height / 6)));
        } else {
            pointList.addFirst(new Point(0, 0));
            pointList.addLast(new Point((int) ((this.getWidth() - (border * 2)) / scaleX), 0));
        }
        repaint();
    }

    /**
     * converts the number to fit the canvas.
     *
     * @param d the number to convert
     * @return the converted number
     */
    public double convertToCanvasY(float d) {
        return (height - (d * (height / maximum)));
    }

    /**
     * converts the number to fit the value.
     *
     * @param d the number to convert
     * @return the converted number
     */
    public float convertToValueY(double d) {
        return (float) Math.round(((height - (height * (d / height))) / (height / maximum)) * 10) / 10;
    }

    /**
     * Visualize the HolonElement on the Graph.
     *
     * @param selectedElement which should be visualized
     */
    public void repaintWithNewElement(ArrayList<HolonElement> selectedElement) {
        arrayOfFloats = selectedElement.get(selectedElement.size() - 1).getAvailableEnergyPerElementAt();
        tempElements = selectedElement;
        pointList = selectedElement.get(selectedElement.size() - 1).getGraphPoints();
        isSwitch = false;
        isElement = true;
        maximum = getMaximum(selectedElement.get(selectedElement.size() - 1));
        // First time clicked on the Element
        if (pointList.isEmpty()) {
            pointList.addFirst(new Point(0, 0));
            pointList.addLast(new Point((int) ((this.getWidth() - (border * 2)) / scaleX), 0));
        }
        repaint();
    }

    /**
     * Visualize the Switch on the Graph.
     *
     * @param s which should be visualized
     */
    public void repaintWithNewSwitch(HolonSwitch s) {
        arrayOfBooleans = s.getActiveAt();
        pointList = s.getGraphPoints();
        isSwitch = true;
        isElement = false;
        // First time clicked on the Element
        if (pointList.isEmpty()) {
            pointList.addFirst(new Point(-border, (int) (height / 6)));
            pointList.addLast(new Point((int) ((this.getWidth()) / scaleX), (int) (height / 6)));
        }
        repaint();
    }

    /**
     * Build a Curve for the Graph.
     *
     * @param p1 startpoint
     * @param p2 endpoint
     * @return the CubicCurve2D for the Graph
     */
    public CubicCurve2D buildCurve(Point p1, Point p2) {
        x1 = (int) p1.getX();
        y1 = (int) p1.getY();
        x2 = (int) p2.getX();
        y2 = (int) p2.getY();

        // calculate the controllpoints
        ctrlx1 = x1 + (x2 - x1) / 2;
        ctrlx2 = x2 - (x2 - x1) / 2;
        if (y1 < y2) {
            ctrly1 = y1 + (y2 - y1) / 10;
            ctrly2 = y2 - (y2 - y1) / 10;
        } else {
            ctrly1 = y1 - (y1 - y2) / 10;
            ctrly2 = y2 + (y1 - y2) / 10;
        }

        // set the curve
        c.setCurve(x1 * scaleX, y1 * scaleY, ctrlx1 * scaleX, ctrly1 * scaleY, ctrlx2 * scaleX, ctrly2 * scaleY,
                x2 * scaleX, y2 * scaleY);
        return c;
    }

    /**
     * Fills the Arrays with booleans.
     */
    public void fillArrayofBooleans() {
        for (int i = 0; i < arrayOfBooleans.length; i++) {
            int t = (int) getYValueAt((int) (i * width / (model.getIterations() - 1)));
            if (t <= height / 2) {
                arrayOfBooleans[i] = true;
            } else {
                arrayOfBooleans[i] = false;
            }

        }
    }

    /**
     * Fills the Arrays of each HolonElement.
     */
    @SuppressWarnings("unchecked")
    public void fillArrayofValue() {
        for (HolonElement he : tempElements) {
            maximum = getMaximum(he);
            he.setGraphPoints((LinkedList<Point>) pointList.clone());
            for (int i = 0; i < arrayOfFloats.length; i++) {
                he.getAvailableEnergyPerElementAt()[i] = convertToValueY(getYValueAt2((int) (i * width / (model.getIterations() - 1))));
            }
            arrayOfFloats = he.getAvailableEnergyPerElementAt();
        }
    }

    /**
     * Get the Y Value at the x Coordination.
     *
     * @param xVal the x value for the y value
     * @return y, the value at x
     */
    public float getYValueAt(int xVal) {
        for (int i = 0; i < pointList.size() - 1; i++) {
            // get the Points
            if (xVal <= pointList.get(i + 1).getX()) {
                // Curve erstellen
                Line2D l1 = new Line2D.Double(pointList.get(i).getX(), pointList.get(i).getY(),
                        pointList.get(i + 1).getX(), pointList.get(i + 1).getY());
                Line2D l2 = new Line2D.Double(xVal, 0, xVal, height);
                return getIntersectionPoint(l1, l2);
            }
        }
        return 0;
    }

    /**
     * Get y value at the x Coordination via curves.
     *
     * @param xVal the x value for the y value
     * @return y value at x
     */
    public float getYValueAt2(int xVal) {
        for (int i = 0; i < pointList.size() - 1; i++) {
            // get the Points
            if (xVal >= pointList.get(i).getX()) {
                // Curve erstellen
                c = buildCurve(pointList.get(i), pointList.get(i + 1));
                c.subdivide(cl, cr);
                // Teil der Kurve aussuchen
                if (cl.getX1() <= xVal * scaleX && cl.getX2() > xVal * scaleX) {
                    c = cl;
                    // Kurve Links von "unten"
                    if (pointList.get(i).getY() >= pointList.get(i + 1).getY()) {
                        for (float j = (float) (height - 1); j >= 0; j -= 0.1f) {
                            if (c.contains(xVal * scaleX, j * scaleY)) {
                                return (float) (j);
                            }
                        }
                    } else {// Kurve Links von "oben"
                        for (float j = 0; j < height; j += 0.1f) {
                            if (c.contains(xVal * scaleX, j * scaleY)) {
                                return (float) (j);
                            }
                        }
                    }
                } else {
                    c = cr;
                    // Kurve Links von "unten"
                    if (pointList.get(i).getY() >= pointList.get(i + 1).getY()) {
                        for (float j = 0; j < height; j += 0.1f) {
                            if (c.contains(xVal * scaleX, j * scaleY)) {
                                return (float) (j);
                            }
                        }
                    } else {// Kurve Links von "oben"
                        for (float j = (float) (height - 1); j >= 0; j -= 0.1f) {
                            if (c.contains(xVal * scaleX, j * scaleY)) {
                                return (float) (j);
                            }
                        }
                    }
                }
            }
        }
        // else
        return getYValueAt(xVal);
    }

    /**
     * Get the Intersection Point of 2 Lines.
     *
     * @param l1 the first Line
     * @param l2 the second Line
     * @return The Intersection Point
     */
    public float getIntersectionPoint(Line2D l1, Line2D l2) {
        if (!l1.intersectsLine(l2)) {
            return 0;// null;
        }
        double px = l1.getX1(), py = l1.getY1(), rx = l1.getX2() - px, ry = l1.getY2() - py;
        double qx = l2.getX1(), qy = l2.getY1(), sx = l2.getX2() - qx, sy = l2.getY2() - qy;

        double det = sx * ry - sy * rx;
        if (det == 0) {
            return 0;// null;
        } else {
            double z = (sx * (qy - py) + sy * (px - qx)) / det;
            if (z < 0 || z > 1) {
                return 0;// new Point(0, 0); // intersection at end point!
            }
            return (float) (py + z * ry);// new Point((int) (px + z * rx), (int)
            // (py + z * ry));
        }
    } // end intersection line-line

    public void update(ArrayList<AbstractCpsObject> obj) {

        ArrayDeque<AbstractCpsObject> queue = new ArrayDeque<>();

        AbstractCpsObject u = null;
        queue.addAll(obj);

        while (!queue.isEmpty()) {
            u = queue.pop();

            repaintGraph(u);
        }
        empty();

        if (u instanceof CpsUpperNode)
            for (AbstractCpsObject adjacent : ((CpsUpperNode) u).getNodes()) {
                queue.add(adjacent);
            }
    }

    void repaintGraph(AbstractCpsObject u) {
        ArrayList<HolonElement> list = new ArrayList<>();

        if (u instanceof HolonObject) {
            for (HolonElement ele : ((HolonObject) u).getElements()) {
                list.add(ele);
                repaintWithNewElement(list);
                fillArrayofValue();
                list.remove(0);
            }
        } else if (u instanceof HolonSwitch) {
            repaintWithNewSwitch((HolonSwitch) u);
            fillArrayofBooleans();
        }
    }

    float getMaximum(HolonElement ele) {
        if (ele.isFlexible()) {
            return ele.getFlexibleEnergyAvailablePerElement();
        } else {
            return ele.getEnergyPerElement();
        }
    }
}