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

package CH.ifa.draw.figures;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Arc2D;
import java.io.IOException;
import java.util.Vector;

import CH.ifa.draw.standard.BoxHandleKit;
import CH.ifa.draw.util.ColorMap;
import CH.ifa.draw.util.Geom;
import de.renew.draw.storables.ontology.Connector;
import de.renew.draw.storables.ontology.StorableInput;
import de.renew.draw.storables.ontology.StorableOutput;
import de.renew.draw.ui.ontology.FigureHandle;

/**
 * An elliptical pie or arc figure.  The figure represents an arc segment
 * of an ellipse.  If the figure is filled with some color, it represents a
 * pie segment of an ellipse.
 * <p>
 * Created: 13 Jul 2008
 *
 * @author Michael Duvigneau
 **/
public class PieFigure extends AttributeFigure {
    /*
     * Serialization support.
     */

    //private static final long serialVersionUID = ;
    //private int pieFigureSerializedDataVersion = 1;
    static final int START_ANGLE = 1;
    static final int END_ANGLE = 2;

    /**
     * Determines position and size of the elliptical arc or pie
     * specifying position and size of its bounding box.
     *
     * @serial
     **/
    private Rectangle _displayBox;

    /**
     * Determines angle (relative to displayBox) where the elliptical pie
     * or arc starts.  If <code>endAngle &lt; startAngle</code> then the arc
     * loops around 0 degrees.
     * The value of this field is always in the interval [0,360[.
     *
     * @serial
     **/
    private double _startAngle;

    /**
     * Determines angle (relative to displayBox) where the elliptical pie
     * or arc ends.  If <code>endAngle &lt; startAngle</code> then the arc
     * loops around 0 degrees.
     * The value of this field is always in the interval [0,360[.
     *
     * @serial
     **/
    private double _endAngle;

    /**
     * Creates a new PieFigure with both origin and corner Points at (0, 0) and
     * initial start and end angles of 180° and 90°.
     */
    public PieFigure() {
        this(new Point(0, 0), new Point(0, 0));
    }

    /**
     * Creates a new PieFigure with the given origin and corner Points.
     * The initial start and end angle are set to 180° and 90°, respectively.
     *
     * @param origin starting corner of the bounding box
     * @param corner opposite corner of the bounding box
     */
    public PieFigure(Point origin, Point corner) {
        this(origin, corner, 180, 90);
    }

    /**
     * Creates a new PieFigure with the given origin and corner Points, as well as start and end angles.
     *
     * @param origin starting corner of the bounding box
     * @param corner opposite corner of the bounding box
     * @param startAngle start angle in degrees
     * @param endAngle end angle in degrees
     */
    public PieFigure(Point origin, Point corner, double startAngle, double endAngle) {
        basicDisplayBox(origin, corner);
        setStartAngle(startAngle);
        setEndAngle(endAngle);
    }

    /**
     * Returns the start angle relative to the displayBox, in the interval [0,360[.
     *
     * @return start angle value of the PieFigure
     */
    public double getStartAngle() {
        return _startAngle;
    }

    /**
     * Sets the start angle of the PieFigure to the given value.
     * The angle is normalized to the interval [0,360[ and relative to the displayBox.
     * Also triggers a change notification.
     *
     * @param startAngle new start angle value in degrees
     */
    public void setStartAngle(double startAngle) {
        this._startAngle = normalizeAngle(startAngle);
        changed();
    }

    /**
     * Sets the given angle kind of the PieFigure to the given angle value.
     * The given new angle value is normalized to the interval [0,360[ and relative to the displayBox.
     * Also triggers a change notification.
     *
     * @param angleKind specifies if the start or end angle should be set
     * @param angle new angle value in degrees
     */
    public void setAngle(int angleKind, double angle) {
        switch (angleKind) {
            case START_ANGLE:
                setStartAngle(angle);
                break;
            case END_ANGLE:
                setEndAngle(angle);
                break;
            default:
                throw new IllegalArgumentException("Undefined angle kind: " + angleKind);
        }
    }

    /**
     * Returns the end angle relative to the displayBox, in the interval [0,360[.
     *
     * @return end angle of the PieFigure in degrees
     */
    public double getEndAngle() {
        return _endAngle;
    }

