/**
 *  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.JaxoWiggleObject;
import net.sf.jaxodraw.object.line.JaxoGlLine;
import net.sf.jaxodraw.util.JaxoUtils;
import net.sf.jaxodraw.util.graphics.JaxoGraphics2D;


/** A gluon arc.
 * @since 2.0
 */
public class JaxoGlArc extends JaxoArcObject implements JaxoWiggleObject {
    private static final long serialVersionUID = 314159L;
    private transient float freq;

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

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

    /** {@inheritDoc} */
    @Override
    public void setState(final JaxoObject o) {
        if (o instanceof JaxoGlArc) {
            copyFrom((JaxoGlArc) 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 JaxoGlArc) {
            isCopy = super.isCopy(comp);
        }

        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 GluonArc, ignored!" + JaxoUtils.LINE_SEPARATOR;
        }

        final double nwin = (double) getWiggles();

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

        if (isOneLine()) {
            final JaxoGlLine line = new JaxoGlLine();
            line.setLocation(getX(), getY(), getX3(), getY3());
            line.setStrokeWidth(getStrokeWidth());
            line.setAmp(getAmp());
            line.setWiggles(getWiggles());
            line.setDoubleLine(isDoubleLine());
            line.setDLSeparation(getDLSeparation());
            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);

        final String options = getOptionsCommand(clockwise);

        return "\\GluonArc" + 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(nwin) + "}";
    }

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

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

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

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

    /** {@inheritDoc} */
    @Override
    public void setPreferences() {
        super.setPreferences();
        setWigglesFromFrequency(GLUON_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.addLineColorPanel(getColor(), 2, 1);

        editPanel.setTitleAndIcon("Gluon_arc_parameters", "gluonarc.png");
    }

      //
     //   private methods
    //

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

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

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

        final int wiggles = getWiggles();

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

        if (isOneLine()) {
            return gluonLine(path);
        }

        final double[] par = getArcParameters();
        final float cx = (float) par[0];
        final float cy = (float) par[1];
        final float radius = (float) par[2];
        final float oa = (float) Math.abs(Math.toRadians(par[3]));
        final float sa = (float) -Math.toRadians(par[4]);
        final boolean clockwise = (par[3] < 0.d);
        final float amp = 0.5f * getAmp();

        // draw the arc
        if (isDoubleLine()) {
            final float ds = getDLSeparation() / 2.f;

            appendFullArc(path, radius + ds, wiggles, amp, sa, oa, cx, cy, clockwise);
            appendFullArc(path, radius - ds, wiggles, amp, sa, oa, cx, cy, clockwise);
        } else {
            appendFullArc(path, radius, wiggles, amp, sa, oa, cx, cy, clockwise);
        }

        return path;
    }

    private void appendFullArc(final GeneralPath gp, final float rad, final int n,
            final float a, final float sa, final float oa, final float cx, final float cy, final boolean clock) {
        final float cos = (float) Math.cos(sa);
        final float sin = (float) Math.sin(sa);

        final float r = rad;

        final float absa = Math.abs(a);
        float thetaj;
        final float delta = oa / (n + 1);

        float y = -1.f;
        if (clock) {
            y = 1.f;
        }

        // head part
        gp.moveTo(r * cos + cx, r * sin + cy);
        polarCurveTo(gp,
                r + 0.5f * a,
                y * (0.05f * delta),
                (float) ((r + a) / Math.cos(0.45d * delta)),
                y * (0.25f * delta),
                r + a,
                y * (0.7f * delta),
                cos, sin, cx, cy);
        polarCurveTo(gp,
                (float) ((r + a) / Math.cos(0.3d * delta + 0.9d * absa / r)),
                y * (delta + 0.9f * absa / r),
                (float) ((r - a) / Math.cos(0.9d * absa / r)),
                y * (delta + 0.9f * absa / r),
                r - a,
                y * delta,
                cos, sin, cx, cy);

        // middle part
        for (int j = 2; j <= n; j++) {
            thetaj = (j - 1) * delta;
            polarCurveTo(gp,
                    (float) ((r - a) / Math.cos(0.9d * absa / r)),
                    y * (thetaj - 0.9f * absa / r),
                    (float) ((r + a) / Math.cos(0.5d * delta + 0.9d * absa / r)),
                    y * (thetaj - 0.9f * absa / r),
                    r + a,
                    y * (thetaj + 0.5f * delta),
                    cos, sin, cx, cy);
            polarCurveTo(gp,
                    (float) ((r + a) / Math.cos(0.5d * delta + 0.9d * absa / r)),
                    y * (thetaj + delta + 0.9f * absa / r),
                    (float) ((r - a) / Math.cos(0.9d * absa / r)),
                    y * (thetaj + delta + 0.9f * absa / r),
                    r - a,
                    y * (thetaj + delta),
                    cos, sin, cx, cy);
        }

        // tail part
        polarCurveTo(gp,
                (float) ((r - a) / Math.cos(0.9d * absa / r)),
                y * (oa - delta - 0.9f * absa / r),
                (float) ((r + a) / Math.cos(0.3d * delta + 0.9d * absa / r)),
                y * (oa - delta - 0.9f * absa / r),
                r + a,
                y * (oa - 0.7f * delta),
                cos, sin, cx, cy);
        polarCurveTo(gp,
                (float) ((r + a) / Math.cos(0.45d * delta)),
                y * (oa - 0.25f * delta),
                r + 0.5f * a,
                y * (oa - 0.05f * delta),
                r,
                y * oa,
                cos, sin, cx, cy);
    }

