/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.object.line;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;

import net.sf.jaxodraw.object.arrow.JaxoArrow;
import net.sf.jaxodraw.object.JaxoHandle;
import net.sf.jaxodraw.object.JaxoObject;
import net.sf.jaxodraw.object.JaxoParticleObject;
import net.sf.jaxodraw.util.JaxoConstants;
import net.sf.jaxodraw.util.JaxoGeometry;
import net.sf.jaxodraw.util.graphics.JaxoGraphics2D;


/** A general line object.
 * @since 2.0
 */
public abstract class JaxoLineObject extends JaxoParticleObject {
    /** Center point of a line. */
    public static final int SELECT_P0 = -5;

    private static final long serialVersionUID = 2L;

    // Bean getter and setter methods

    /** {@inheritDoc} */
    @Override
    public boolean isCopy(final JaxoObject comp) {
        boolean isCopy = false;

        if (comp instanceof JaxoLineObject) {
            isCopy = super.isCopy(comp);
        }

        return isCopy;
    }

    /** {@inheritDoc} */
    @Override
    public void setState(final JaxoObject o) {
        if (o instanceof JaxoLineObject) {
            copyFrom((JaxoLineObject) o);
        } else {
            throw new UnsupportedOperationException("Cannot copy from super type!");
        }
    }

    /** {@inheritDoc} */
    @Override
    public int getGrabbedHandle(final int clickX, final int clickY, final JaxoHandle h) {
        int selected = super.getGrabbedHandle(clickX, clickY, h);

        if (isAroundCenter(clickX, clickY, h)) {
            selected = SELECT_P0;
        }

        return selected;
    }

    private boolean isAroundCenter(final int clickX, final int clickY, final JaxoHandle h) {
        return h.contains(clickX, clickY, (getX() + getX2()) / 2,
            (getY() + getY2()) / 2);
    }

    /** {@inheritDoc} */
    @Override
    public final void paintHandles(final JaxoGraphics2D g2, final JaxoHandle h,
        final int editMode) {
        if (editMode == JaxoConstants.UNGROUP) {
            return;
        }

        h.paint(g2, getX(), getY(), isMarked(), !canBeSelected(SELECT_P1, editMode));
        h.paint(g2, getX2(), getY2(), isMarked(), !canBeSelected(SELECT_P2, editMode));
        h.paint(g2, (getX() + getX2()) / 2, (getY() + getY2()) / 2,
            isMarked(), !canBeSelected(SELECT_P0, editMode));
    }

    /** {@inheritDoc} */
    @Override
    public boolean canBeSelected(final int handle, final int mode) {
        boolean active =
            ((handle == SELECT_P1) || (handle == SELECT_P2)
            || (handle == SELECT_P0));
        if (mode == JaxoConstants.RESIZE) {
            active = ((handle == SELECT_P1) || (handle == SELECT_P2));
        }
        return active;
    }

    /** {@inheritDoc} */
    @Override
    public final float smallestDistanceTo(final int px, final int py) {
        final float dist1 = super.smallestDistanceTo(px, py);
        final int distX = px - ((getX2() + getX()) / 2);
        final int distY = py - ((getY2() + getY()) / 2);
        final float dist2 = (float) Math.sqrt((distX * distX) + (distY * distY));
        return (dist1 < dist2) ? dist1 : dist2;

    }

    /** {@inheritDoc} */
    public void paint(final JaxoGraphics2D g2) {
        g2.setColor(getColor());
        g2.setStroke(getStroke());
        g2.draw(getObjectPath());

        if (isPaintArrow()) {
            paintArrow(g2);
        }
    }
    /**
     * Returns the bounding box of this line.
     *
     * @return the bounding box of this line.
     */
    public Rectangle getBounds() {
        final Rectangle bb =
            getStroke().createStrokedShape(getObjectPath()).getBounds();

        if (isPaintArrow()) {
            bb.add(getArrow().getBounds(arrowCoordinates()));
        }

        return bb;
    }