    /**
     * Sets the end angle of the PieFigure to the given value.
     * The given new angle value is normalized to the interval [0,360[ and relative to the displayBox.
     * Also triggers a change notification.
     *
     * @param endAngle new end angle in degrees
     */
    public void setEndAngle(double endAngle) {
        this._endAngle = normalizeAngle(endAngle);
        changed();
    }

    /**
     * Returns the angle of the given angle kind in degrees.
     * The returned value is relative to the displayBox and in the interval [0,360[.
     *
     * @param angleKind specifies if the start or end angle should be returned
     * @return value of the given angle kind in degrees
     */
    public double getAngle(int angleKind) {
        return switch (angleKind) {
            case START_ANGLE -> _startAngle;
            case END_ANGLE -> _endAngle;
            default -> throw new IllegalArgumentException("Undefined angle kind: " + angleKind);
        };
    }

    static double normalizeAngle(double angle) {
        // Duplicate execution of modulo is intended: inner modulo
        // operation may result negative values that are compensated by
        // adding 360 and computing modulo again
        return (360 + (angle % 360)) % 360;
    }

    @Override
    public Vector<FigureHandle> handles() {
        Vector<FigureHandle> handles = new Vector<>();
        handles.add(new PieAngleHandle(this, START_ANGLE));
        handles.add(new PieAngleHandle(this, END_ANGLE));
        BoxHandleKit.addHandles(this, handles);
        return handles;
    }

    @Override
    public void basicDisplayBox(Point origin, Point corner) {
        _displayBox = new Rectangle(origin);
        _displayBox.add(corner);
    }

    @Override
    public Rectangle displayBox() {
        return new Rectangle(_displayBox.x, _displayBox.y, _displayBox.width, _displayBox.height);
    }

    /**
     * Checks if a point is inside the figure.
     */
    @Override
    public boolean containsPoint(int x, int y) {
        if (super.containsPoint(x, y)) {
            return Geom.ellipseContainsPoint(displayBox(), x, y);
        } else {
            return false;
        }
    }

    @Override
    protected void basicMoveBy(int x, int y) {
        _displayBox.translate(x, y);
    }

    @Override
    public void drawBackground(Graphics g) {
        Rectangle r = displayBox();
        Graphics2D g2 = (Graphics2D) g;
        int arcType = Arc2D.PIE;
        double angleExtent = _endAngle - _startAngle;
        if (angleExtent < 0) {
            angleExtent += 360;
        }
        Shape s = new Arc2D.Double(r.x, r.y, r.width, r.height, _startAngle, angleExtent, arcType);
        g2.fill(s);

    }

    @Override
    public void drawFrame(Graphics g) {
        Rectangle r = displayBox();
        Graphics2D g2 = (Graphics2D) g;
        int arcType = Arc2D.PIE;
        if (ColorMap.isTransparent(getFillColor())) {
            arcType = Arc2D.OPEN;
        }
        double angleExtent = _endAngle - _startAngle;
        if (angleExtent < 0) {
            angleExtent += 360;
        }
        Shape s = new Arc2D.Double(r.x, r.y, r.width, r.height, _startAngle, angleExtent, arcType);
        g2.draw(s);
    }

    @Override
    public Insets connectionInsets() {
        Rectangle r = _displayBox;
        int cx = r.width / 2;
        int cy = r.height / 2;
        return new Insets(cy, cx, cy, cx);
    }

    @Override
    public Connector connectorAt(int x, int y) {
        return new ChopPieConnector(this);
    }

    @Override
    public void write(StorableOutput dw) {
        super.write(dw);
        dw.writeInt(_displayBox.x);
        dw.writeInt(_displayBox.y);
        dw.writeInt(_displayBox.width);
        dw.writeInt(_displayBox.height);
        dw.writeDouble(_startAngle);
        dw.writeDouble(_endAngle);
    }

    @Override
    public void read(StorableInput dr) throws IOException {
        super.read(dr);
        _displayBox = new Rectangle(dr.readInt(), dr.readInt(), dr.readInt(), dr.readInt());
        _startAngle = dr.readDouble();
        _endAngle = dr.readDouble();
    }
}