/*
 * @(#)ConnectionHandle.java 5.1
 *
 */

package CH.ifa.draw.standard;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import CH.ifa.draw.util.Geom;
import de.renew.draw.storables.ontology.ConnectionFigure;
import de.renew.draw.storables.ontology.Connector;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.FigureEnumeration;
import de.renew.draw.storables.ontology.Locator;
import de.renew.draw.ui.ontology.DrawingView;


/**
 * A handle to connect figures.
 * The connection object to be created is specified by a prototype.
 * <hr>
 * <b>Design Patterns</b><P>
 * <img src="images/red-ball-small.gif" width=6 height=6 alt=" o ">
 * <b><a href=../pattlets/sld029.htm>Prototype</a></b><br>
 * ConnectionHandle creates the connection by cloning a prototype.
 * <hr>
 *
 * @see ConnectionFigure
 * @see Object#clone
 */
public class ConnectionHandle extends LocatorHandle {

    /**
     * the currently created connection
     */
    private ConnectionFigure _connection;

    /**
     * the prototype of the connection to be created
     */
    private final ConnectionFigure _prototype;

    /**
     * the current target
     */
    private Figure _target = null;

    /**
     * Constructs a handle with the given owner, locator, and connection prototype
     */
    public ConnectionHandle(Figure owner, Locator l, ConnectionFigure prototype) {
        super(owner, l);
        _prototype = prototype;
    }

    /**
     * Creates the connection
     */
    @Override
    public void invokeStart(int x, int y, DrawingView view) {
        super.invokeStart(x, y, view);
        _connection = createConnection();
        Point p = locate();
        _connection.startPoint(p.x, p.y);
        _connection.endPoint(p.x, p.y);
        view.drawing().add(_connection);
    }

    protected ConnectionFigure getConnection() {
        return _connection;
    }

    /**
     * Tracks the connection.
     */
    @Override
    public void invokeStep(MouseEvent e, int x, int y, int anchorX, int anchorY, DrawingView view) {
        Point p = new Point(x, y);
        Figure f = null;
        List<Figure> figures = findAllConnectableFigures(x, y, view.drawing());
        for (Figure fig : figures) {
            if ((fig != null) && fig.canConnect() && !fig.includes(owner())
                && _connection.canConnect(owner(), fig)) {
                f = fig;
                break;
            }
        }

        // track the figure containing the mouse
        if (f != _target) {
            if (_target != null) {
                _target.connectorVisibility(false);
            }
            _target = f;
            if (_target != null) {
                _target.connectorVisibility(true);
            }
        }

        Connector target = findConnectionTarget(p.x, p.y, view.drawing());
        if (target != null) {
            p = Geom.center(target.displayBox());
        } else if (e.isControlDown()) {
            p = snap(p.x, p.y);
        }
        _connection.endPoint(p.x, p.y);
    }

    protected Point snap(int x, int y) {
        return new Point(x, y);
    }

    /**
     * Handles the parent child relationships between figures when
     * a line connection is established successfully between figures.
     * Override this method to handle this event.
     * @param start the start figure of this connection
     * @param end the end figure of this connection
     */
    protected void handleConnect(Figure start, Figure end) {

    }

    /**
     * Connects the figures if the mouse is released over another
     * figure.
     */
    @Override
    public void invokeEnd(int x, int y, int anchorX, int anchorY, DrawingView view) {
        Connector target = findConnectionTarget(x, y, view.drawing());
        if (target != null) {
            _connection.connectStart(startConnector());
            _connection.connectEnd(target);
            _connection.updateConnection();
            handleConnect(startConnector().owner(), target.owner());
            target.owner().changed();
            view.clearSelection();
            view.addToSelection(target.owner());
        } else {
            view.drawing().remove(_connection);
            noChangesMade();
        }
        _connection = null;
        if (_target != null) {
            _target.connectorVisibility(false);
            _target = null;
        }
        super.invokeEnd(x, y, anchorX, anchorY, view);
    }

    @Override
    public void abort(DrawingView view) {
        view.drawing().remove(_connection);
        noChangesMade();
        _connection = null;
    }

    /**
     * Gets the Connector from the owner the connection starts at.
     *
     * @return the Connector from the owner where the connection starts
     */
    protected Connector startConnector() {
        Point p = locate();
        return owner().connectorAt(p.x, p.y);
    }

    /**
     * Creates the ConnectionFigure. By default, the figure prototype is
     * cloned.
     */
    protected ConnectionFigure createConnection() {
        // OK: I do not know why, but the following cast to Figure
        // avoids a strange error message under Java 1.1:
        //   Reference to clone is ambiguous.
        //   It is defined in java.lang.Object clone() and
        //   java.lang.Object clone().
        return (ConnectionFigure) _prototype.clone();
    }

    /**
     * Finds a connection end figure.
     */
    protected Connector findConnectionTarget(int x, int y, Drawing drawing) {
        List<Figure> possibleTargets = findAllConnectableFigures(x, y, drawing);
        for (Figure target : possibleTargets) {
            if ((target != null) && target.canConnect() && !target.includes(owner())
                && _connection.canConnect(owner(), target)) {
                return findConnector(x, y, target);
            }
        }
        return null;
    }

    protected Figure findConnectableFigure(int x, int y, Drawing drawing) {
        FigureEnumeration k = drawing.figuresReverse();
        while (k.hasMoreElements()) {
            Figure figure = k.nextFigure();
            if (!figure.includes(_connection) && figure.canConnect()) {
                if (figure.containsPoint(x, y)) {
                    return figure;
                }
            }
        }
        return null;
    }

    protected List<Figure> findAllConnectableFigures(int x, int y, Drawing drawing) {
        List<Figure> connectableFigures = new ArrayList<>();
        FigureEnumeration k = drawing.figuresReverse();
        while (k.hasMoreElements()) {
            Figure figure = k.nextFigure();
            if (!figure.includes(_connection) && figure.canConnect()) {
                if (figure.containsPoint(x, y)) {
                    connectableFigures.add(figure);
                }
            }
        }
        return connectableFigures;
    }

    protected Connector findConnector(int x, int y, Figure f) {
        return f.connectorAt(x, y);
    }

    /**
     * Draws the connection handle, by default the outline of a
     * blue circle.
     */
    @Override
    public void draw(Graphics g) {
        Rectangle r = displayBox();
        g.setColor(Color.blue);
        g.drawOval(r.x, r.y, r.width, r.height);
    }
}