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

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Rectangle;

import net.sf.jaxodraw.object.JaxoExtendedObject;
import net.sf.jaxodraw.object.JaxoHandle;
import net.sf.jaxodraw.object.JaxoList;
import net.sf.jaxodraw.object.JaxoObject;
import net.sf.jaxodraw.object.JaxoObjectEditPanel;
import net.sf.jaxodraw.object.JaxoObjectList;
import net.sf.jaxodraw.object.JaxoWiggleObject;
import net.sf.jaxodraw.object.text.JaxoLatexText;
import net.sf.jaxodraw.object.text.JaxoPSText;
import net.sf.jaxodraw.util.JaxoPrefs;
import net.sf.jaxodraw.util.graphics.JaxoGraphics2D;


/** Groups together a number of JaxoObjects.
 * @since 2.0
 */
public class JaxoGroup extends JaxoExtendedObject {
    private static final long serialVersionUID = 2L;

    /** The default amplitude of all wiggle objects in this group. */
    private int groupAmp;

    /** The default size of the TeX labels of this group. */
    private int groupTeXSize;

    /** The default font of the PS labels of this group. */
    private Font groupTextFont;

    /** The list of JaxoObjects in this group. */
    private JaxoList<JaxoObject> objectList;

    /** Constructor: creates a new empty vector and sets the dimensions
     *  to a default value.
     */
    public JaxoGroup() {
        super();
        this.objectList = new JaxoObjectList<JaxoObject>(5);
    }

    /** Returns an exact copy of this JaxoGroup.
     * @return A copy of this JaxoGroup.
     */
    @Override
    public final JaxoObject copy() {
        final JaxoGroup copy = (JaxoGroup) super.copy();

        copy.setFont(getFont());
        copy.setLatexTextSize(getLatexTextSize());
        copy.setAmp(getAmp());
        copy.setObjectList(objectList.copyOf());

        return copy;
    }

    /** {@inheritDoc} */
    @Override
    public final boolean isCopy(final JaxoObject comp) {
        // Two groups are equal if they contain equal objects
        // at equal positions in the the object list vector.
        boolean isCopy = false;

        if (comp instanceof JaxoGroup) {
            final JaxoGroup group = (JaxoGroup) comp;
            final JaxoList<JaxoObject> v1 = getObjectList();
            final JaxoList<JaxoObject> v2 = group.getObjectList();
            final int length = v1.size();
            if (length == v2.size()) {
                boolean temp = true;
                for (int i = 0; i < length; i++) {
                    final JaxoObject ob1 = v1.get(i);
                    final JaxoObject ob2 = v2.get(i);
                    temp = temp && ob1.isCopy(ob2);
                }
                final boolean equalTexFonts = (getFont() == null
                    ? group.getFont() == null
                    : getFont().equals(group.getFont()));
                isCopy = temp && equalTexFonts
                    && (group.getLatexTextSize() == getLatexTextSize())
                    && (group.getAmp() == getAmp()) && super.isCopy(group);
            }
        }

        return isCopy;
    }

    /** Sets all parameters from the given object to the current one.
     * @param temp The object to copy from.
     */
    public void copyFrom(final JaxoGroup temp) {
        super.copyFrom(temp);
        setFont(temp.getFont());
        setLatexTextSize(temp.getLatexTextSize());
        setAmp(temp.getAmp());

        clearGroup();
        setObjectList(temp.getObjectList().copyOf());
    }


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

    /** {@inheritDoc} */
    @Override
    public final int getGrabbedHandle(final int clickX, final int clickY, final JaxoHandle h) {
        final Rectangle bBox = getBounds();
        int selected = SELECT_NONE;

        final int gx = bBox.x;
        final int gy = bBox.y;
        final int gx2 = bBox.x + bBox.width;
        final int gy2 = bBox.y + bBox.height;

        if (h.contains(clickX, clickY, gx2, gy2)) {
            selected = SELECT_P2;
        } else if (h.contains(clickX, clickY, gx, gy2)) {
            selected = SELECT_DX;
        } else if (h.contains(clickX, clickY, gx2, gy)) {
            selected = SELECT_DY;
        } else if (h.contains(clickX, clickY, gx, gy)) {
            selected = SELECT_P1;
        }

        return selected;
    }

