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

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.JaxoObject;
import net.sf.jaxodraw.object.JaxoParticleObject;
import net.sf.jaxodraw.util.JaxoGeometry;
import net.sf.jaxodraw.util.graphics.JaxoGraphics2D;


/** A general loop object.
 * @since 2.0
 */
public abstract class JaxoLoopObject extends JaxoParticleObject {

    private static final long serialVersionUID = 2L;

    private static final int LP4 = 12;

    /** Returns an exact copy of this JaxoLoop.
     * @param temp An instance of the loop to copy to (since this is an abstract
     *  class, we have to provide an instance of a sub-class).
     * @return A copy of this JaxoLoop.
     */
    public final JaxoObject copy(final JaxoLoopObject temp) {
        temp.copyFrom(this);
        return temp;
    }


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

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

        return isCopy;
    }

    /** {@inheritDoc} */
    public void paint(final JaxoGraphics2D g2) {
        final GeneralPath gp = getObjectPath();

        if (gp == null) {
            return;
        }

        g2.setColor(getColor());
        g2.setStroke(getStroke());
        g2.draw(gp);

        if (isPaintArrow()) {
            paintArrow(g2);
        }
    }

    /**
     * Returns the bounding box of this object.
     *
     * @return the bounding box of this object.
     */
    public Rectangle getBounds() {
        final GeneralPath gp = getObjectPath();
        Rectangle bb;

        if (gp == null) {
            bb = new Rectangle();
            bb.add(getX(), getY());
            bb.add(getX2(), getY2());
            return bb;
        }

        bb = getStroke().createStrokedShape(gp).getBounds();

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

        return bb;
    }

    /** {@inheritDoc} */
    public String latexCommand(final float scale, final Dimension canvasDim) {
        final float radius = getLaTexRadius(scale);

        if ((int) radius == 0) {
            return "%";
        }

        final Point2D center = getLaTexCenter(scale, canvasDim.height);
        final Point2D angles = getLaTexAngles();
        final String options = getAxo4JOptions(scale);

        return "\\Arc" + options + "(" + D_FORMAT.format(center.getX()) + ","
            + D_FORMAT.format(center.getY()) + ")" + "("
            + D_FORMAT.format(radius) + ","
            + D_FORMAT.format(angles.getX()) + ","
            + D_FORMAT.format(angles.getY()) + ")";
    }

    /** {@inheritDoc} */
    @Override
    public final void paintVisualAid(final JaxoGraphics2D g2) {
        final int x = getX();
        final int y = getY();

        g2.drawLine(x - LP4, y, x + LP4, y);
        g2.drawLine(x, y - LP4, x, y + LP4);
    }

    /** {@inheritDoc} */
    public JaxoArrow.Coordinates arrowCoordinates() {
        final Point2D.Double ap = getLoopPoint(1 - getArrowPosition());

        double theta = Math.atan2(ap.y - getY(), ap.x - getX()) - (Math.PI / 2.d);

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

        return new JaxoArrow.Coordinates(ap.x, ap.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 a center point which is used by the latexCommand method.
     * @param scaleFactor A scale factor.
     * @param canvasHeight The height of the current canvas.
     * @return A Point2D object holding the center point.
     */
    protected Point2D getLaTexCenter(final float scaleFactor, final int canvasHeight) {
        return getLaTexLocation(scaleFactor, canvasHeight);
    }

    /**
     * Dividing the loop into n equidistant segments of length d,
     * so n * d = 2 * r * PI is the circumference of the loop,
     * this routine returns the n + 1 loop points that are i * d away from P1,
     * where 0 <= i <= n.
     *
     * @param n The number of segments to divide the loop.
     * @return The equidistant points of the loop.
     */
    public final Point2D.Float[] getEquidistantPoints(final int n) {
        final Point2D.Float p0 = new Point2D.Float(getX(), getY());
        final Point2D.Float p1 = new Point2D.Float(getX2(), getY2());

        final double radius = getRadius();
        final double dphi = (2.d * Math.PI) / n;
        final double phi1 = Math.atan2(p1.y - p0.y, p1.x - p0.x);
        float px, py;

        final Point2D.Float[] point = new Point2D.Float[n + 1];
        point[0] = p1;

        for (int i = 1; i <= n; i++) {
            px = p0.x + (float) (radius * Math.cos(phi1 + (i * dphi)));
            py = p0.y + (float) (radius * Math.sin(phi1 + (i * dphi)));
            point[i] = new Point2D.Float(px, py);
        }

        return point;
    }

    private Point2D.Double getLoopPoint(final double t) {
        final double par = JaxoGeometry.curveParameter(t) * 2 * Math.PI;
        final double phi1 = Math.atan2(getY2() - getY(), getX2() - getX()) + Math.PI;
        final double radius = getRadius();

        final double px = getX() + (radius * Math.cos(phi1 + par));
        final double py = getY() + (radius * Math.sin(phi1 + par));

        return new Point2D.Double(px, py);
    }

    /** Returns a radius which is used by the latexCommand method.
     * @param scaleFactor A scale factor.
     * @return The latex radius.
     */
    protected float getLaTexRadius(final float scaleFactor) {
        return ((float) getRadius() / scaleFactor);
    }

    /**
     * Returns an angle which is used by the latexCommand method.
     * This is basically the relative angle between the center and
     * the second (release) point of the loop.
     *
     * @return A Point2D object with x-component the starting angle
     *      and y-component the starting angle + 360.
     */
    protected Point2D getLaTexAngles() {
        final int sAngle =
            (int) -Math.round((Math.atan2(getRelh(), getRelw()) * 180.f
                ) / Math.PI) + 180;

        final int eAngle = sAngle + 360;

        return new Point2D.Float(sAngle, eAngle);
    }

    /**
     * Get the GeneralPath that paints this loop.
     *
     * @return GeneralPath. May be null for an object that cannot be painted.
     */
    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);
}
