/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.painter;

import icy.canvas.IcyCanvas;
import icy.common.CollapsibleEvent;
import icy.common.UpdateEventHandler;
import icy.common.listener.ChangeListener;
import icy.file.xml.XMLPersistent;
import icy.gui.viewer.Viewer;
import icy.main.Icy;
import icy.painter.OverlayEvent.OverlayEventType;
import icy.sequence.Sequence;
import icy.system.IcyExceptionHandler;
import icy.type.point.Point5D;
import icy.util.ClassUtil;
import icy.util.StringUtil;
import icy.util.XMLUtil;

import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.Point2D;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;

import org.w3c.dom.Node;

/**
 * Overlay class.<br>
 * <br>
 * This class allow interaction and rich informations display on Sequences.<br>
 * {@link IcyCanvas} subclasses should propagate mouse and key events to overlay.
 * 
 * @author Stephane
 */
@SuppressWarnings("deprecation")
public abstract class Overlay implements Painter, ChangeListener, Comparable<Overlay>, XMLPersistent
{
    /**
     * Define the overlay priority:
     * 
     * <pre>
     * Lowest   |   BACKGROUND  (below image)
     *          |   IMAGE       (image level)
     *          |   SHAPE       (just over the image)
     *          |   TEXT        (over image and shape)
     *          |   TOOLTIP     (all over the rest)
     * Highest  |   TOPMOST     (absolute topmost)
     * </pre>
     * 
     * You have 4 levels for each category (except TOPMOST) for finest adjustment:
     * 
     * <pre>
     * Lowest   |   LOW
     *          |   NORMAL
     *          |   HIGH
     * Highest  |   TOP
     * </pre>
     * 
     * TOP level should be used to give <i>focus<i> to a specific Overlay over all other in the same
     * category.
     */
    public static enum OverlayPriority
    {
        BACKGROUND_LOW, BACKGROUND_NORMAL, BACKGROUND_HIGH, BACKGROUND_TOP, IMAGE_LOW, IMAGE_NORMAL, IMAGE_HIGH, IMAGE_TOP, SHAPE_LOW, SHAPE_NORMAL, SHAPE_HIGH, SHAPE_TOP, TEXT_LOW, TEXT_NORMAL, TEXT_HIGH, TEXT_TOP, TOOLTIP_LOW, TOOLTIP_NORMAL, TOOLTIP_HIGH, TOOLTIP_TOP, TOPMOST
    }

    public static final String ID_OVERLAY = "overlay";

    public static final String ID_CLASSNAME = "classname";
    public static final String ID_ID = "id";
    public static final String ID_NAME = "name";
    public static final String ID_PRIORITY = "priority";
    public static final String ID_READONLY = "readOnly";
    public static final String ID_CANBEREMOVED = "canBeRemoved";
    public static final String ID_RECEIVEKEYEVENTONHIDDEN = "receiveKeyEventOnHidden";
    public static final String ID_RECEIVEMOUSEEVENTONHIDDEN = "receiveMouseEventOnHidden";

    public static final String PROPERTY_NAME = ID_NAME;
    public static final String PROPERTY_PRIORITY = ID_PRIORITY;
    public static final String PROPERTY_READONLY = ID_READONLY;
    public static final String PROPERTY_PERSISTENT = "persitent";
    public static final String PROPERTY_CANBEREMOVED = ID_CANBEREMOVED;
    public static final String PROPERTY_RECEIVEKEYEVENTONHIDDEN = ID_RECEIVEKEYEVENTONHIDDEN;
    public static final String PROPERTY_RECEIVEMOUSEEVENTONHIDDEN = ID_RECEIVEMOUSEEVENTONHIDDEN;

    /**
     * We consider as tiny object anything with a size of 10 pixels or less
     */
    public static final int LOD_SMALL = 10;
    public static final int LOD_TINY = 4;

    protected static int id_gen = 1;