    /** {@inheritDoc} */
    @Override
    public boolean canBeSelected(final int handle, final int mode) {
        return ((handle == SELECT_P1) || (handle == SELECT_P2)
        || (handle == SELECT_DX) || (handle == SELECT_DY));
    }

    /** {@inheritDoc} */
    @Override
    public final void paintHandles(final JaxoGraphics2D g2, final JaxoHandle h,
        final int editMode) {
        final Rectangle bBox = getBounds();

        final int gx1 = bBox.x;
        final int gy1 = bBox.y;
        final int gx2 = bBox.x + bBox.width;
        final int gy2 = bBox.y + bBox.height;

        h.paint(g2, gx1, gy1, isMarked(), !canBeSelected(SELECT_P1, editMode));
        h.paint(g2, gx1, gy2, isMarked(), !canBeSelected(SELECT_P2, editMode));
        h.paint(g2, gx2, gy1, isMarked(), !canBeSelected(SELECT_DX, editMode));
        h.paint(g2, gx2, gy2, isMarked(), !canBeSelected(SELECT_DY, editMode));

    }

    /** {@inheritDoc} */
    @Override
    public final float smallestDistanceTo(final int px, final int py) {
        final Rectangle bBox = getBounds();
        final int x = bBox.x;
        final int y = bBox.y;
        final int w = bBox.width;
        final int h = bBox.height;

        int distX = px - x;
        int distY = py - y;
        final float dist1 = (float) Math.sqrt((distX * distX) + (distY * distY));
        distX = px - x;
        distY = py - y - h;
        final float dist2 = (float) Math.sqrt((distX * distX) + (distY * distY));
        distX = px - x - w;
        distY = py - y;
        final float dist3 = (float) Math.sqrt((distX * distX) + (distY * distY));
        distX = px - x - w;
        distY = py - y - h;
        final float dist4 = (float) Math.sqrt((distX * distX) + (distY * distY));
        return smallest(dist1, dist2, dist3, dist4);
    }

    private float smallest(final float dist1, final float dist2, final float dist3, final float dist4) {
        if (dist1 < dist2) {
            return smallest(dist1, dist3, dist4);
        } else {
            return smallest(dist2, dist3, dist4);
        }
    }

    private float smallest(final float dist1, final float dist2, final float dist3) {
        float dist = 0.f;
        if (dist1 < dist2) {
            if (dist1 < dist3) {
                dist = dist1;
            } else {
                dist = dist3;
            }
        } else {
            if (dist2 < dist3) {
                dist = dist2;
            } else {
                dist = dist3;
            }
        }
        return dist;
    }

    /** {@inheritDoc} */
    @Override
    public final void paintVisualAid(final JaxoGraphics2D g2) {
        final Rectangle bBox = getBounds();

        g2.drawRect(bBox.x, bBox.y, bBox.width, bBox.height);
    }

    /** {@inheritDoc} */
    public final void paint(final JaxoGraphics2D g2) {
        objectList.paint(g2);
    }

    /** Displaces the group.
     * @param deltaX The displacement in x direction
     * @param deltaY The displacement in y direction
     */
    @Override
    public final void moveBy(final int deltaX, final int deltaY) {
        if ((deltaX == 0) && (deltaY == 0)) {
            return;
        }

        super.moveBy(deltaX, deltaY);

        objectList.moveAllObjects(deltaX, deltaY);

        // why is this necessary?
        final Rectangle bBox = getBounds();
        setX(bBox.x);
        setY(bBox.y);
        setX2(bBox.x + bBox.width);
        setY2(bBox.y + bBox.height);
    }

    /** The latex command of this group (obsolete because every JaxoObject
     * has its own latex command).
     * @param scale A scale factor to translate Java coordinates to
     * LaTeX coordinates.
     * @param canvasDim The current dimension of the canvas.
     * @return The string "%"
     */
    public final String latexCommand(final float scale, final Dimension canvasDim) {
        return "%";
    }

    /** The latex command setting the width of this group
     * (obsolete because every JaxoObject has its own latexWidth).
     * @return The string "%".
     */
    @Override
    public final String latexWidth() {
        return "%";
    }

