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

import java.awt.BasicStroke;
import java.awt.Dimension;

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

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


/** A JaxoObject with at least two points.
 * @since 2.0
 */
public abstract class JaxoExtendedObject extends JaxoObject {

    /** A point with coordinates (x,y2). */
    public static final int SELECT_DX = -3;

    /** A point with coordinates (x2,y). */
    public static final int SELECT_DY = -4;

    /** Second point (index 1). */
    public static final int SELECT_P2 = 1;

    static {
        setTransient(JaxoExtendedObject.class, new String[] {"x2", "y2"});
    }

    private static final long serialVersionUID = 2L;
    private static final int POINT_COUNT = 2;

      //
     // bean properties: to be serialized to XML
    //

    /** The x coordinate of the second point of this JaxoObject.*/
    private int x2;

    /** The y coordinate of the second point of this JaxoObject.*/
    private int y2;

    /** The stroke (line width) of this extended object.*/
    private float strokeWidth;

      //
     // transients will not be serialized to XML!
    //

    private transient BasicStroke stroke = new BasicStroke();

    private void readObject(final ObjectInputStream in)
        throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        stroke = new BasicStroke();
    }

      //
     // Bean getter and setter methods
    //

    /** {@inheritDoc}
     * @return 2.
     */
    @Override
    public int getPointCount() {
        return POINT_COUNT;
    }

    /** {@inheritDoc} */
    @Override
    public int getX(final int index) {
        if (index == SELECT_P2) {
            return x2;
        }
        return super.getX(index);
    }

    /** {@inheritDoc} */
    @Override
    public int getY(final int index) {
        if (index == SELECT_P2) {
            return y2;
        }
        return super.getY(index);
    }

    /** {@inheritDoc} */
    @Override
    public void setX(final int index, final int value) {
        if (index == SELECT_P2) {
            x2 = value;
        } else {
            super.setX(index, value);
        }
    }

    /** {@inheritDoc} */
    @Override
    public void setY(final int index, final int value) {
        if (index == SELECT_P2) {
            y2 = value;
        } else {
            super.setY(index, value);
        }
    }

    /** Returns the x2 coordinate of this object.
     * @return The x2 coordinate of this object.
     */
    public final int getX2() {
        return getX(SELECT_P2);
    }

    /** Sets the x2 coordinate of this object.
     * @param newX2 The x2 coordinate of this object.
     */
    public final void setX2(final int newX2) {
        setX(SELECT_P2, newX2);
    }

    /** Returns the y2 coordinate of this object.
     * @return The y2 coordinate of this object.
     */
    public final int getY2() {
        return getY(SELECT_P2);
    }

    /** Sets the y2 coordinate of this object.
     * @param newY2 The y2 coordinate of this object.
     */
    public final void setY2(final int newY2) {
        setY(SELECT_P2, newY2);
    }

    /** Returns the stroke width property of this particle object.
     * @return The stroke width property of this particle object.
     */
    public final float getStrokeWidth() {
        return strokeWidth;
    }

    /** Sets the stroke width property of this particle object.
     * @param newStroke The stroke width property of this particle object.
     */
    public void setStrokeWidth(final float newStroke) {
        final float old = getStrokeWidth();
        this.strokeWidth = newStroke;
        resetStroke();
        firePropertyChange("strokeWidth", Float.valueOf(old), Float.valueOf(newStroke));
    }

      //
     // convenience methods
    //

    /** Returns the width of this object.
     * @return The width of this object.
     */
    @Override
    public final int getWidth() {
        return Math.abs(getRelw());
    }

    /** Returns the height of this object.
     * @return The height of this object.
     */
    @Override
    public final int getHeight() {
        return Math.abs(getRelh());
    }

    /** Returns the relative width of this object.
     * @return The relative width of this object.
     */
    public final int getRelw() {
        return getX2() - getX();
    }

    /** Returns the relative height of this object.
     * @return The relative height of this object.
     */
    public final int getRelh() {
        return getY2() - getY();
    }

    /** Sets the relative width and height of this object.
     * @param w The relative width of this object.
     * @param h The relative height of this object.
     */
    public final void setRelWAndH(final int w, final int h) {
        setX2(getX() + w);
        setY2(getY() + h);
    }

    /** Sets the relative width of this object.
     * @param w The relative width of this object.
     * @deprecated unused. Use {@link #setX2(int)} or {@link #setRelWAndH(int,int)} instead.
     */
    public final void setRelativeWidth(final int w) {
        setX2(getX() + w);
    }

    /** Sets the relative height of this object.
     * @param h The relative heigh of this object.
     * @deprecated unused. Use {@link #setY2(int)} or {@link #setRelWAndH(int,int)} instead.
     */
    public final void setRelativeHeight(final int h) {
        setY2(getY() + h);
    }

    /**
     * Convenience method for setting the 'radius' of a 2 point object.
     * This keeps the first point of this object fixed and adjusts the second
     * point such that the distance between the two is newRadius.
     *
     * @param newRadius The radius to set.
     */
    public void setRadius(final float newRadius) {
        final float oldrelw = getRelw();
        final float oldrelh = getRelh();
        final double oldR = Math.sqrt((oldrelw * oldrelw) + (oldrelh * oldrelh));

        if (JaxoUtils.zero(oldR)) {
            setRelWAndH(Math.round(newRadius), 0);
        } else {
            final int newRelW = (int) Math.round((newRadius / oldR) * oldrelw);
            final int newRelH = (int) Math.round((newRadius / oldR) * oldrelh);
            setRelWAndH(newRelW, newRelH);
        }
    }

    /** Convenience method for getting the radius of  a 2 point object,
     * ie the distance between the first two points.
     * @return The radius.
     */
    public double getRadius() {
        return Math.sqrt((getWidth() * getWidth())
            + (getHeight() * getHeight()));
    }

    /** Sets the position coordinates of this object.
     * @param newX The x coordinate of this object.
     * @param newY The y coordinate of this object.
     * @param newX2 The x2 coordinate of this object.
     * @param newY2 The y2 coordinate of this object.
     */
    public final void setLocation(final int newX, final int newY, final int newX2, final int newY2) {
        setLocation(newX, newY);
        setX2(newX2);
        setY2(newY2);
    }

    /** {@inheritDoc} */
    @Override
    public void moveBy(final int deltaX, final int deltaY) {
        if ((deltaX != 0) || (deltaY != 0)) {
            super.moveBy(deltaX, deltaY);
            setX2(getX2() + deltaX);
            setY2(getY2() + deltaY);
        }
    }

    /** Returns the width and height of this object.
     * @return A dimension with the size of the object.
     */
    public final Dimension getSize() {
        return new Dimension(getWidth(), getHeight());
    }

    /** Returns the relative width and height of this object.
     * @return A dimension with the relative size of the object.
     */
    public final Dimension getRelSize() {
        return new Dimension(getRelw(), getRelh());
    }

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

        if (comp instanceof JaxoExtendedObject) {
            final JaxoExtendedObject tmp = (JaxoExtendedObject) comp;
            if ((tmp.getX2() == getX2()) && (tmp.getY2() == getY2())
                    && tmp.strokeIs(getStrokeWidth()) && super.isCopy(tmp)) {
                isCopy = true;
            }
        }

        return isCopy;
    }

    /** Sets all parameters from the given object to the current one.
     * @param temp The object to copy from.
     */
    public void copyFrom(final JaxoExtendedObject temp) {
        super.copyFrom(temp);
        setX2(temp.getX2());
        setY2(temp.getY2());
        setStrokeWidth(temp.getStrokeWidth());
    }

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

    /** {@inheritDoc} */
    @Override
    public float smallestDistanceTo(final int px, final int py) {
        final float dist1 = super.smallestDistanceTo(px, py);
        final int distX = px - getX2();
        final int distY = py - getY2();
        final float dist2 = (float) Math.sqrt((distX * distX) + (distY * distY));
        return (dist1 < dist2) ? dist1 : dist2;
    }

    /** {@inheritDoc} */
    @Override
    public int getGrabbedHandle(final int clickX, final int clickY, final JaxoHandle h) {
        int selected = super.getGrabbedHandle(clickX, clickY, h);

        if (isAround(SELECT_P2, clickX, clickY, h)) {
            selected = SELECT_P2;
        }

        return selected;
    }

    /** {@inheritDoc} */
    public void paintHandles(final JaxoGraphics2D g2, final JaxoHandle h, final int editMode) {
        if (editMode == JaxoConstants.UNGROUP) {
            return;
        }

        h.paint(g2, getX(), getY(), isMarked(), !canBeSelected(SELECT_P1, editMode));
        h.paint(g2, getX2(), getY2(), isMarked(), !canBeSelected(SELECT_P2, editMode));
    }

    /** {@inheritDoc} */
    public boolean canBeSelected(final int handle, final int mode) {
        boolean active = ((handle == SELECT_P1) || (handle == SELECT_P2));
        if (mode == JaxoConstants.RESIZE) {
            active = (handle == SELECT_P2);
        }
        return active;
    }

    /** {@inheritDoc} */
    public String latexWidth() {
        final String thisValue = Float.toString(getStrokeWidth());

        return "\\SetWidth{".concat(thisValue.concat("}"));
    }

    /** {@inheritDoc} */
    @Override
    public void setPreferences() {
        super.setPreferences();
        setStrokeWidth(JaxoPrefs.getFloatPref(JaxoPrefs.PREF_LINEWIDTH));
    }

      //
     // comparison methods for float properties
    //

    /** Compares the stroke of this JaxoExtendedObject to the
     * given float, taking into account a 0.1% error margin.
     * @param comp The float to compare to.
     * @return True, if the stroke matches within 0.1%.
     */
    public final boolean strokeIs(final float comp) {
        return JaxoUtils.equal(comp, getStrokeWidth());
    }

    /** Returns the stroke of this particle object.
     * @return The stroke of this particle object.
     */
    protected final BasicStroke getStroke() {
        return stroke;
    }

    /** Sets the stroke of this particle object.
     * @param newStroke The strokeof this particle object.
     */
    protected final void setStroke(final BasicStroke newStroke) {
        this.stroke = newStroke;
    }

    /** Resets the stroke to a default BasicStroke with current width.
     * This should be overridden by objects that use a different stroke.
     */
    protected void resetStroke() {
        setStroke(new BasicStroke(getStrokeWidth()));
    }
}