    /**
     * Create a Overlay from a XML node.
     * 
     * @param node
     *        XML node defining the overlay
     * @return the created Overlay or <code>null</code> if the Overlay class does not support XML
     *         persistence a default
     *         constructor
     */
    public static Overlay createFromXML(Node node)
    {
        if (node == null)
            return null;

        final String className = XMLUtil.getElementValue(node, ID_CLASSNAME, "");
        if (StringUtil.isEmpty(className))
            return null;

        final Overlay result;

        try
        {
            // search for the specified className
            final Class<?> clazz = ClassUtil.findClass(className);

            // class found
            if (clazz != null)
            {
                final Class<? extends Overlay> overlayClazz = clazz.asSubclass(Overlay.class);

                // default constructor
                final Constructor<? extends Overlay> constructor = overlayClazz.getConstructor(new Class[] {});
                // build Overlay
                result = constructor.newInstance();

                // load properties from XML
                if (result != null)
                {
                    // error while loading infos --> return null
                    if (!result.loadFromXML(node))
                        return null;
                }

                return result;
            }
        }
        catch (NoSuchMethodException e)
        {
            IcyExceptionHandler.handleException(new NoSuchMethodException("Default constructor not found in class '"
                    + className + "', cannot create the Overlay."), true);
        }
        catch (ClassNotFoundException e)
        {
            IcyExceptionHandler.handleException(new ClassNotFoundException("Cannot find '" + className
                    + "' class, cannot create the Overlay."), true);
        }
        catch (Exception e)
        {
            IcyExceptionHandler.handleException(e, true);
        }

        return null;
    }

    /**
     * Return the number of Overlay defined in the specified XML node.
     * 
     * @param node
     *        XML node defining the Overlay list
     * @return the number of Overlay defined in the XML node.
     */
    public static int getOverlayCount(Node node)
    {
        if (node != null)
        {
            final List<Node> nodesOverlay = XMLUtil.getChildren(node, ID_OVERLAY);

            if (nodesOverlay != null)
                return nodesOverlay.size();
        }

        return 0;
    }

    /**
     * Return a list of Overlay from a XML node.
     * 
     * @param node
     *        XML node defining the Overlay list
     * @return a list of Overlay
     */
    public static List<Overlay> loadOverlaysFromXML(Node node)
    {
        final List<Overlay> result = new ArrayList<Overlay>();

        if (node != null)
        {
            final List<Node> nodesOverlay = XMLUtil.getChildren(node, ID_OVERLAY);

            if (nodesOverlay != null)
            {
                for (Node n : nodesOverlay)
                {
                    final Overlay overlay = createFromXML(n);

                    if (overlay != null)
                    {
                        // we assume this overlay should stay persistent then
                        overlay.setPersistent(true);
                        result.add(overlay);
                    }
                }
            }
        }

        return result;
    }

    /**
     * Set a list of Overlay to a XML node.
     * 
     * @param node
     *        XML node which is used to store the list of Overlay
     * @param overlays
     *        the list of Overlay to store in the XML node
     */
    public static void saveOverlaysToXML(Node node, List<Overlay> overlays)
    {
        if (node != null)
        {
            for (Overlay overlay : overlays)
            {
                // only save persistent overlay
                if (overlay.isPersistent())
                {
                    final Node nodeOverlay = XMLUtil.addElement(node, ID_OVERLAY);

                    if (!overlay.saveToXML(nodeOverlay))
                    {
                        XMLUtil.removeNode(node, nodeOverlay);
                        System.err.println("Error: the overlay " + overlay.getName()
                                + " was not correctly saved to XML !");
                    }
                }
            }
        }
    }

    /**
     * properties
     */
    protected int id;
    protected String name;
    protected OverlayPriority priority;
    protected boolean persistent;
    protected boolean readOnly;
    protected boolean canBeRemoved;
    protected boolean receiveKeyEventOnHidden;
    protected boolean receiveMouseEventOnHidden;

    /**
     * internals
     */
    protected final List<OverlayListener> listeners;
    protected final UpdateEventHandler updater;

    public Overlay(String name, OverlayPriority priority)
    {
        super();

        synchronized (Overlay.class)
        {
            id = id_gen++;
        }

        this.name = name;
        this.priority = priority;
        // by default the overlay is not persistent
        persistent = false;
        readOnly = false;
        canBeRemoved = true;
        receiveKeyEventOnHidden = false;
        receiveMouseEventOnHidden = false;

        listeners = new ArrayList<OverlayListener>();
        updater = new UpdateEventHandler(this, false);
    }