    /**
     * Returns the bounding box of this object.
     * Note that this returns null if the group is empty.
     *
     * @return the bounding box of this object.
     */
    public Rectangle getBounds() {
        return objectList.getBounds();
    }

    /** Removes all JaxoObjects from the Group. */
    public final void clearGroup() {
        objectList.clear();
    }

    /** Returns the JaxoObjects in this group.
     * @return A list containing all the JaxoObjects of this group
     */
    public final JaxoList<JaxoObject> getObjectList() {
        return objectList;
    }

    /** Sets the JaxoObjects of this group.
     * @param obList A list containing all the JaxoObjects to be set
     * for this group
     */
    public final void setObjectList(final JaxoList<JaxoObject> obList) {
        this.objectList = new JaxoObjectList<JaxoObject>(obList);
    }

    /** Returns the number of JaxoObjects in this group.
     * @return The size of the list of JaxoObjects in this group
     */
    public final int size() {
        return objectList.size();
    }

    /** {@inheritDoc} */
    public final void rescaleObject(final int orx, final int ory, final float scale) {
        for (int i = 0; i < this.size(); i++) {
            final JaxoObject ob = this.getObjectList().get(i);
            ob.rescaleObject(orx, ory, scale);
        }

        final Rectangle bBox = getBounds();
        setLocation(bBox.x, bBox.y, bBox.x + bBox.width, bBox.y + bBox.height);
    }

    /**
     * Rescales the group with the given scale factor,
     * leaving the first point fixed.
     *
     * @param scale the scale factor.
     */
    public final void setNewScale(final float scale) {
        rescaleObject(getX(), getY(), scale);
    }

    /**
     * Sets the strokeWidth property of this group object.
     * @param newStroke The strokeWidth property of this group object.
     */
    @Override
    public final void setStrokeWidth(final float newStroke) {
        super.setStrokeWidth(newStroke);

        final Float stroke = Float.valueOf(newStroke);

        for (int i = 0; i < this.size(); i++) {
            final JaxoObject ob = this.getObjectList().get(i);
            ob.setParameter("strokeWidth", Float.TYPE, stroke, false);
        }
    }

    /** Returns the groupAmp property of this group object.
     * @return The groupAmp property of this group object.
     */
    public final int getAmp() {
        return groupAmp;
    }

    /** Sets the groupAmp property of this group object. If amp != 0,
     * applies the value to all WiggleObjects in the group.
     * @param amp The groupAmp property of this group object.
     */
    public final void setAmp(final int amp) {
        this.groupAmp = amp;

        if (amp == 0) {
            return;
        }

        final Integer amplitude = Integer.valueOf(amp);

        for (int i = 0; i < this.size(); i++) {
            final JaxoObject ob = this.getObjectList().get(i);
            ob.setParameter("amp", Integer.TYPE, amplitude, false);
        }
    }

    /** Returns the groupTeXSize property of this group object.
     * @return The groupTeXSize property of this group object.
     */
    public final int getLatexTextSize() {
        return groupTeXSize;
    }

    /** Sets the groupTeXSize property of this group object. If teXSize != 0,
     * applies the value to all LatexText objects in the group.
     * @param teXSize The groupTeXSize property of this group object.
     */
    public final void setLatexTextSize(final int teXSize) {
        this.groupTeXSize = teXSize;

        if (teXSize == 0) {
            return;
        }

        final Integer size = Integer.valueOf(teXSize);

        for (int i = 0; i < this.size(); i++) {
            final JaxoObject ob = this.getObjectList().get(i);
            ob.setParameter("latexTextSize", Integer.TYPE, size, false);
        }
    }

    /** Returns the groupTextFont property of this text object.
     * @return The groupTextFont property of this text object.
     */
    public final Font getFont() {
        return groupTextFont;
    }

    /** Sets the groupTextFont property of this group. If textFont != null,
     * applies the value to all PSText objects in the group.
     * @param textFont The groupTextFont property of this group object.
     */
    public final void setFont(final Font textFont) {
        this.groupTextFont = textFont;

        if (textFont == null) {
            return;
        }

        for (int i = 0; i < this.size(); i++) {
            final JaxoObject ob = getObjectList().get(i);
            ob.setParameter("font", Font.class, textFont, false);
        }
    }

