package de.renew.gui;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Enumeration;
import java.util.Vector;

import CH.ifa.draw.standard.CompositeFigure;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.FigureChangeEvent;
import de.renew.draw.storables.ontology.FigureEnumeration;
import de.renew.draw.storables.ontology.StorableOutput;
import de.renew.draw.ui.ontology.DrawingView;
import de.renew.draw.ui.ontology.FigureHandle;

/**
 * A partial implementation of CompositeFigure which specifies a figure that is composed of several figures.
 * Does not yet have an implementation to determine when the layout needs to be adjusted.
 */
public abstract class SimpleCompositeFigure extends CompositeFigure {

    /**
     * Determines (reflects?) position and size of the
     * composite figure.
     * @serial
     **/
    protected Rectangle fDisplayBox = new Rectangle(0, 0, 0, 0);

    /**
     * Cache for handles of all included figures.
     * Initially <code>null</code>.
     * <p>
     * This field is transient, because its contents will
     * automatically be recalculated by {@link #handles()}.
     * </p>
     **/
    private transient Vector<FigureHandle> fHandles = null;

    /**
     * Additional ClickHandles.
     * @serial
     **/
    protected final Vector<ClickHandle> fClickHandles = new Vector<>();

    /**
     * Adds a ClickHandle.
     *
     * @param handle the ClickHandle to be added
     */
    public void addClickHandle(ClickHandle handle) {
        fClickHandles.addElement(handle);
        fHandles = null;
        handlesChanged();
    }

    /**
     * Moves the figure and all it's contained figures by x and y.
     *
     * @param x the horizontal distance to be moved
     * @param y the vertical distance to be moved
     */
    @Override
    protected void basicMoveBy(int x, int y) {
        fDisplayBox.translate(x, y);
        super.basicMoveBy(x, y);
    }

    /**
     * Moves the figure and all it's contained figures by x and y.
     *
     * @param x the horizontal distance to be moved
     * @param y the vertical distance to be moved
     */
    @Override
    public void moveBy(int x, int y) {
        willChange();
        basicMoveBy(x, y);
        changed();
    }

    /**
     * Returns a rectangle with the same size and position as the display box
     *
     * @return a rectangle with the same size and position as the display box
     */
    @Override
    public Rectangle displayBox() {
        return new Rectangle(fDisplayBox.x, fDisplayBox.y, fDisplayBox.width, fDisplayBox.height);
    }

    /**
     * Sets the DisplayBox to be a rectangle defined by the corners origin and corner
     *
     * @param origin one corner of the new DisplayBox
     * @param corner the other corner of the new DisplayBox
     */
    @Override
    public void basicDisplayBox(Point origin, Point corner) {
        fDisplayBox = new Rectangle(origin);
        fDisplayBox.add(corner);
        layout();
    }

    /**
     * Creates an empty vector that contains handles.
     * @return the new vector.
     */
    protected Vector<FigureHandle> basicHandles() {
        return new Vector<>();
    }

    /**
     * Returns a vector of all handles of the figure and all TokenHandles of contained figures.
     *
     * @return a vector of all handles of the figure and all TokenHandles of contained figures
     */
    @Override
    public Vector<FigureHandle> handles() {
        if (fHandles == null) {
            fHandles = basicHandles();
            FigureEnumeration k = figures();
            while (k.hasMoreElements()) {
                Figure figure = k.nextFigure();
                Enumeration<FigureHandle> kk = figure.handles().elements();
                while (kk.hasMoreElements()) {
                    FigureHandle handle = kk.nextElement();
                    if (handle instanceof TokenHandle) {
                        fHandles.addElement(handle);
                    }
                }
            }
            Enumeration<ClickHandle> clickHandles = fClickHandles.elements();
            while (clickHandles.hasMoreElements()) {
                fHandles.addElement(clickHandles.nextElement());
            }
        }
        return fHandles;
    }