    public Overlay(String name)
    {
        // create overlay with default priority
        this(name, OverlayPriority.SHAPE_NORMAL);
    }

    /**
     * @return the name
     */
    public String getName()
    {
        return name;
    }

    /**
     * @param name
     *        the name to set
     */
    public void setName(String name)
    {
        if (this.name != name)
        {
            this.name = name;
            propertyChanged(PROPERTY_NAME);
        }
    }

    /**
     * @return the priority
     */
    public OverlayPriority getPriority()
    {
        return priority;
    }

    /**
     * @param priority
     *        the priority to set
     */
    public void setPriority(OverlayPriority priority)
    {
        if (this.priority != priority)
        {
            this.priority = priority;
            propertyChanged(PROPERTY_PRIORITY);
        }
    }

    /**
     * Returns <code>true</code> if the overlay is attached to the specified {@link Sequence}.
     */
    public boolean isAttached(Sequence sequence)
    {
        if (sequence != null)
            return sequence.contains(this);

        return false;
    }

    /**
     * @deprecated Use {@link #getCanBeRemoved()} instead.
     * @see #setCanBeRemoved(boolean)
     */
    @Deprecated
    public boolean isFixed()
    {
        return !getCanBeRemoved();
    }

    /**
     * @deprecated Use {@link #setCanBeRemoved(boolean)} instead.
     */
    @Deprecated
    public void setFixed(boolean value)
    {
        setCanBeRemoved(!value);
    }

    /**
     * Returns <code>true</code> if the overlay can be freely removed from the Canvas where it
     * appears and <code>false</code> otherwise.<br/>
     * 
     * @see #setCanBeRemoved(boolean)
     */
    public boolean getCanBeRemoved()
    {
        return canBeRemoved;
    }

    /**
     * Set the <code>canBeRemoved</code> property.<br/>
     * Set it to false if you want to prevent the overlay to be removed from the Canvas where it
     * appears.
     */
    public void setCanBeRemoved(boolean value)
    {
        if (canBeRemoved != value)
        {
            canBeRemoved = value;
            propertyChanged(PROPERTY_CANBEREMOVED);
        }
    }

    /**
     * Return persistent property.<br/>
     * When set to <code>true</code> the Overlay will be saved in the Sequence persistent XML data.
     */
    public boolean isPersistent()
    {
        return persistent;
    }

    /**
     * Set persistent property.<br/>
     * When set to <code>true</code> the Overlay will be saved in the Sequence persistent XML data
     * (default is <code>false</code>).
     */
    public void setPersistent(boolean value)
    {
        if (persistent != value)
        {
            persistent = value;
            propertyChanged(PROPERTY_PERSISTENT);
        }
    }

    /**
     * Return read only property.<br/>
     * When set to <code>true</code> we cannot anymore modify overlay properties from the GUI.
     */
    public boolean isReadOnly()
    {
        return readOnly;
    }

    /**
     * Set read only property.<br/>
     * When set to <code>true</code> we cannot anymore modify overlay properties from the GUI.
     */
    public void setReadOnly(boolean value)
    {
        if (readOnly != value)
        {
            readOnly = value;
            propertyChanged(PROPERTY_READONLY);
        }
    }

    /**
     * @return <code>true</code> is the overlay should receive {@link KeyEvent} even when it is not
     *         visible.
     */
    public boolean getReceiveKeyEventOnHidden()
    {
        return receiveKeyEventOnHidden;
    }

    /**
     * Set to <code>true</code> if you want to overlay to receive {@link KeyEvent} even when it is
     * not visible.
     */
    public void setReceiveKeyEventOnHidden(boolean value)
    {
        if (receiveKeyEventOnHidden != value)
        {
            receiveKeyEventOnHidden = value;
            propertyChanged(PROPERTY_RECEIVEKEYEVENTONHIDDEN);
        }
    }

    /**
     * @return <code>true</code> is the overlay should receive {@link MouseEvent} even when it is
     *         not visible.
     */
    public boolean getReceiveMouseEventOnHidden()
    {
        return receiveMouseEventOnHidden;
    }

