/**
 *  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.BasicStroke;
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;

import java.io.IOException;
import java.io.ObjectInputStream;

import net.sf.jaxodraw.util.JaxoUtils;
import net.sf.jaxodraw.util.graphics.JaxoGraphics2D;


/** A dashed loop.
 * @since 2.0
 */
public abstract class JaxoDashLoop extends JaxoLoopObject {
    private static final long serialVersionUID = 314159L;
    private transient BasicStroke innerStroke;
    private transient BasicStroke outerStroke;
    private transient Ellipse2D loop = new Ellipse2D.Double();

    private void readObject(final ObjectInputStream in)
        throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        loop = new Ellipse2D.Double();
        resetStroke();
    }

    /** {@inheritDoc} */
    @Override
    public final void paint(final JaxoGraphics2D g2) {
        g2.setColor(getColor());

        if (isDoubleLine()) {
            resetStroke(); // need to recalculate the strokes
            g2.setStroke(innerStroke);
            g2.draw(getInnerPath());
            g2.setStroke(outerStroke);
            g2.draw(getOuterPath());
        } else {
            g2.setStroke(getStroke());
            g2.draw(getObjectPath());
        }

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

    /**
     * Returns the bounding box of this object.
     *
     * @return the bounding box of this object.
     */
    @Override
    public Rectangle getBounds() {
        Rectangle bb;

        if (isDoubleLine()) {
            bb = innerStroke.createStrokedShape(getInnerPath()).getBounds();
            bb.add(outerStroke.createStrokedShape(getOuterPath()).getBounds());
        } else {
            bb = getStroke().createStrokedShape(getObjectPath()).getBounds();
        }

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

        return bb;
    }

    /** {@inheritDoc} */
    @Override
    protected void resetStroke() {
        if (JaxoUtils.zero(getStrokeWidth()) || JaxoUtils.zero(getDash())) {
            // during initialization, either of them is still null
            // so wait until both are set
            return;
        }

        float length = (float) getRadius();
        if (JaxoUtils.zero(length)) {
            length = 1.f;
        }

        setStroke(new BasicStroke(getStrokeWidth(), BasicStroke.CAP_ROUND,
                BasicStroke.JOIN_MITER, 10.0f, strokeDashes(), 0.0f));
        this.innerStroke =
            new BasicStroke(getStrokeWidth(), BasicStroke.CAP_ROUND,
                BasicStroke.JOIN_MITER, 10.0f, innerStrokeDashes(length), 0.0f);
        this.outerStroke =
            new BasicStroke(getStrokeWidth(), BasicStroke.CAP_ROUND,
                BasicStroke.JOIN_MITER, 10.0f, outerStrokeDashes(length), 0.0f);
    }

    /** {@inheritDoc} */
    protected GeneralPath getObjectPath() {
        final GeneralPath gp = getGeneralPath();
        gp.reset();
        final double length = getRadius();
        loop.setFrame(-length + getX(), -length + getY(), 2 * length,
            2 * length);
        gp.append(loop, false);
        return gp;
    }

    private GeneralPath getOuterPath() {
        final GeneralPath gp = getGeneralPath();
        gp.reset();
        final float length = (float) getRadius();
        final float dlsep = getDLSeparation() / 2.f;
        loop.setFrame(-length - dlsep + getX(), -length - dlsep + getY(),
            2 * (length + dlsep), 2 * (length + dlsep));
        gp.append(loop, false);
        return gp;
    }

    private GeneralPath getInnerPath() {
        final GeneralPath gp = getGeneralPath();
        gp.reset();
        final float length = (float) getRadius();
        final float dlsep = getDLSeparation() / 2.f;
        loop.setFrame(-length + dlsep + getX(), -length + dlsep + getY(),
            2 * (length - dlsep), 2 * (length - dlsep));
        gp.append(loop, false);
        return gp;
    }

    /** {@inheritDoc} */
    protected String getAxo4JOptions(final float scale) {

        final float dashSize = getDash() / scale;
        final StringBuffer optioncmd = new StringBuffer("dash,dashsize=").append(
                    D_FORMAT.format(dashSize)); // All loops are anticlockwise

        if (isPaintArrow()) {
            final float arpos = getArrowPosition();
            final StringBuffer arrowcmd = new StringBuffer(this.getArrow().latexCommand(arpos, scale));

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

            optioncmd.append(',').append(arrowcmd);

            if (isDoubleLine()) {
                optioncmd.append(",double,sep=").append(
                    D_FORMAT.format(this.getDLSeparation()));
            }

        } else if (isDoubleLine()) {
            optioncmd.append(",double,sep=").append(
                    D_FORMAT.format(this.getDLSeparation()));

        }

        optioncmd.insert(0, '[').append(']');

        return optioncmd.toString();
    }

    /**
     * Returns an array that is used as the dash parameter in
     * {@link java.awt.BasicStroke} to paint this object.
     * @return a dash array.
     */
    protected abstract float[] strokeDashes();

    /**
     * Returns an array that is used as the dash parameter in
     * {@link java.awt.BasicStroke} to paint the inner part of
     * this loop in double-line mode.
     * @param radius the radius.
     * @return a dash array.
     */
    protected abstract float[] innerStrokeDashes(double radius);

    /**
     * Returns an array that is used as the dash parameter in
     * {@link java.awt.BasicStroke} to paint the outer part of
     * this loop in double-line mode.
     * @param radius the radius.
     * @return a dash array.
     */
    protected abstract float[] outerStrokeDashes(double radius);
}
