/*
 * 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 java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EventListener;
import java.util.List;

import org.w3c.dom.Node;

import icy.canvas.IcyCanvas;
import icy.canvas.IcyCanvas2D;
import icy.common.CollapsibleEvent;
import icy.sequence.Sequence;
import icy.system.thread.ThreadUtil;
import icy.type.point.Point3D;
import icy.type.point.Point5D;
import icy.util.EventUtil;
import icy.util.GraphicsUtil;
import icy.util.ShapeUtil;
import icy.util.XMLUtil;
import icy.vtk.IcyVtkPanel;
import plugins.kernel.canvas.VtkCanvas;
import vtk.vtkActor;
import vtk.vtkInformation;
import vtk.vtkPolyDataMapper;
import vtk.vtkProp;
import vtk.vtkSphereSource;

/**
 * Anchor3D class, used for 3D point control.
 * 
 * @author Stephane
 */
public class Anchor3D extends Overlay implements VtkPainter, Runnable
{
    /**
     * Interface to listen Anchor3D position change
     */
    public static interface Anchor3DPositionListener extends EventListener
    {
        public void positionChanged(Anchor3D source);
    }

    public static class Anchor3DEvent implements CollapsibleEvent
    {
        private final Anchor3D source;

        public Anchor3DEvent(Anchor3D source)
        {
            super();

            this.source = source;
        }

        /**
         * @return the source
         */
        public Anchor3D getSource()
        {
            return source;
        }

        @Override
        public boolean collapse(CollapsibleEvent event)
        {
            if (equals(event))
            {
                // nothing to do here
                return true;
            }

            return false;
        }

        @Override
        public int hashCode()
        {
            return source.hashCode();
        }

        @Override
        public boolean equals(Object obj)
        {
            if (obj == this)
                return true;

            if (obj instanceof Anchor3DEvent)
            {
                final Anchor3DEvent event = (Anchor3DEvent) obj;

                return (event.getSource() == source);
            }

            return super.equals(obj);
        }
    }

    protected static final String ID_COLOR = "color";
    protected static final String ID_SELECTEDCOLOR = "selected_color";
    protected static final String ID_SELECTED = "selected";
    protected static final String ID_POS_X = "pos_x";
    protected static final String ID_POS_Y = "pos_y";
    protected static final String ID_POS_Z = "pos_z";
    protected static final String ID_RAY = "ray";
    protected static final String ID_VISIBLE = "visible";

    public static final int DEFAULT_RAY = 6;
    public static final Color DEFAULT_NORMAL_COLOR = Color.GREEN;
    public static final Color DEFAULT_SELECTED_COLOR = Color.WHITE;

    /**
     * position (canvas)
     */
    protected final Point3D.Double position;
    /**
     * radius (integer as we express it in pixel)
     */
    protected int ray;

    /**
     * color
     */
    protected Color color;
    /**
     * selection color
     */
    protected Color selectedColor;
    /**
     * selection flag
     */
    protected boolean selected;
    /**
     * flag that indicate if anchor is visible
     */
    protected boolean visible;

    // drag internals
    protected Point3D startDragMousePosition;
    protected Point3D startDragPainterPosition;

    // 2D shape for X,Y contains test
    protected final Ellipse2D ellipse;

    // VTK 3D objects
    vtkSphereSource vtkSource;
    protected vtkPolyDataMapper polyMapper;
    protected vtkActor actor;
    protected vtkInformation vtkInfo;

    // 3D internal
    protected boolean needRebuild;
    protected boolean needPropertiesUpdate;
    protected double scaling[];
    protected WeakReference<VtkCanvas> canvas3d;

    // listeners
    protected final List<Anchor3DPositionListener> anchor3DPositionlisteners;