    /**
     * Set to <code>true</code> if you want to overlay to receive {@link KeyEvent} even when it is
     * not visible.
     */
    public void setReceiveMouseEventOnHidden(boolean value)
    {
        if (receiveMouseEventOnHidden != value)
        {
            receiveMouseEventOnHidden = value;
            propertyChanged(PROPERTY_RECEIVEMOUSEEVENTONHIDDEN);
        }
    }

    /**
     * Override this method to provide an extra options panel for the overlay.<br>
     * The options panel will appears in the inspector when the layer's overlay is selected.
     */
    public JPanel getOptionsPanel()
    {
        return null;
    }

    /**
     * @deprecated Use {@link Sequence#addOverlay(Overlay)} instead.
     */
    @Deprecated
    public void attachTo(Sequence sequence)
    {
        if (sequence != null)
            sequence.addOverlay(this);
    }

    /**
     * @deprecated Use {@link Sequence#removeOverlay(Overlay)} instead.
     */
    @Deprecated
    public void detachFrom(Sequence sequence)
    {
        if (sequence != null)
            sequence.removeOverlay(this);
    }

    /**
     * Remove the Overlay from all sequences and canvas where it is currently attached.
     */
    public void remove()
    {
        for (Sequence sequence : getSequences())
            sequence.removeOverlay(this);
        for (IcyCanvas canvas : getAttachedCanvas())
            canvas.removeLayer(this);
    }

    /**
     * Returns all sequences where the overlay is currently attached.
     */
    public List<Sequence> getSequences()
    {
        return Icy.getMainInterface().getSequencesContaining(this);
    }

    /**
     * Returns all canvas where the overlay is currently present as a layer.
     */
    public List<IcyCanvas> getAttachedCanvas()
    {
        final List<IcyCanvas> result = new ArrayList<IcyCanvas>();

        for (Viewer viewer : Icy.getMainInterface().getViewers())
        {
            final IcyCanvas canvas = viewer.getCanvas();

            if ((canvas != null) && canvas.hasLayer(this))
                result.add(canvas);
        }

        return result;
    }

    public void beginUpdate()
    {
        updater.beginUpdate();
    }

    public void endUpdate()
    {
        updater.endUpdate();
    }

    public boolean isUpdating()
    {
        return updater.isUpdating();
    }

    /**
     * @deprecated Use {@link #painterChanged()} instead.
     */
    @Deprecated
    public void changed()
    {
        painterChanged();
    }

    /**
     * Notify the painter content has changed.<br>
     * All sequence containing the overlay will be repainted to reflect the change.
     */
    public void painterChanged()
    {
        updater.changed(new OverlayEvent(this, OverlayEventType.PAINTER_CHANGED));
    }

    /**
     * Notify the overlay property has changed.
     */
    public void propertyChanged(String propertyName)
    {
        updater.changed(new OverlayEvent(this, OverlayEventType.PROPERTY_CHANGED, propertyName));
    }

    @Override
    public void onChanged(CollapsibleEvent object)
    {
        fireOverlayChangedEvent((OverlayEvent) object);
    }

    protected void fireOverlayChangedEvent(OverlayEvent event)
    {
        for (OverlayListener listener : new ArrayList<OverlayListener>(listeners))
            listener.overlayChanged(event);
    }

    /**
     * Add a listener.
     */
    public void addOverlayListener(OverlayListener listener)
    {
        listeners.add(listener);
    }

    /**
     * Remove a listener.
     */
    public void removeOverlayListener(OverlayListener listener)
    {
        listeners.remove(listener);
    }

    /**
     * Paint method called to draw the overlay.
     */
    @Override
    public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
    {
        // nothing by default
    }