    /** {@inheritDoc} */
    public String latexCommand(final float scale, final Dimension canvasDim) {
        final int canvasHeight = canvasDim.height;

        final Point2D startPoint = getLaTexStartPoint(scale, canvasHeight);
        final Point2D endPoint = getLaTexEndPoint(scale, canvasHeight);

        if (((int) endPoint.getX() == (int) startPoint.getX())
                && ((int) endPoint.getY() == (int) startPoint.getY())) {
            return "%";
        }

        final String options = getAxo4JOptions(scale);

        return "\\Line" + options + "(" + D_FORMAT.format(startPoint.getX()) + ","
            + D_FORMAT.format(startPoint.getY()) + ")" + "("
            + D_FORMAT.format(endPoint.getX()) + ","
            + D_FORMAT.format(endPoint.getY()) + ")";
    }

    /** Returns the first point of this line in latex (ie axodraw) coordinates.
     * @param scaleFactor A scale factor.
     * @param canvasHeight The height of the current canvas.
     * @return A Point2D object holding the start point.
     */
    protected Point2D getLaTexStartPoint(final float scaleFactor, final int canvasHeight) {
        return getLaTexLocation(scaleFactor, canvasHeight);
    }

    /** Returns the end point of this line in latex (ie axodraw) coordinates.
     * @param scaleFactor A scale factor.
     * @param canvasHeight The height of the current canvas.
     * @return A Point2D object holding the end point.
     */
    protected Point2D getLaTexEndPoint(final float scaleFactor, final int canvasHeight) {
        final Point2D endVec = new Point2D.Float();
        endVec.setLocation(getX2() / scaleFactor,
            (canvasHeight - getY2()) / scaleFactor);
        return endVec;
    }

    /** Returns the amount this line is offset in double-line mode.
     * @param scaleFactor A scale factor.
     * @return A Point2D object holding offset.
     */
    protected Point2D getLatexDLOffSet(final float scaleFactor) {
        final Point2D offSet = new Point2D.Float();
        final float lSep = getDLSeparation() / scaleFactor;
        final double theta = Math.atan2((float) getRelh(), (float) getRelw());

        offSet.setLocation(lSep * Math.sin(theta), lSep * Math.cos(theta));

        return offSet;
    }

    /** {@inheritDoc} */
    public JaxoArrow.Coordinates arrowCoordinates() {
        final double length = this.length();
        final float arp = getArrowPosition();

        double theta = Math.atan2(getY2() - getY(), getX2() - getX());

        final double x = getX() + arp * length * Math.cos(theta);
        final double y = getY() + arp * length * Math.sin(theta);

        if (isFlip()) {
            theta += Math.PI;
        }

        return new JaxoArrow.Coordinates(x, y, theta);
    }

    /** {@inheritDoc} */
    public final void rescaleObject(final int orx, final int ory, final float scale) {
        final int newRelWidth = Math.round(getRelSize().width * scale);
        final int newRelHeight = Math.round(getRelSize().height * scale);

        final Point2D p = JaxoGeometry.scaledPoint(orx, ory, scale, getX(), getY());
        setLocation((int) Math.round(p.getX()), (int) Math.round(p.getY()));

        setX2(getX() + newRelWidth);
        setY2(getY() + newRelHeight);
        setWiggles(Math.round(getWiggles() * scale));
        setAmp(Math.round(getAmp() * scale));
    }

    /** Returns the length of this line.
     * @return The length.
     */
    public double length() {
        return Math.sqrt((getWidth() * getWidth())
            + (getHeight() * getHeight()));
    }

    /**
     * Get the GeneralPath that paints this line.
     *
     * @return GeneralPath.
     */
    protected abstract GeneralPath getObjectPath();

    /**
     * Return the option part of the LaTeX command for this line.
     *
     * @param scale the axodraw4j scale factor.
     *
     * @return the option String.
     */
    protected abstract String getAxo4JOptions(float scale);
}
