/*
 * @(#)ArrowTip.java 5.1
 *
 */

package CH.ifa.draw.figures;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.io.IOException;
import java.io.Serial;

import CH.ifa.draw.util.ColorMap;
import de.renew.draw.storables.ontology.StorableInput;
import de.renew.draw.storables.ontology.StorableOutput;

/**
 * An arrow tip line decoration.
 * <p>
 * {@link PolyLineFigure}
 */
public class ArrowTip implements LineDecoration {
    /*
     * Serialization support.
     */
    @Serial
    private static final long serialVersionUID = -3459171428373823638L;
    /**
     * Angle at which the sides of the arrow tip spread out (pointiness).
     */
    protected double fAngle;

    /**
     * Specifies the length of the sides of the arrow tip.
     */
    protected double fOuterRadius;

    /**
     * Specifies the length of the middle line of the arrow tip.
     */
    protected double fInnerRadius;

    /**
     * Whether the arrow tip is filled.
     */
    protected boolean _filled;

    /**
     * Serial number for the serialized data format of ArrowTip.
     */
    @SuppressWarnings("unused")
    private final int _arrowTipSerializedDataVersion = 1;

    /**
     * Constructs a new default ArrowTip with the angle 0.40, outer and inner radius 8 and that is filled.
     */
    public ArrowTip() {
        fAngle = 0.40; //0.35;
        fOuterRadius = 8; //15;
        fInnerRadius = 8; //12;
        _filled = true;
    }

    /**
     * Constructs an ArrowTip with the given angle and radius.
     *
     * @param angle the angle of the arrow tip.
     * @param outerRadius the outer radius of the arrow tip.
     * @param innerRadius the inner radius of the arrow tip.
     * @param filled if the arrow tip is filled or not.
     */
    public ArrowTip(double angle, double outerRadius, double innerRadius, boolean filled) {
        fAngle = angle;
        fOuterRadius = outerRadius;
        fInnerRadius = innerRadius;
        _filled = filled;
    }

    /**
     * Draws the arrow tip in the direction specified by the given two
     * points.
     */
    @Override
    public void draw(Graphics g, int x1, int y1, int x2, int y2, Color fillColor, Color lineColor) {
        // TBD: reuse the Polygon object
        Shape p = outline(x1, y1, x2, y2);

        // if filled, fill with lineColor.
        if (_filled) {
            fillColor = lineColor;
        }

        if (!ColorMap.isTransparent(fillColor)) {
            g.setColor(fillColor);
            ((Graphics2D) g).fill(p);
        }

        g.setColor(lineColor);
        ((Graphics2D) g).draw(p);

    }

    /**
     * Calculates the outline of an arrow tip in the direction specified by the two points (x1,y1) and (x2,y2).
     *
     * @param x1 the x coordinate of the first point
     * @param y1 the y coordinate of the first point
     * @param x2 the x coordinate of the second point
     * @param y2 the y coordinate of the second point
     * @return the outline the arrow tip.
     */
    public Shape outline(int x1, int y1, int x2, int y2) {
        double dir = Math.PI / 2 - Math.atan2(x2 - x1, y1 - y2);
        return outline(x1, y1, dir);
    }

    /**
     * Calculates the outline of an arrow tip in the direction specified by
     * an x and y coordinate and an angle as direction in rad.
     *
     * @param x starting point of arrow tip
     * @param y starting point of arrow tip
     * @param direction Direction of arrow tip in rad
     * @return GeneralPath outlining the arrow tip
     */
    protected Shape outline(int x, int y, double direction) {
        GeneralPath shape = new GeneralPath();

        shape.moveTo(x, y);
        addPointRelative(shape, x, y, fOuterRadius, direction - fAngle);
        addPointRelative(shape, x, y, fInnerRadius, direction);
        addPointRelative(shape, x, y, fOuterRadius, direction + fAngle);
        shape.closePath();

        return shape;
    }

    /**
     * Adds a point to the given shape by calculating a position relative to a given point (x, y),
     * using the specified radius and angle (in radians). The new point is connected to the given shape
     * with a line.
     *
     * @param shape Shape from where the line should start
     * @param x X-coordinate of the reference point
     * @param y Y-coordinate of the reference point
     * @param radius Distance from the reference point
     * @param angle Direction of the new point given in rad
     */
    public void addPointRelative(GeneralPath shape, int x, int y, double radius, double angle) {
        shape.lineTo(x + (int) (radius * Math.cos(angle)), y - (int) (radius * Math.sin(angle)));
    }

    /**
     * Stores the arrow tip to a StorableOutput.
     */
    @Override
    public void write(StorableOutput dw) {
        dw.writeDouble(fAngle);
        dw.writeDouble(fOuterRadius);
        dw.writeDouble(fInnerRadius);
        dw.writeBoolean(_filled);
    }

    /**
     * Reads the arrow tip from a StorableInput.
     */
    @Override
    public void read(StorableInput dr) throws IOException {
        if (dr.getVersion() >= 5) {
            fAngle = dr.readDouble();
            fOuterRadius = dr.readDouble();
            fInnerRadius = dr.readDouble();
            _filled = dr.readBoolean();
        }
    }
}