    private void polarCurveTo(final GeneralPath gp, final float r1, final float th1, final float r2, final float th2,
            final float r3, final float th3, final float cos, final float sin, final float cx, final float cy) {
        final float x1 = (float) (r1 * Math.cos(th1));
        final float y1 = (float) (r1 * Math.sin(th1));
        final float x2 = (float) (r2 * Math.cos(th2));
        final float y2 = (float) (r2 * Math.sin(th2));
        final float x3 = (float) (r3 * Math.cos(th3));
        final float y3 = (float) (r3 * Math.sin(th3));

        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 GeneralPath gluonLine(final GeneralPath gp) {
        if (isDoubleLine()) {
            final float sep = getDLSeparation() / 2.f;
            appendFullLine(gp, sep);
            appendFullLine(gp, -sep);
        } else {
            appendFullLine(gp, 0.f);
        }

        return gp;
    }

    // should be re-used from GlLine
    private void appendFullLine(final GeneralPath gp, final float sep) {
        final float length = (float) Math.sqrt(((getX3() - getX()) * (getX3() - getX()))
                + ((getY3() - getY()) * (getY3() - getY())));
        final int n = getWiggles();
        final float delta = length / (n + 1);
        final float amp = -0.5f * getAmp();
        final float absamp = Math.abs(amp);

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

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

        float x1 = 0.05f * delta;
        float x2 = 0.25f * delta;
        float x3 = 0.7f * delta;
        float y1 = 0.5f * amp + sep;
        float y2 = amp + sep;
        float y3 = amp + sep;

        curveTo(gp, x1, y1, x2, y2, x3, y3, cos, sin);

        x1 = delta + 0.9f * absamp;
        x2 = delta + 0.9f * absamp;
        x3 = delta;
        y1 = amp + sep;
        y2 = -amp + sep;
        y3 = -amp + sep;

        curveTo(gp, x1, y1, x2, y2, x3, y3, cos, sin);

        float xj;

        for (int j = 1; j < n; j++) {
            xj = j * delta;
            x1 = xj - 0.9f * absamp;
            x2 = xj - 0.9f * absamp;
            x3 = xj + 0.5f * delta;
            y1 = -amp + sep;
            y2 = amp + sep;
            y3 = amp + sep;
            curveTo(gp, x1, y1, x2, y2, x3, y3, cos, sin);

            x1 = xj + 0.9f * absamp + delta;
            x2 = xj + 0.9f * absamp + delta;
            x3 = xj + delta;
            y1 = amp + sep;
            y2 = -amp + sep;
            y3 = -amp + sep;
            curveTo(gp, x1, y1, x2, y2, x3, y3, cos, sin);
        }

        x1 = length - delta - 0.9f * absamp;
        x2 = length - delta - 0.9f * absamp;
        x3 = length - 0.7f * delta;
        y1 = -amp + sep;
        y2 = amp + sep;
        y3 = amp + sep;
        curveTo(gp, x1, y1, x2, y2, x3, y3, cos, sin);

        x1 = length - 0.25f * delta;
        x2 = length - 0.05f * delta;
        x3 = length;
        y1 = amp + sep;
        y2 = 0.5f * amp + sep;
        y3 = 0.f + sep;
        curveTo(gp, x1, y1, x2, y2, x3, y3, cos, sin);
    }

    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) {

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

    // 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();
    }

}
