/**
 *  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.bezier;

import java.awt.geom.Point2D;

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


/** Abstract class for the implementation of a bezier curve.
 * @since 2.0
 */
public abstract class JaxoBezierObject extends Jaxo3PointObject {
    /** Fourth point (index 3). */
    public static final int SELECT_P4 = 3;

    // private
    private static final long serialVersionUID = 2L;
    private static final int POINT_COUNT = 4;

    static {
        setTransient(JaxoBezierObject.class, new String[] {"x4", "y4"});
    }

    /** The fourth points defining this Bezier object.*/
    private int x4;

    /** The fourth points defining this Bezier object.*/
    private int y4;

    /** Indicates whether frequency stretching is allowed for this Bezier. */
    private boolean noFreqStretching = true;

    /** {@inheritDoc}
     * @return 4.
     */
    @Override
    public int getPointCount() {
        return POINT_COUNT;
    }

    /** {@inheritDoc} */
    @Override
    public int getX(final int index) {
        if (index == SELECT_P4) {
            return x4;
        }
        return super.getX(index);
    }

    /** {@inheritDoc} */
    @Override
    public int getY(final int index) {
        if (index == SELECT_P4) {
            return y4;
        }
        return super.getY(index);
    }

    /** {@inheritDoc} */
    @Override
    public void setX(final int index, final int value) {
        if (index == SELECT_P4) {
            x4 = value;
        } else {
            super.setX(index, value);
        }
    }

    /** {@inheritDoc} */
    @Override
    public void setY(final int index, final int value) {
        if (index == SELECT_P4) {
            y4 = value;
        } else {
            super.setY(index, value);
        }
    }

    // Bean getter and setter methods

    /** Sets the frequency stretching property of this Bezier.
     * @param nofs True if this Bezier is not frequency stretched.
     */
    public final void setNoFreqStretching(final boolean nofs) {
        this.noFreqStretching = nofs;
    }

    /** Returns the frequency stretching property of this Bezier.
     * @return  True if this Bezier is not frequency stretched.
     */
    public final boolean isNoFreqStretching() {
        return noFreqStretching;
    }

    /** Sets the x coordinate of the fourth click point of this Bezier.
     * @param newx4 The x coordinate of the fourth click point of this Bezier.
     */
    public final void setX4(final int newx4) {
        setX(SELECT_P4, newx4);
    }

    /** Returns the x coordinate of the fourth click point of this Bezier.
     * @return The x coordinate of the fourth click point of this Bezier.
     */
    public final int getX4() {
        return getX(SELECT_P4);
    }

    /** Sets the y coordinate of the fourth click point of this Bezier.
     * @param newy4 The y coordinate of the fourth click point of this Bezier.
     */
    public final void setY4(final int newy4) {
        setY(SELECT_P4, newy4);
    }

    /** Returns the y coordinate of the fourth click point of this Bezier.
     * @return The y coordinate of the fourth click point of this Bezier.
     */
    public final int getY4() {
        return getY(SELECT_P4);
    }

    /** Sets the points of this Bezier.
     * @param sx1 The x coordinate of the first click point of this Bezier.
     * @param sy1 The y coordinate of the first click point of this Bezier.
     * @param sx2 The x coordinate of the second click point of this Bezier.
     * @param sy2 The y coordinate of the second click point of this Bezier.
     * @param sx3 The x coordinate of the third click point of this Bezier.
     * @param sy3 The y coordinate of the third click point of this Bezier.
     * @param sx4 The x coordinate of the fourth click point of this Bezier.
     * @param sy4 The y coordinate of the fourth click point of this Bezier.
     * @deprecated unused. Use {@link #setLocation(int,int,int,int,int,int,int,int)} instead.
     */
    public final void setBezierPts(final int sx1, final int sy1, final int sx2, final int sy2,
        final int sx3, final int sy3, final int sx4, final int sy4) {
        setLocation(sx1, sy1, sx2, sy2, sx3, sy3, sx4, sy4);
    }