    public Anchor3D(double x, double y, double z, int ray, Color color, Color selectedColor)
    {
        super("Anchor", OverlayPriority.SHAPE_NORMAL);

        position = new Point3D.Double(x, y, z);
        this.ray = ray;
        this.color = color;
        this.selectedColor = selectedColor;
        selected = false;
        visible = true;

        startDragMousePosition = null;
        startDragPainterPosition = null;

        ellipse = new Ellipse2D.Double();

        vtkSource = null;
        polyMapper = null;
        actor = null;
        vtkInfo = null;

        scaling = new double[3];
        Arrays.fill(scaling, 1d);

        needRebuild = true;
        needPropertiesUpdate = false;

        canvas3d = new WeakReference<VtkCanvas>(null);

        anchor3DPositionlisteners = new ArrayList<Anchor3DPositionListener>();
    }

    public Anchor3D(double x, double y, double z, Color color, Color selectedColor)
    {
        this(x, y, z, DEFAULT_RAY, color, selectedColor);
    }

    public Anchor3D(double x, double y, double z, int ray)
    {
        this(x, y, z, ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
    }

    public Anchor3D(double x, double y, double z)
    {
        this(x, y, z, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
    }

    public Anchor3D()
    {
        this(0d, 0d, 0d, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
    }

    @Override
    protected void finalize() throws Throwable
    {
        super.finalize();

        // release allocated VTK resources
        if (vtkSource != null)
            vtkSource.Delete();
        if (actor != null)
        {
            actor.SetPropertyKeys(null);
            actor.Delete();
        }
        if (vtkInfo != null)
        {
            vtkInfo.Remove(VtkCanvas.visibilityKey);
            vtkInfo.Delete();
        }
        if (polyMapper != null)
            polyMapper.Delete();
    }

    /**
     * @return X coordinate position
     */
    public double getX()
    {
        return position.x;
    }

    /**
     * Sets the X coordinate position
     */
    public void setX(double value)
    {
        setPosition(value, position.y, position.z);
    }

    /**
     * @return Y coordinate position
     */
    public double getY()
    {
        return position.y;
    }

    /**
     * Sets the Y coordinate position
     */
    public void setY(double value)
    {
        setPosition(position.x, value, position.z);
    }

    /**
     * @return Z coordinate position
     */
    public double getZ()
    {
        return position.z;
    }

    /**
     * Sets the Z coordinate position
     */
    public void setZ(double value)
    {
        setPosition(position.x, position.y, value);
    }

    /**
     * Get anchor position (return the internal reference)
     */
    public Point3D getPositionInternal()
    {
        return position;
    }

    /**
     * Get anchor position
     */
    public Point3D getPosition()
    {
        return new Point3D.Double(position.x, position.y, position.z);
    }

    /**
     * Sets anchor position
     */
    public void setPosition(Point3D p)
    {
        setPosition(p.getX(), p.getY(), p.getZ());
    }

    /**
     * Sets anchor position
     */
    public void setPosition(double x, double y, double z)
    {
        if ((position.x != x) || (position.y != y) || (position.z != z))
        {
            position.x = x;
            position.y = y;
            position.z = z;

            needRebuild = true;
            positionChanged();
            painterChanged();
        }
    }

    /**
     * Performs a translation on the anchor position
     */
    public void translate(double dx, double dy, double dz)
    {
        setPosition(position.x + dx, position.y + dy, position.z + dz);
    }

    /**
     * @return the ray
     */
    public int getRay()
    {
        return ray;
    }

    /**
     * Sets the ray
     */
    public void setRay(int value)
    {
        if (ray != value)
        {
            ray = value;
            needRebuild = true;
            painterChanged();
        }
    }

    /**
     * @return the color
     */
    public Color getColor()
    {
        return color;
    }

    /**
     * Sets the color
     */
    public void setColor(Color value)
    {
        if (color != value)
        {
            color = value;
            needPropertiesUpdate = true;
            painterChanged();
        }
    }

    /**
     * @return the <code>selected</code> state color
     */
    public Color getSelectedColor()
    {
        return selectedColor;
    }

    /**
     * Sets the <code>selected</code> state color
     */
    public void setSelectedColor(Color value)
    {
        if (selectedColor != value)
        {
            selectedColor = value;
            needPropertiesUpdate = true;
            painterChanged();
        }
    }

    /**
     * @return the <code>selected</code> state
     */
    public boolean isSelected()
    {
        return selected;
    }

    /**
     * Sets the <code>selected</code> state
     */
    public void setSelected(boolean value)
    {
        if (selected != value)
        {
            selected = value;

            // end drag
            if (!value)
                startDragMousePosition = null;

            needPropertiesUpdate = true;
            painterChanged();
        }
    }

    /**
     * @return the visible
     */
    public boolean isVisible()
    {
        return visible;
    }

    /**
     * @param value
     *        the visible to set
     */
    public void setVisible(boolean value)
    {
        if (visible != value)
        {
            visible = value;
            needPropertiesUpdate = true;
            painterChanged();
        }
    }

    /**
     * Returns <code>true</code> if specified Point3D is over the anchor in the specified canvas
     */
    public boolean isOver(IcyCanvas canvas, Point3D imagePoint)
    {
        updateEllipseForCanvas(canvas);

        // specific VTK canvas processing
        if (canvas instanceof VtkCanvas)
        {
            // faster to use picked object
            return (actor != null) && (actor == ((VtkCanvas) canvas).getPickedObject());
        }

        // at this point we need image position
        if (imagePoint == null)
            return false;

        final double x = imagePoint.getX();
        final double y = imagePoint.getY();
        final double z = imagePoint.getZ();

        // default processing for other canvas
        final int cnvZ = canvas.getPositionZ();

        // same Z position ?
        if ((cnvZ == -1) || (z == -1d) || (cnvZ == (int) z))
        {
            // fast contains test to start with
            if (ellipse.getBounds2D().contains(x, y))
                return ellipse.contains(x, y);
        }

        return false;
    }

    /**
     * Returns adjusted ray for specified Canvas
     */
    protected double getAdjRay(IcyCanvas canvas)
    {
        // assume X dimension is ok here
        return canvas.canvasToImageLogDeltaX(ray);
    }

    /**
     * Update internal ellipse for specified Canvas
     */
    protected void updateEllipseForCanvas(IcyCanvas canvas)
    {
        // specific VTK canvas processing
        if (canvas instanceof VtkCanvas)
        {
            // 3D canvas
            final VtkCanvas cnv = (VtkCanvas) canvas;
            // update reference if needed
            if (canvas3d.get() != cnv)
                canvas3d = new WeakReference<VtkCanvas>(cnv);

            // FIXME : need a better implementation
            final double[] s = cnv.getVolumeScale();

            // scaling changed ?
            if (!Arrays.equals(scaling, s))
            {
                // update scaling
                scaling = s;
                // need rebuild
                needRebuild = true;
            }

            if (needRebuild)
            {
                // initialize VTK objects if not yet done
                if (actor == null)
                    initVtkObjects();

                // request rebuild 3D objects
                ThreadUtil.runSingle(this);
                needRebuild = false;
            }

            if (needPropertiesUpdate)
            {
                updateVtkDisplayProperties();
                needPropertiesUpdate = false;
            }

            // update sphere radius
            updateVtkRadius();
        }
        else
        {
            final double adjRay = getAdjRay(canvas);

            ellipse.setFrame(position.x - adjRay, position.y - adjRay, adjRay * 2, adjRay * 2);
        }
    }

    protected boolean updateDrag(InputEvent e, double x, double y, double z)
    {
        // not dragging --> exit
        if (startDragMousePosition == null)
            return false;

        double dx = x - startDragMousePosition.getX();
        double dy = y - startDragMousePosition.getY();
        double dz = z - startDragMousePosition.getZ();

        // shift action --> limit to one direction
        if (EventUtil.isShiftDown(e))
        {
            // X or Z drag
            if (Math.abs(dx) > Math.abs(dy))
            {
                dy = 0d;

                // Z drag
                if (Math.abs(dz) > Math.abs(dx))
                    dx = 0d;
                else
                    dz = 0d;
            }
            // Y or Z drag
            else
            {
                dx = 0d;

                // Z drag
                if (Math.abs(dz) > Math.abs(dy))
                    dy = 0d;
                else
                    dz = 0d;
            }
        }

        // set new position
        setPosition(new Point3D.Double(startDragPainterPosition.getX() + dx, startDragPainterPosition.getY() + dy,
                startDragPainterPosition.getZ() + dz));

        return true;
    }

    protected boolean updateDrag(InputEvent e, Point3D pt)
    {
        return updateDrag(e, pt.getX(), pt.getY(), pt.getZ());
    }

    /**
     * called when anchor position has changed
     */
    protected void positionChanged()
    {
        updater.changed(new Anchor3DEvent(this));
    }

    @Override
    public void onChanged(CollapsibleEvent object)
    {
        // we got a position change event
        if (object instanceof Anchor3DEvent)
        {
            firePositionChangedEvent(((Anchor3DEvent) object).getSource());
            return;
        }

        super.onChanged(object);
    }

    protected void firePositionChangedEvent(Anchor3D source)
    {
        for (Anchor3DPositionListener listener : new ArrayList<Anchor3DPositionListener>(anchor3DPositionlisteners))
            listener.positionChanged(source);
    }

    public void addPositionListener(Anchor3DPositionListener listener)
    {
        anchor3DPositionlisteners.add(listener);
    }

    public void removePositionListener(Anchor3DPositionListener listener)
    {
        anchor3DPositionlisteners.remove(listener);
    }

    protected void initVtkObjects()
    {
        // init 3D painters stuff
        vtkSource = new vtkSphereSource();
        vtkSource.SetRadius(getRay());
        vtkSource.SetThetaResolution(12);
        vtkSource.SetPhiResolution(12);

        polyMapper = new vtkPolyDataMapper();
        polyMapper.SetInputConnection((vtkSource).GetOutputPort());

        actor = new vtkActor();
        actor.SetMapper(polyMapper);

        // use vtkInformations to store outline visibility state (hacky)
        vtkInfo = new vtkInformation();
        vtkInfo.Set(VtkCanvas.visibilityKey, 0);
        // VtkCanvas use this to restore correctly outline visibility flag
        actor.SetPropertyKeys(vtkInfo);

        // initialize color
        final Color col = getColor();
        actor.GetProperty().SetColor(col.getRed() / 255d, col.getGreen() / 255d, col.getBlue() / 255d);
    }

    /**
     * update 3D painter for 3D canvas (called only when VTK is loaded).
     */
    protected boolean rebuildVtkObjects()
    {
        final VtkCanvas canvas = canvas3d.get();
        // canvas was closed
        if (canvas == null)
            return false;

        final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
        // canvas was closed
        if (vtkPanel == null)
            return false;

        final Point3D pos = getPosition();

        // actor can be accessed in canvas3d for rendering so we need to synchronize access
        vtkPanel.lock();
        try
        {
            // need to handle scaling on radius and position to keep a "round" sphere (else we obtain ellipsoid)
            vtkSource.SetCenter(pos.getX() * scaling[0], pos.getY() * scaling[1], pos.getZ() * scaling[2]);
            vtkSource.Update();
            polyMapper.Update();

            // actor.SetScale(scaling[0], scaling[1], scaling[0]);
        }
        finally
        {
            vtkPanel.unlock();
        }

        // need to repaint
        painterChanged();

        return true;
    }

    protected void updateVtkDisplayProperties()
    {
        if (actor == null)
            return;

        final VtkCanvas cnv = canvas3d.get();
        final Color col = isSelected() ? getSelectedColor() : getColor();
        final double r = col.getRed() / 255d;
        final double g = col.getGreen() / 255d;
        final double b = col.getBlue() / 255d;
        // final float opacity = getOpacity();

        final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null;

        // we need to lock canvas as actor can be accessed during rendering
        if (vtkPanel != null)
            vtkPanel.lock();
        try
        {
            actor.GetProperty().SetColor(r, g, b);
            if (isVisible())
            {
                actor.SetVisibility(1);
                vtkInfo.Set(VtkCanvas.visibilityKey, 1);
            }
            else
            {
                actor.SetVisibility(0);
                vtkInfo.Set(VtkCanvas.visibilityKey, 0);
            }
        }
        finally
        {
            if (vtkPanel != null)
                vtkPanel.unlock();
        }

        // need to repaint
        painterChanged();
    }

    protected void updateVtkRadius()
    {
        final VtkCanvas canvas = canvas3d.get();
        // canvas was closed
        if (canvas == null)
            return;

        final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
        // canvas was closed
        if (vtkPanel == null)
            return;

        if (vtkSource == null)
            return;

        // update sphere radius base on canvas scale X
        final double radius = getAdjRay(canvas) * scaling[0];

        if (vtkSource.GetRadius() != radius)
        {
            // actor can be accessed in canvas3d for rendering so we need to synchronize access
            vtkPanel.lock();
            try
            {
                vtkSource.SetRadius(radius);
                vtkSource.Update();
            }
            finally
            {
                vtkPanel.unlock();
            }

            // need to repaint
            painterChanged();
        }
    }

    @Override
    public vtkProp[] getProps()
    {
        // initialize VTK objects if not yet done
        if (actor == null)
            initVtkObjects();

        return new vtkActor[] {actor};
    }

    @Override
    public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
    {
        paint(g, sequence, canvas, false);
    }

    public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas, boolean simplified)
    {
        // this will update VTK objects if needed
        updateEllipseForCanvas(canvas);

        if (canvas instanceof IcyCanvas2D)
        {
            // nothing to do here when not visible
            if (!isVisible())
                return;

            // get canvas Z position
            final int cnvZ = canvas.getPositionZ();
            // get delta Z (difference between canvas Z position and anchor Z pos)
            final int dz = Math.abs(((int) getZ()) - cnvZ);
            // calculate z fade range
            final int zRange = Math.min(10, Math.max(3, sequence.getSizeZ() / 10));

            // not visible on this Z position
            if (dz > zRange)
                return;

            // trivial paint optimization
            final boolean shapeVisible = ShapeUtil.isVisible(g, ellipse);

            if (shapeVisible)
            {
                final Graphics2D g2 = (Graphics2D) g.create();
                // ration for size / opacity
                final float ratio = 1f - ((float) dz / (float) zRange);

                if (ratio != 1f)
                    GraphicsUtil.mixAlpha(g2, ratio);

                // draw content
                if (isSelected())
                    g2.setColor(getSelectedColor());
                else
                    g2.setColor(getColor());

                // simplified small drawing
                if (simplified)
                {
                    final int ray = (int) canvas.canvasToImageDeltaX(2);
                    g2.fillRect((int) getX() - ray, (int) getY() - ray, ray * 2, ray * 2);
                }
                // normal drawing
                else
                {
                    // draw ellipse content
                    g2.fill(ellipse);
                    // draw black border
                    g2.setStroke(new BasicStroke((float) (getAdjRay(canvas) / 8f)));
                    g2.setColor(Color.black);
                    g2.draw(ellipse);
                }

                g2.dispose();
            }
        }
        else if (canvas instanceof VtkCanvas)
        {
            // nothing to do here
        }
    }

    @Override
    public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        if (!isVisible() && !getReceiveKeyEventOnHidden())
            return;

        // no image position --> exit
        if (imagePoint == null)
            return;

        // just for the shift key state change
        updateDrag(e, imagePoint.x, imagePoint.y, imagePoint.z);
    }

    @Override
    public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        if (!isVisible() && !getReceiveKeyEventOnHidden())
            return;

        // no image position --> exit
        if (imagePoint == null)
            return;

        // just for the shift key state change
        updateDrag(e, imagePoint.x, imagePoint.y, imagePoint.z);
    }