    /**
     * Informs interested parties that the figure has changed and resets the list of handles.
     */
    @Override
    protected void figureSetChanged() {
        super.figureSetChanged();
        fHandles = null;
        handlesChanged();
    }

    /**
     * Updates all ClickHandles' boxes to match their widths and heights.
     */
    protected void layout() {
        // update ClickHandles:
        for (int i = 0; i < fClickHandles.size(); ++i) {
            ClickHandle handle = fClickHandles.elementAt(i);
            Dimension size = handle.owner().size();
            handle.setBox(new Rectangle(0, 0, size.width, size.height));
        }
    }

    /**
     * Checks whether the layout needs to be changed.
     *
     * @return whether the layout needs to be changed as boolean
     */
    protected abstract boolean needsLayout();

    /**
     * Checks whether the layout needs to be changed
     * and changes it if necessary.
     *
     * @param e [{@link FigureChangeEvent}] UNUSED
     *
     * @author Eva Mueller
     * @date Dec 3, 2010
     * @version 0.1
     */
    public void update(FigureChangeEvent e) {
        if (needsLayout()) {
            willChange();
            layout();
            changed();
        }
    }

    /**
     * Updates the layout.
     *
     * @param e an unused FigureChangeEvent
     */
    @Override
    public void figureChanged(FigureChangeEvent e) {
        update(e);
    }

    /**
     * Informs interested parties that the handles have
     * changed and resets the list of handles.
     *
     * @param e an unused FigureChangeEvent
     */
    @Override
    public void figureHandlesChanged(FigureChangeEvent e) {
        fHandles = null;
        handlesChanged();
    }

    /**
     * Updates the layout.
     *
     * @param e an unused FigureChangeEvent
     */
    @Override
    public void figureRemoved(FigureChangeEvent e) {
        update(e);
    }

    /**
     * Is called when the figure is clicked on.
     * Finds the composite figure that was clicked on
     * and forwards the click.
     *
     * @param view the drawing that was clicked on
     * @param alternate the figure is asked to perform some alternate action (usually right mouse-click)
     * @return tells whether the event was consumed.
     */
    @Override
    public boolean inspect(DrawingView view, boolean alternate) {
        Point click = view.lastClick();
        Figure inside = findFigureInside(click.x, click.y);
        if (inside != null && inside.inspect(view, alternate)) {
            return true;
        }
        if (alternate) {
            return false; // don't use default alternate behaviour
        } else {
            return super.inspect(view, false); // do use default normal behaviour
        }
    }

    /**
     * Sets a named attribute for all contained figures.
     *
     * @param name the name of the attribute to be set
     * @param value the value it is to be set to
     */
    @Override
    public void setAttribute(String name, Object value) {
        FigureEnumeration figenumeration = figures();
        while (figenumeration.hasMoreElements()) {
            (figenumeration.nextElement()).setAttribute(name, value);
        }
    }

    /**
     * Returns a named attribute of the first contained figure (in drawing order)
     * or null if there is no such attribute.
     * Only needs to look at the first figure because all figures have the same
     * values for attributes with the same name.
     *
     * @param name the name of the attribute to be gotten ie. FillColor or FrameColor
     * @return the value of the attribute
     */
    @Override
    public Object getAttribute(String name) {
        FigureEnumeration figenumeration = figures();
        if (figenumeration.hasMoreElements()) {
            return (figenumeration.nextElement()).getAttribute(name);
        }
        return null;
    }

    /**
     * Although inheriting the {@link de.renew.draw.storables.ontology.Storable} interface from its
     * superclass, this class is <b>not</b> storable.
     * (Just because no one made the effort - and it currently
     * doesn't need to be storable. But if you want to implement
     * storability, please take a look at all its subclasses, too.)
     * @throws RuntimeException always.
     */
    @Override
    public void write(StorableOutput dw) {
        throw new RuntimeException("SimpleCompositeFigure is not storable!");
    }
}
