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

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

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

import net.sf.jaxodraw.object.JaxoObject;
import net.sf.jaxodraw.object.JaxoObjectEditPanel;
import net.sf.jaxodraw.object.JaxoSymmetricObject;
import net.sf.jaxodraw.object.JaxoWiggleObject;
import net.sf.jaxodraw.object.line.JaxoPLine;
import net.sf.jaxodraw.util.JaxoUtils;
import net.sf.jaxodraw.util.graphics.JaxoGraphics2D;


/** A photon arc.
 * @since 2.0
 */
public class JaxoPArc extends JaxoArcObject implements JaxoWiggleObject,
    JaxoSymmetricObject {
    private static final long serialVersionUID = 314159L;
    private transient float freq;
    private boolean symmetric = true;

    private void readObject(final ObjectInputStream in)
        throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        freq = 0.f;
    }

    /**
     * Returns the symmetric property of this JaxoPArc.
     * Determines whether the wiggles of this photon arc
     * start and end in the same direction.
     *
     * @return The symmetric property of this JaxoPArc.
     */
    public final boolean isSymmetric() {
        return symmetric;
    }

    /**
     * Sets the symmetric property of this arc object.
     *
     * @param newSymm The symmetric property of this arc object.
     */
    public final void setSymmetric(final boolean newSymm) {
        final Boolean old = Boolean.valueOf(symmetric);
        this.symmetric = newSymm;
        firePropertyChange("symmetric", old, Boolean.valueOf(symmetric));
    }

    /** Sets all parameters from the given object to the current one.
     * @param temp The object to copy from.
     */
    public void copyFrom(final JaxoPArc temp) {
        super.copyFrom(temp);
        this.freq = temp.getFrequency();
        this.symmetric = temp.isSymmetric();
    }

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

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

        if (comp instanceof JaxoPArc) {
            final JaxoPArc arc = (JaxoPArc) comp;
            if ((arc.isSymmetric() == isSymmetric()) && super.isCopy(arc)) {
                isCopy = true;
            }
        }

        return isCopy;

    }

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

        if (gp != null) {
            g2.setColor(getColor());
            g2.setStroke(getStroke());
            g2.draw(gp);
        }
    }

    /**
     * Returns the bounding box of this object.
     *
     * @return the bounding box of this object.
     */
    public Rectangle getBounds() {
        final GeneralPath gp = getObjectPath();
        if (gp == null) {
            // singular arc: return a Rectangle that just contains
            // the three arc points to avoid NPE
            final Rectangle r = new Rectangle(getX(), getY(), 0, 0);
            r.add(getX2(), getY2());
            r.add(getX3(), getY3());
            return r;
        } else {
            return getStroke().createStrokedShape(getObjectPath()).getBounds();
        }
    }

    /** {@inheritDoc} */
    public final String latexCommand(final float scale, final Dimension canvasDim) {
        if (isSingular()) {
            return "% Singular PhotonArc, ignored!" + JaxoUtils.LINE_SEPARATOR;
        }

        final double nwin = (double) getWiggles();

        if (nwin > 1000) {
            return "% Enormous PhotonArc, ignored!" + JaxoUtils.LINE_SEPARATOR;
        }

        if (isOneLine()) {
            final JaxoPLine line = new JaxoPLine();
            line.setLocation(getX(), getY(), getX3(), getY3());
            line.setStrokeWidth(getStrokeWidth());
            line.setAmp(getAmp());
            line.setWiggles(getWiggles());
            line.setDoubleLine(isDoubleLine());
            line.setDLSeparation(getDLSeparation());
            line.setSymmetric(isSymmetric());
            return line.latexCommand(scale, canvasDim);
        }

        final double[] par = getArcParameters();
        final double cx = par[0] / scale;
        final double cy = (canvasDim.height - par[1]) / scale;
        final double r = par[2] / scale;
        final double ea = par[3] + par[4];
        final double sa = par[4];
        final boolean clockwise = (par[3] < 0.d);
        final double amp = getAmp() / (2.d * scale);
        double nwig = (double) getWiggles();

        if (isSymmetric()) {
            nwig += 0.5d;
        }

        final String options = getOptionsCommand(clockwise);

        return "\\PhotonArc" + options
            + "(" + D_FORMAT.format(cx) + "," + D_FORMAT.format(cy)
            + ")" + "(" + D_FORMAT.format(r) + "," + D_FORMAT.format(sa)
            + "," + D_FORMAT.format(ea) + ")" + "{" + D_FORMAT.format(amp)
            + "}" + "{" + D_FORMAT.format(nwig) + "}";
    }

    /** {@inheritDoc} */
    public float getFrequency() {
        return freq;
    }

    /** {@inheritDoc} */
    public void setWigglesFromFrequency(final float frequency) {
        this.freq = frequency;
        setWigglesFromFrequency();
    }

    /** {@inheritDoc} */
    public void setWigglesFromFrequency() {
        final int n = (int) Math.round(getArcLength() * getFrequency());
        setWiggles(n);
    }

    /** {@inheritDoc} */
    public void setFrequencyFromWiggles(final int wiggles) {
        setWiggles(wiggles);
        this.freq = (float) (wiggles / getArcLength());
    }

    /** {@inheritDoc} */
    @Override
    public void setPreferences() {
        super.setPreferences();
        setWigglesFromFrequency(PHOTON_FREQ);
    }

    /** {@inheritDoc} */
    public void prepareEditPanel(final JaxoObjectEditPanel editPanel) {
        editPanel.add3PointsPanel(getPoints(), 0, 0, 2);
        editPanel.addDoubleLinePanel(isDoubleLine(), getDLSeparation(), 2, 0);
        editPanel.addStrokePanel(getStrokeWidth(), 0, 1);
        editPanel.addWigglePanel(getAmp(), getWiggles(), 1, 1);
        editPanel.addSymmPanel(isSymmetric(), 2, 1);
        editPanel.addLineColorPanel(getColor(), 3, 1);

        editPanel.setTitleAndIcon("Photon_arc_parameters", "photonarc.png");
    }

      //
     // private methods
    //

    private GeneralPath getObjectPath() {
        if (isSingular()) {
            return null;
        }

        final GeneralPath gp = getGeneralPath();
        gp.reset();

        if (JaxoUtils.zero(freq)) {
            setFrequencyFromWiggles(getWiggles());
        } else {
            setWigglesFromFrequency();
        }

        if (isOneLine()) {
            appendPhotonLine(gp);
            return gp;
        }

        // cutoff
        if (getWiggles() > 1000) {
            return null;
        }

        if (isDoubleLine()) {
            final float ds = getDLSeparation() / 2.f;
            appendFullArc(gp, ds);
            appendFullArc(gp, -ds);
        } else {
            appendFullArc(gp, 0.f);
        }

        return gp;
    }

    private void appendFullArc(final GeneralPath gp, final float sep) {

        final double[] par = getArcParameters();
        final float cx = (float) par[0];
        final float cy = (float) par[1];
        final float r = (float) par[2] + sep;
        final float oa = (float) -Math.toRadians(par[3]);
        final float sa = (float) -Math.toRadians(par[4]);

        int n = 2 * getWiggles();
        if (isSymmetric()) {
            n += 1;
        }

        float amp = -0.5f * getAmp();

        final float cos = (float) Math.cos(sa);
        final float sin = (float) Math.sin(sa);

        final float delta = oa / n;
        final float cp = (float) Math.cos(delta);
        final float sp = (float) Math.sin(delta);
        final float cp2 = (float) Math.cos(delta / 2.f);
        final float sp2 = (float) Math.sin(delta / 2.f);

        gp.moveTo(getX(), getY());

        float thetaj;
        float cs, sn, beta, tt, amp1;
        float x1, y1, x2, y2, x3, y3;

        for (int j = 1; j <= n; j++) {
            thetaj = (j - 1) * delta;
            amp = -amp;

            cs = (float) Math.cos(thetaj);
            sn = (float) Math.sin(thetaj);

            beta = (float) (r * oa / Math.PI / amp / n);
            tt = (sp - cp * beta) / (cp + sp * beta);
            amp1 = (8.f * (r + amp) * (beta * cp2 - sp2)
                    - (beta * (4.f + cp) + 3.f * tt * cp - 4.f * sp) * r)
                    / (3.f * (beta - tt));

            x1 = (8.f * (r + amp) * cp2 - (1.f + cp) * r) / 3.f - amp1;
            y1 = (x1 - r) * beta;
            x2 = amp1;
            y2 = (amp1 - r * cp) * tt + r * sp;
            x3 = r * cp;
            y3 = r * sp;

            curveTo(gp, x1 * cs - y1 * sn, x1 * sn + y1 * cs,
                x2 * cs - y2 * sn, x2 * sn + y2 * cs,
                x3 * cs - y3 * sn, x3 * sn + y3 * cs, cos, sin, cx, cy);
        }
    }

    private void curveTo(final GeneralPath gp, final float x1, final float y1, final float x2, final float y2,
            final float x3, final float y3, final float cos, final float sin, final float cx, final float cy) {

        gp.curveTo(x1 * cos - y1 * sin + cx, x1 * sin + y1 * cos + cy,
                x2 * cos - y2 * sin + cx, x2 * sin + y2 * cos + cy,
                x3 * cos - y3 * sin + cx, x3 * sin + y3 * cos + cy);
    }

    private void appendPhotonLine(final GeneralPath gp) {
        if (isDoubleLine()) {
            final float dlsep = getDLSeparation() / 2.f;
            appendFullLine(gp, dlsep);
            appendFullLine(gp, -dlsep);
        } else {
            appendFullLine(gp, 0.f);
        }
    }

    // should be re-used from PLine
    private void appendFullLine(final GeneralPath gp, final float sep) {
        final float length = (float) Math.sqrt(((getX3() - getX()) * (getX3() - getX()))
                + ((getY3() - getY()) * (getY3() - getY())));

        int n = 2 * getWiggles();
        if (isSymmetric()) {
            n += 1;
        }

        final float ts = length / n;

        final float sin = (getY3() - getY()) / length;
        final float cos = (getX3() - getX()) / length;

        final float c = 4.f / 3.f * ts / (float) Math.PI;
        float amp = -2.f / 3.f * getAmp();

        gp.moveTo(getX(), getY() + sep);
        float x0;

        for (int i = 1; i <= n; i++) {
            x0 = (i - 1) * ts;
            curveTo(gp, x0 + c, amp + sep, x0 + ts - c, amp + sep, x0 + ts, sep,
                    cos, sin, getX(), getY());
            amp = -amp;
        }
    }

    // Get the axodraw4j options set for this line

    private String getOptionsCommand(final boolean clockwise) {

        final StringBuffer buffer = new StringBuffer(128);

        if (isDoubleLine()) {
            final String dlsep = D_FORMAT.format(getDLSeparation());
            buffer.append("double,sep=").append(dlsep);
        }

        if (clockwise) {
            if (buffer.length() > 0) {
                buffer.append(',');
            }

            buffer.append("clock");
        }

        if (buffer.length() > 0) {
            buffer.insert(0, '[');
            buffer.append(']');
        }

        return buffer.toString();
    }
}