    /**
     * Sets the font name of this group. If the current font is null,
     * the style and size of the font are taken from the preferences.
     *
     * @param name the font name to set.
     */
    public final void setFontName(final String name) {
        final Font oldFont = getFont();
        Font newFont;
        if (oldFont == null) {
            newFont = new Font(name,
                JaxoPrefs.getIntPref(JaxoPrefs.PREF_PSSTYLE),
                JaxoPrefs.getIntPref(JaxoPrefs.PREF_PSSIZE));
        } else {
            newFont = new Font(name, oldFont.getStyle(), oldFont.getSize());
        }
        setFont(newFont);
    }

    /**
     * Sets the font style of this group. If the current font is null,
     * the name and size of the font are taken from the preferences.
     *
     * @param style the font style to set.
     */
    public final void setFontStyle(final int style) {
        final Font oldFont = getFont();
        Font newFont;
        if (oldFont == null) {
            newFont = new Font(JaxoPrefs.getStringPref(JaxoPrefs.PREF_PSFAMILY),
                style, JaxoPrefs.getIntPref(JaxoPrefs.PREF_PSSIZE));
        } else {
            newFont = new Font(oldFont.getName(), style, oldFont.getSize());
        }
        setFont(newFont);
    }

    /**
     * Sets the font size of this group. If the current font is null,
     * the style and name of the font are taken from the preferences.
     *
     * @param size the font size to set.
     */
    public final void setFontSize(final int size) {
        final Font oldFont = getFont();
        Font newFont;
        if (oldFont == null) {
            newFont = new Font(JaxoPrefs.getStringPref(JaxoPrefs.PREF_PSFAMILY),
                JaxoPrefs.getIntPref(JaxoPrefs.PREF_PSSTYLE), size);
        } else {
            newFont = new Font(oldFont.getName(), oldFont.getStyle(), size);
        }
        setFont(newFont);
    }

    /** Sets the color for all objects in this group.If newColor != null,
     * applies the value to all objects in the group.
     * @param newColor The color to be set.
     */
    @Override
    public final void setColor(final Color newColor) {
        super.setColor(newColor);

        if (newColor == null) {
            return;
        }

        for (int i = 0; i < this.size(); i++) {
            final JaxoObject ob = this.getObjectList().get(i);

            if (!(ob instanceof JaxoLatexText)) { // why not?
                ob.setColor(newColor);
            }
        }
    }

    /**
     * Determines whether there are instances of the given Class in this group.
     *
     * @param clazz a Class to look for.
     * @return True if the group contains any Object ob for which
     *      clazz.isInstance(ob) returns true.
     */
    public final boolean containsInstanceOf(final Class<?> clazz) {
        boolean thereare = false;

        for (int i = 0; i < this.size(); i++) {
            final JaxoObject ob = this.getObjectList().get(i);

            if (ob instanceof JaxoGroup) {
                thereare = ((JaxoGroup) ob).containsInstanceOf(clazz);
                if (thereare) {
                    break;
                }
            } else if (clazz.isInstance(ob)) {
                thereare = true;

                break;
            }
        }

        return thereare;
    }

    /** {@inheritDoc} */
    public void prepareEditPanel(final JaxoObjectEditPanel editPanel) {
        prepareGroupPanel(editPanel);
    }

    private void prepareGroupPanel(final JaxoObjectEditPanel editPanel) {

        editPanel.addPositionPanel(getX(), getY(), 0, 0);
        editPanel.addReScalePanel(1, 0);
        editPanel.addColorPanel(getColor(), JaxoObjectEditPanel.TYPE_LINE_COLOR, 2, 0);

        if (containsInstanceOf(JaxoExtendedObject.class)) {
            editPanel.addStrokePanel(getStrokeWidth(), 0, 1);
        }

        if (containsInstanceOf(JaxoWiggleObject.class)) {
            editPanel.addWigglePanel(getAmp(), 1, 1);
        }

        if (containsInstanceOf(JaxoLatexText.class)) {
            editPanel.addLatexFontSizePanel(getLatexTextSize(), 2, 1);
        }

        if (containsInstanceOf(JaxoPSText.class)) {
            editPanel.addPSFontPanel(getFont(), 0, 2, 3);
        }

        editPanel.setTitleAndIcon("Group_parameters", "group.png");
    }

}