    /**
     * @deprecated Use {@link #mousePressed(MouseEvent, Point5D.Double, IcyCanvas)} instead
     */
    @Deprecated
    @Override
    public void mousePressed(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
    {
        // no action by default
    }

    /**
     * @deprecated Use {@link #mouseReleased(MouseEvent, Point5D.Double, IcyCanvas)} instead
     */
    @Deprecated
    @Override
    public void mouseReleased(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
    {
        // no action by default
    }

    /**
     * @deprecated Use {@link #mouseClick(MouseEvent, Point5D.Double, IcyCanvas)} instead
     */
    @Deprecated
    @Override
    public void mouseClick(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
    {
        // no action by default
    }

    /**
     * @deprecated Use {@link #mouseMove(MouseEvent, Point5D.Double, IcyCanvas)} instead
     */
    @Deprecated
    @Override
    public void mouseMove(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
    {
        // no action by default
    }

    /**
     * @deprecated Use {@link #mouseDrag(MouseEvent, Point5D.Double, IcyCanvas)} instead
     */
    @Deprecated
    @Override
    public void mouseDrag(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
    {
        // no action by default
    }

    /**
     * @deprecated Use {@link #mouseEntered(MouseEvent, Point5D.Double, IcyCanvas)} instead
     */
    @Deprecated
    public void mouseEntered(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
    {
        // no action by default
    }

    /**
     * @deprecated Use {@link #mouseExited(MouseEvent, Point5D.Double, IcyCanvas)} instead
     */
    @Deprecated
    public void mouseExited(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
    {
        // no action by default
    }

    /**
     * @deprecated Use {@link #mouseWheelMoved(MouseWheelEvent, Point5D.Double, IcyCanvas)} instead
     */
    @Deprecated
    public void mouseWheelMoved(MouseWheelEvent e, Point2D imagePoint, IcyCanvas canvas)
    {
        // no action by default
    }

    /**
     * @deprecated Use {@link #keyPressed(KeyEvent, Point5D.Double, IcyCanvas)} instead
     */
    @Deprecated
    @Override
    public void keyPressed(KeyEvent e, Point2D imagePoint, IcyCanvas canvas)
    {
        // no action by default
    }

    /**
     * @deprecated Use {@link #keyReleased(KeyEvent, Point5D.Double, IcyCanvas)} instead
     */
    @Deprecated
    @Override
    public void keyReleased(KeyEvent e, Point2D imagePoint, IcyCanvas canvas)
    {
        // no action by default
    }

    /**
     * Mouse press event forwarded to the overlay.
     * 
     * @param e
     *        mouse event
     * @param imagePoint
     *        mouse position (image coordinates)
     * @param canvas
     *        icy canvas
     */
    public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        // provide backward compatibility
        if (imagePoint != null)
            mousePressed(e, imagePoint.toPoint2D(), canvas);
        else
            mousePressed(e, (Point2D) null, canvas);
    }

    /**
     * Mouse release event forwarded to the overlay.
     * 
     * @param e
     *        mouse event
     * @param imagePoint
     *        mouse position (image coordinates)
     * @param canvas
     *        icy canvas
     */
    public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        // provide backward compatibility
        if (imagePoint != null)
            mouseReleased(e, imagePoint.toPoint2D(), canvas);
        else
            mouseReleased(e, (Point2D) null, canvas);
    }

    /**
     * Mouse click event forwarded to the overlay.
     * 
     * @param e
     *        mouse event
     * @param imagePoint
     *        mouse position (image coordinates)
     * @param canvas
     *        icy canvas
     */
    public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        // provide backward compatibility
        if (imagePoint != null)
            mouseClick(e, imagePoint.toPoint2D(), canvas);
        else
            mouseClick(e, (Point2D) null, canvas);
    }

    /**
     * Mouse move event forwarded to the overlay.
     * 
     * @param e
     *        mouse event
     * @param imagePoint
     *        mouse position (image coordinates)
     * @param canvas
     *        icy canvas
     */
    public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        // provide backward compatibility
        if (imagePoint != null)
            mouseMove(e, imagePoint.toPoint2D(), canvas);
        else
            mouseMove(e, (Point2D) null, canvas);
    }

    /**
     * Mouse drag event forwarded to the overlay.
     * 
     * @param e
     *        mouse event
     * @param imagePoint
     *        mouse position (image coordinates)
     * @param canvas
     *        icy canvas
     */
    public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        // provide backward compatibility
        if (imagePoint != null)
            mouseDrag(e, imagePoint.toPoint2D(), canvas);
        else
            mouseDrag(e, (Point2D) null, canvas);
    }

    /**
     * Mouse enter event forwarded to the overlay.
     * 
     * @param e
     *        mouse event
     * @param imagePoint
     *        mouse position (image coordinates)
     * @param canvas
     *        icy canvas
     */
    public void mouseEntered(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        // provide backward compatibility
        if (imagePoint != null)
            mouseEntered(e, imagePoint.toPoint2D(), canvas);
        else
            mouseEntered(e, (Point2D) null, canvas);
    }

    /**
     * Mouse exit event forwarded to the overlay.
     * 
     * @param e
     *        mouse event
     * @param imagePoint
     *        mouse position (image coordinates)
     * @param canvas
     *        icy canvas
     */
    public void mouseExited(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        // provide backward compatibility
        if (imagePoint != null)
            mouseExited(e, imagePoint.toPoint2D(), canvas);
        else
            mouseExited(e, (Point2D) null, canvas);
    }

    /**
     * Mouse wheel moved event forwarded to the overlay.
     * 
     * @param e
     *        mouse event
     * @param imagePoint
     *        mouse position (image coordinates)
     * @param canvas
     *        icy canvas
     */
    public void mouseWheelMoved(MouseWheelEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        // provide backward compatibility
        if (imagePoint != null)
            mouseWheelMoved(e, imagePoint.toPoint2D(), canvas);
        else
            mouseWheelMoved(e, (Point2D) null, canvas);
    }

    /**
     * Key press event forwarded to the overlay.
     * 
     * @param e
     *        key event
     * @param imagePoint
     *        mouse position (image coordinates)
     * @param canvas
     *        icy canvas
     */
    public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        // provide backward compatibility
        if (imagePoint != null)
            keyPressed(e, imagePoint.toPoint2D(), canvas);
        else
            keyPressed(e, (Point2D) null, canvas);
    }

    /**
     * Key release event forwarded to the overlay.
     * 
     * @param e
     *        key event
     * @param imagePoint
     *        mouse position (image coordinates)
     * @param canvas
     *        icy canvas
     */
    public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        // provide backward compatibility
        if (imagePoint != null)
            keyReleased(e, imagePoint.toPoint2D(), canvas);
        else
            keyReleased(e, (Point2D) null, canvas);
    }