    /** Sets the points of this Bezier.
     * @param sx1 The x coordinate of the first click point of this Bezier.
     * @param sy1 The y coordinate of the first click point of this Bezier.
     * @param sx2 The x coordinate of the second click point of this Bezier.
     * @param sy2 The y coordinate of the second click point of this Bezier.
     * @param sx3 The x coordinate of the third click point of this Bezier.
     * @param sy3 The y coordinate of the third click point of this Bezier.
     * @param sx4 The x coordinate of the fourth click point of this Bezier.
     * @param sy4 The y coordinate of the fourth click point of this Bezier.
     */
    public final void setLocation(final int sx1, final int sy1, final int sx2, final int sy2,
        final int sx3, final int sy3, final int sx4, final int sy4) {
        setLocation(sx1, sy1, sx2, sy2, sx3, sy3);
        setX4(sx4);
        setY4(sy4);
    }

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

        if (comp instanceof JaxoBezierObject) {
            final JaxoBezierObject tmp = (JaxoBezierObject) comp;
            if ((tmp.getX4() == getX4()) && (tmp.getY4() == getY4())
                    && (tmp.isNoFreqStretching() == isNoFreqStretching())
                    && super.isCopy(tmp)) {
                isCopy = true;
            }
        }

        return isCopy;
    }

    /** Sets all parameters from the given object to the current one.
     * @param temp The object to copy from.
     */
    public void copyFrom(final JaxoBezierObject temp) {
        super.copyFrom(temp);
        setLocation(temp.getX(), temp.getY(), temp.getX2(), temp.getY2(),
            temp.getX3(), temp.getY3(), temp.getX4(), temp.getY4());
        setNoFreqStretching(temp.isNoFreqStretching());
    }

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

    /** {@inheritDoc} */
    @Override
    public final void paintVisualAid(final JaxoGraphics2D g2) {
        g2.drawLine(getX(), getY(), getX2(), getY2());
        g2.drawLine(getX2(), getY2(), getX3(), getY3());
        g2.drawLine(getX3(), getY3(), getX4(), getY4());
        g2.drawLine(getX4(), getY4(), getX(), getY());
    }

    /** {@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, getX3(), getY3(), isMarked(), !canBeSelected(SELECT_P3, editMode));
        h.paint(g2, getX4(), getY4(), isMarked(), !canBeSelected(SELECT_P4, editMode));
    }

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

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

        if (isAround(SELECT_P4, clickX, clickY, h)) {
            selected = SELECT_P4;
        }

        return selected;
    }

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

    /** {@inheritDoc} */
    @Override
    public final void moveBy(final int deltaX, final int deltaY) {
        if ((deltaX != 0) || (deltaY != 0)) {
            super.moveBy(deltaX, deltaY);
            setX4(getX4() + deltaX);
            setY4(getY4() + deltaY);
        }
    }

    /** {@inheritDoc} */
    @Override
    public final void rescaleObject(final int orx, final int ory, final float scale) {
        final Point2D p1 = JaxoGeometry.scaledPoint(orx, ory, scale, getX(), getY());
        final Point2D p2 =
            JaxoGeometry.scaledPoint(orx, ory, scale, getX2(), getY2());
        final Point2D p3 =
            JaxoGeometry.scaledPoint(orx, ory, scale, getX3(), getY3());
        final Point2D p4 =
            JaxoGeometry.scaledPoint(orx, ory, scale, getX4(), getY4());
        setLocation((int) Math.round(p1.getX()), (int) Math.round(p1.getY()),
            (int) Math.round(p2.getX()), (int) Math.round(p2.getY()),
            (int) Math.round(p3.getX()), (int) Math.round(p3.getY()),
            (int) Math.round(p4.getX()), (int) Math.round(p4.getY()));
    }

    /** Returns the latex (ie axodraw) coordinates of the point passed
     * of this JaxoObject.
     * @param x the x coordinate of the point
     * @param y the y coordinate of the point
     * @param scaleFactor A scalefactor.
     * @param canvasHeight The current height of the canvas.
     * @return A Point2D.
     */
    protected static Point2D getLatexPoint(final double x, final double y, final float scaleFactor,
        final int canvasHeight) {
        return new Point2D.Double(x / scaleFactor, (canvasHeight - y) / scaleFactor);
    }

    /**
     * Returns the latex (ie axodraw) coordinates of the point passed
     * of this JaxoObject.
     *
     * @param p the point
     * @param scaleFactor A scalefactor.
     * @param canvasHeight The current height of the canvas.
     *
     * @return A Point2D.
     *
     * @since 2.1
     */
    protected static Point2D getLatexPoint(final Point2D p, final float scaleFactor, final int canvasHeight) {
        return getLatexPoint(p.getX(), p.getY(), scaleFactor, canvasHeight);
    }

    /**
     * Return a latex Bezier command.
     *
     * @param options the latex options for the command.
     * @param p1 the first control point of the Bezier.
     * @param p2 the second control point of the Bezier.
     * @param p3 the third control point of the Bezier.
     * @param p4 the fourth control point of the Bezier.
     *
     * @return a latex (axodraw4j) Bezier command.
     *
     * @since 2.1
     */
    protected static String bezierLatexCommand(final String options, final Point2D p1, final Point2D p2, final Point2D p3, final Point2D p4) {
        return "\\Bezier" + options + "("
                + D_FORMAT.format(p1.getX()) + "," + D_FORMAT.format(p1.getY()) + ")("
                + D_FORMAT.format(p2.getX()) + "," + D_FORMAT.format(p2.getY()) + ")("
                + D_FORMAT.format(p3.getX()) + "," + D_FORMAT.format(p3.getY()) + ")("
                + D_FORMAT.format(p4.getX()) + "," + D_FORMAT.format(p4.getY()) + ")";
    }

    // return the arrow settings for this Bezier

    protected String getArrowCommand(final float scale) {

        final StringBuffer arrowcmd = new StringBuffer("["); //Assumes all option default to false in axodraw4j

        final float arpos = getArrowPosition(); // The position should be 0.5 ALWAYS, for
                                          // we are putting the arrow as an arrowed line
                                          // layered ovr the actual Bezier. However the line
                                          // is so short that FAPP whatever you have as arpos
                                          // will not matter. It is easier however
                                          // to export the Bezier with the correct arpos.
        arrowcmd.append(this.getArrow().latexCommand(arpos, scale));

        if (isFlip()) {
            arrowcmd.append(",flip");
        }

        arrowcmd.append(']');

        return arrowcmd.toString();
    }

    /** {@inheritDoc} */
    public JaxoArrow.Coordinates arrowCoordinates() {
        final float arrowPosition = getArrowPosition();

        final Point2D point = getPoint(arrowPosition);

        double theta = slope(arrowPosition);

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

        return new JaxoArrow.Coordinates(point.getX(), point.getY(), theta);
    }

    /** Returns a point of this Bezier curve at parameter u.
     * @param u the parameter value, needs to be between 0 and 1.
     * @return A Point2D.
     */
    public Point2D getPoint(final double u) {
        return JaxoGeometry.bezierPoint(getPoints(), u);
    }

    /**
     * Returns a point of this Bezier curve at a given parameter value.
     * Unlike {@link #getPoint(double)}, the parameter may be outside the
     * interval [0:1]. If it lies outside, the closest end-point of the Bezier
     * curve is returned, i.e. the returned Point is always a point of the
     * Bezier curve. This method should only be used to avoid rounding errors
     * at the end points.
     *
     * @param t the curve parameter.
     * If t &lt; 0 then the Point at t=0 will be returned.
     * If t &gt; 1 then the Point at t=1 will be returned.
     *
     * @return a Point of the Bezier curve at given parameter value.
     *
     * @since 2.1
     */
    protected Point2D getPointOnCurve(final double t) {
        double u = t;

        if (u > 1.d) {
            u = 1.d;
        }

        if (u < 0.d) {
            u = 0.d;
        }

        return getPoint(u);
    }

    /** Returns the first derivative of this Bezier curve at parameter u.
     * @param u the parameter value, needs to be between 0 and 1.
     * @return the slope (in radians) at t.
     */
    public double slope(final double u) {
        return JaxoGeometry.bezierSlope(getPoints(), u);
    }

    /** Returns the approximate length of this Bezier curve.
     * @return The length of the Bezier curve.
     */
    public final double getBezierLength() {
        return JaxoGeometry.bezierLength(getPoints(), 10.d);
    }

    /** {@inheritDoc} */
    @Override
    public void setPreferences() {
        super.setPreferences();
        setNoFreqStretching(true);
    }
}