    @Override
    public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        if (!isVisible() && !getReceiveMouseEventOnHidden())
            return;

        if (e.isConsumed())
            return;

        // no image position --> exit
        if (imagePoint == null)
            return;

        if (EventUtil.isLeftMouseButton(e))
        {
            // consume event to activate drag
            if (isSelected())
            {
                startDragMousePosition = imagePoint.toPoint3D();
                startDragPainterPosition = getPosition();
                e.consume();
            }
        }
    }

    @Override
    public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        startDragMousePosition = null;
    }

    @Override
    public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        if (!isVisible() && !getReceiveMouseEventOnHidden())
            return;

        if (e.isConsumed())
            return;

        // no image position --> exit
        if (imagePoint == null)
            return;

        if (EventUtil.isLeftMouseButton(e))
        {
            // if selected then move according to mouse position
            if (isSelected())
            {
                // force start drag if not already the case
                if (startDragMousePosition == null)
                {
                    startDragMousePosition = imagePoint.toPoint3D();
                    startDragPainterPosition = getPosition();
                }

                updateDrag(e, imagePoint.x, imagePoint.y, imagePoint.z);

                e.consume();
            }
        }
    }

    @Override
    public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
    {
        if (!isVisible() && !getReceiveMouseEventOnHidden())
            return;

        // already consumed, no selection possible
        if (e.isConsumed())
            setSelected(false);
        else
        {
            final boolean overlapped = isOver(canvas, (imagePoint != null) ? imagePoint.toPoint3D() : null);

            setSelected(overlapped);

            // so we can only have one selected at once
            if (overlapped)
                e.consume();
        }
    }

    @Override
    public void run()
    {
        rebuildVtkObjects();
    }

    public boolean loadPositionFromXML(Node node)
    {
        if (node == null)
            return false;

        beginUpdate();
        try
        {
            setX(XMLUtil.getElementDoubleValue(node, ID_POS_X, 0d));
            setY(XMLUtil.getElementDoubleValue(node, ID_POS_Y, 0d));
            setZ(XMLUtil.getElementDoubleValue(node, ID_POS_Z, 0d));
        }
        finally
        {
            endUpdate();
        }

        return true;
    }

    public boolean savePositionToXML(Node node)
    {
        if (node == null)
            return false;

        XMLUtil.setElementDoubleValue(node, ID_POS_X, getX());
        XMLUtil.setElementDoubleValue(node, ID_POS_Y, getY());
        XMLUtil.setElementDoubleValue(node, ID_POS_Z, getZ());

        return true;
    }

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

        beginUpdate();
        try
        {
            setColor(new Color(XMLUtil.getElementIntValue(node, ID_COLOR, DEFAULT_NORMAL_COLOR.getRGB())));
            setSelectedColor(
                    new Color(XMLUtil.getElementIntValue(node, ID_SELECTEDCOLOR, DEFAULT_SELECTED_COLOR.getRGB())));
            setX(XMLUtil.getElementDoubleValue(node, ID_POS_X, 0d));
            setY(XMLUtil.getElementDoubleValue(node, ID_POS_Y, 0d));
            setZ(XMLUtil.getElementDoubleValue(node, ID_POS_Z, 0d));
            setRay(XMLUtil.getElementIntValue(node, ID_RAY, DEFAULT_RAY));
            setVisible(XMLUtil.getElementBooleanValue(node, ID_VISIBLE, true));
        }
        finally
        {
            endUpdate();
        }

        return true;
    }

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

        XMLUtil.setElementIntValue(node, ID_COLOR, getColor().getRGB());
        XMLUtil.setElementIntValue(node, ID_SELECTEDCOLOR, getSelectedColor().getRGB());
        XMLUtil.setElementDoubleValue(node, ID_POS_X, getX());
        XMLUtil.setElementDoubleValue(node, ID_POS_Y, getY());
        XMLUtil.setElementDoubleValue(node, ID_POS_Z, getY());
        XMLUtil.setElementIntValue(node, ID_RAY, getRay());
        XMLUtil.setElementBooleanValue(node, ID_VISIBLE, isVisible());

        return true;
    }
}