    public boolean loadFromXML(Node node, boolean preserveId)
    {
        if (node == null)
            return false;

        beginUpdate();
        try
        {
            // FIXME : this can make duplicate id but it is also important to preserve id
            if (!preserveId)
            {
                id = XMLUtil.getElementIntValue(node, ID_ID, 0);
                synchronized (Overlay.class)
                {
                    // avoid having same id
                    if (id_gen <= id)
                        id_gen = id + 1;
                }
            }
            setName(XMLUtil.getElementValue(node, ID_NAME, ""));
            setPriority(OverlayPriority.values()[XMLUtil.getElementIntValue(node, ID_PRIORITY,
                    OverlayPriority.SHAPE_NORMAL.ordinal())]);
            setReadOnly(XMLUtil.getElementBooleanValue(node, ID_READONLY, false));
            setReceiveKeyEventOnHidden(XMLUtil.getElementBooleanValue(node, ID_RECEIVEKEYEVENTONHIDDEN, false));
            setReceiveMouseEventOnHidden(XMLUtil.getElementBooleanValue(node, ID_RECEIVEMOUSEEVENTONHIDDEN, false));
        }
        finally
        {
            endUpdate();
        }

        return true;
    }

    @Override
    public boolean loadFromXML(Node node)
    {
        return loadFromXML(node, false);
    }

    @Override
    public boolean saveToXML(Node node)
    {
        if (node == null)
            return false;

        XMLUtil.setElementValue(node, ID_CLASSNAME, getClass().getName());
        XMLUtil.setElementIntValue(node, ID_ID, id);
        XMLUtil.setElementValue(node, ID_NAME, getName());
        XMLUtil.setElementIntValue(node, ID_PRIORITY, getPriority().ordinal());
        XMLUtil.setElementBooleanValue(node, ID_READONLY, isReadOnly());
        XMLUtil.setElementBooleanValue(node, ID_RECEIVEKEYEVENTONHIDDEN, getReceiveKeyEventOnHidden());
        XMLUtil.setElementBooleanValue(node, ID_RECEIVEMOUSEEVENTONHIDDEN, getReceiveMouseEventOnHidden());

        return true;
    }

    @Override
    public int compareTo(Overlay o)
    {
        // highest priority first
        return o.priority.ordinal() - priority.ordinal();
    }
}