package pipe.views;

import pipe.constants.GUIConstants;
import uk.ac.imperial.pipe.models.petrinet.Connectable;
import uk.ac.imperial.pipe.models.petrinet.ArcPoint;

import javax.swing.*;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;

import static java.lang.Math.max;

/**
 * Building tool for creating an arc, allows the user to go from source to
 * target before adding to petri net
 *
 * @param <T> source class
 */
@SuppressWarnings("serial")
public class TemporaryArcView<T extends Connectable> extends JComponent {
    /**
     * Arc source, this source was clicked on to initialse the drawing of the remporary arc
     */
    private T source;

    /**
     * Head shown when drawing the arc
     */
    private final ArcHead arcHead;

    /**
     * Current end location of the mouse
     */
    private Point2D end;

    /**
     * Intermediate points build up by clicking on the canvas
     * Press shift for curved points
     */
    private List<ArcPoint> intermediatePoints = new ArrayList<>();

    /**
     * Maximum intermediate point seen so far for setting rectangle x bounds
     */
    private double maxX;

    /**
     * Maximum intermediate point seen so far for setting rectangle y bounds
     */
    private double maxY;

    /**
     * Constructor
     * @param source start point of the arc
     * @param arcHead head to draw on the temporary arc
     */
    public TemporaryArcView(T source, ArcHead arcHead) {
        super();
        this.source = source;
        this.arcHead = arcHead;
        Point2D centre = source.getCentre();
        end = new Point2D.Double(centre.getX(), centre.getY());
        updateMax(end);
        setBounds(0, 0, (int) centre.getX(), (int) centre.getY());
    }

    /**
     * Updates max points based on point
     */
    private void updateMax(Point2D point) {
        maxX = max(maxX, point.getX());
        maxY = max(maxY, point.getY());
    }

    /**
     *
     * @param end 2D end point
     */
    public void setEnd(Point2D end) {
        this.end = end;
        updateMax(end);
        updateBounds();
    }

    /**
     * Updates bounds to maxX and maxY
     */
    private void updateBounds() {
        int x = (int) maxX;
        int y = (int) maxY;
        setBounds(0, 0, x, y);
    }

    /**
     * Add this point to the arc when its created
     * @param point to add
     */
    public void addIntermediatePoint(ArcPoint point) {
        intermediatePoints.add(point);
        updateMax(point.getPoint());
        updateBounds();
    }

    /**
     *
     * @return the source model of this arc
     */
    public T getSourceConnectable() {
        return source;
    }


    /**
     * Paints the temporary arc with straight arc points and the specified head
     * @param g graphics 
     */
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;

        g2.setPaint(GUIConstants.ELEMENT_LINE_COLOUR);
        GeneralPath path = new GeneralPath();
        path.moveTo(source.getCentre().getX(), source.getCentre().getY());

        for (ArcPoint arcPoint : intermediatePoints) {
            path.lineTo(arcPoint.getX(), arcPoint.getY());
        }
        path.lineTo(end.getX(), end.getY());
        g2.draw(path);

        g2.translate(end.getX(), end.getY());
        g2.rotate(getRotationAngle());
        arcHead.draw(g2);
    }

    /**
     * @return angle in radians between last point on the arc and the current end
     */
    private double getRotationAngle() {
        Point2D lastPoint = getLastPoint();
        double deltax = end.getX() - lastPoint.getX();
        double deltay = end.getY() - lastPoint.getY();
        return Math.atan2(deltay, deltax);

    }

    /**
     * This method always returns false as to avoid any interaction with the
     * temporary arc view
     *
     * @param x x location of mouse
     * @param y y location of mouse
     * @return false
     */
    @Override
    public boolean contains(int x, int y) {
        return false;
    }

    /**
     *
     * @return the very last point of the temporary arc path points
     */
    public Point2D getLastPoint() {
        if (!intermediatePoints.isEmpty()) {
            return intermediatePoints.get(intermediatePoints.size() - 1).getPoint();
        }
        return source.getCentre();
    }

    /**
     *
     * @return all intermediate path points created whilst creating the arc
     */
    public List<ArcPoint> getIntermediatePoints() {
        return intermediatePoints;
    }
}
