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

package CH.ifa.draw.standard;

import java.awt.Point;
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.ui.ontology.DrawingEditor;


/**
 * A tool that can be used to connect figures, to split
 * connections, and to join two segments of a connection.
 * ConnectionTools turns the visibility of the Connectors
 * on when it enters a figure.
 * 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>
 * ConnectionTools creates the connection by cloning a prototype.
 * <hr>
 *
 * @see ConnectionFigure
 * @see Object#clone
 */
public class ConnectionTool extends UndoableTool {

    /**
     * the anchor point of the interaction
     */
    private Connector _startConnector;
    private Connector _endConnector;
    private Connector _connectorTarget = null;
    private Figure _target = null;

    /**
     * the currently created figure
     */
    private ConnectionFigure _connection = null;

    /**
     * the currently manipulated connection point
     */
    private int _splitPoint;

    /**
     * the currently edited connection
     */
    private ConnectionFigure _editedConnection = null;

    /**
     * the prototypical figure that is used to create new
     * connections.
     */
    private final ConnectionFigure _prototype;

    public ConnectionTool(DrawingEditor editor, ConnectionFigure prototype) {
        super(editor);
        _prototype = prototype;
    }

    /**
     * Handles mouse move events in the drawing view.
     */
    @Override
    public void mouseMove(MouseEvent e, int x, int y) {
        trackConnectors(e, x, y);
    }

    /**
     * Manipulates connections in a context dependent way. If the
     * mouse down hits a figure start a new connection. If the mousedown
     * hits a connection split a segment or join two segments.
     */
    @Override
    public void mouseDown(MouseEvent e, int x, int y) {
        ConnectionFigure connection = findConnection(x, y, drawing());
        if (connection != null) {
            if (!connection.joinSegments(x, y)) {
                _splitPoint = connection.splitSegment(x, y);
                _editedConnection = connection;
            } else {
                _editedConnection = null;
            }
            changesMade();
        } else {
            _connection = createConnection();
            _target = findConnectionStart(x, y, drawing());
            //logger.debug("_target: " + _target);
            if (_target != null) {
                _startConnector = findConnector(x, y, _target);
                //logger.debug("_startConnector: " + _startConnector);
                if (_startConnector != null) {
                    Point p = new Point(x, y);

                    //logger.debug("_connection: " + _connection);
                    _connection.startPoint(p.x, p.y);
                    _connection.endPoint(p.x, p.y);
                    view().add(_connection);
                    changesMade();
                } else {
                    _connection = null;
                }
            } else {
                _connection = null;
            }
        }
    }

    /**
     * Adjust the created connection or split segment.
     */
    @Override
    public void mouseDrag(MouseEvent e, int x, int y) {
        Point p = new Point(x, y);
        if (_connection != null) {
            trackConnectors(e, x, y);
            if (_connectorTarget != null) {
                p = Geom.center(_connectorTarget.displayBox());
            }
            _connection.endPoint(p.x, p.y);
        } else if (_editedConnection != null) {
            Point pp = new Point(x, y);
            _editedConnection.setPointAt(pp, _splitPoint);
        }
    }

    /**
     * Connects the figures if the mouse is released over another
     * figure.
     */
    @Override
    public void mouseUp(MouseEvent e, int x, int y) {
        Figure c = null;
        if (_startConnector != null) {
            c = findTarget(x, y, drawing());
        }

        if (c != null) {
            _endConnector = findConnector(x, y, c);
            if (_endConnector != null) {
                _connection.connectStart(_startConnector);
                _connection.connectEnd(_endConnector);
                _connection.updateConnection();
            }
        } else if (_connection != null) {
            view().remove(_connection);
            noChangesMade();
        }

        _connection = null;
        _startConnector = _endConnector = null;
        editor().toolDone();
    }

    /**
     * Deactivates the tool and clears any unfinished connections,
     * connectors or target figures.
     **/
    @Override
    public void deactivate() {
        super.deactivate();
        if (_connection != null) {
            view().remove(_connection);
            noChangesMade();
            _connection = null;
            _startConnector = _endConnector = null;
        }
        if (_target != null) {
            _target.connectorVisibility(false);
        }
    }

    /**
     * Creates the ConnectionFigure. By default, the figure prototype is
     * cloned.
     */
    protected ConnectionFigure createConnection() {
        return (ConnectionFigure) _prototype.clone();
    }

    /**
     * Finds a connectable figure target.
     */
    protected Figure findSource(int x, int y, Drawing drawing) {
        List<Figure> possibleSources = findAllConnectableFigures(x, y, drawing);

        for (Figure source : possibleSources) {
            if (source != null && _connection != null && source.canConnect()
                && _connection.canConnectStart(source)) {
                return source;
            }
        }
        return null;
    }

    /**
     * Finds a connectable figure target.
     */
    protected Figure findTarget(int x, int y, Drawing drawing) {
        List<Figure> possibleTargets = findAllConnectableFigures(x, y, drawing);
        Figure start = _startConnector.owner();

        for (Figure target : possibleTargets) {
            if (target != null && _connection != null && target.canConnect()
                && !target.includes(start) && _connection.canConnect(start, target)) {
                return target;
            }
        }
        return null;
    }

    /**
     * Finds an existing connection figure.
     */
    protected ConnectionFigure findConnection(int x, int y, Drawing drawing) {
        FigureEnumeration k = drawing.figuresReverse();
        while (k.hasMoreElements()) {
            Figure figure = k.nextElement();
            figure = figure.findFigureInside(x, y);
            if ((figure instanceof ConnectionFigure)) {
                return (ConnectionFigure) figure;
            }
        }
        return null;
    }

    /**
     * Gets the currently created figure
     */
    protected ConnectionFigure createdFigure() {
        return _connection;
    }

    protected void trackConnectors(MouseEvent e, int x, int y) {
        if (_connection == null) {
            //logger.debug("_connection is null!!!");
            return;
        }

        Figure c;

        if (_startConnector == null) {
            c = findSource(x, y, drawing());
        } else {
            c = findTarget(x, y, drawing());
        }

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

        Connector cc = null;
        if (c != null) {
            cc = findConnector(x, y, c);
        }
        if (cc != _connectorTarget) {
            _connectorTarget = cc;
        }

        view().checkDamage();
    }

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

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

    private 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 ((_connection == null || !figure.includes(_connection)) && figure.canConnect()) {
                if (figure.containsPoint(x, y)) {
                    connectableFigures.add(figure);
                }
            }
        }
        return connectableFigures;
    }

    protected Connector getStartConnector() {
        return _startConnector;
    }

    protected Connector getEndConnector() {
        return _endConnector;
    }

    protected Connector getTarget() {
        return _connectorTarget;
    }
}