/**
 * 
 */
package plugins.kernel.roi.roi3d;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

import org.w3c.dom.Node;

import icy.canvas.IcyCanvas;
import icy.canvas.IcyCanvas2D;
import icy.common.CollapsibleEvent;
import icy.painter.Anchor3D;
import icy.painter.OverlayEvent;
import icy.painter.OverlayEvent.OverlayEventType;
import icy.resource.ResourceUtil;
import icy.roi.ROI;
import icy.roi.ROIEvent;
import icy.sequence.Sequence;
import icy.type.geom.Line3D;
import icy.type.point.Point3D;
import icy.type.point.Point5D;
import icy.util.GraphicsUtil;
import icy.util.StringUtil;
import icy.util.XMLUtil;
import icy.vtk.IcyVtkPanel;
import plugins.kernel.canvas.VtkCanvas;
import vtk.vtkActor;
import vtk.vtkPolyDataMapper;
import vtk.vtkSphereSource;

/**
 * ROI 3D Point class.<br>
 * 
 * @author Stephane Dallongeville
 */
public class ROI3DPoint extends ROI3DShape
{
    public class ROI3DPointPainter extends ROI3DShapePainter
    {
        vtkSphereSource vtkSource;

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

            // release extra VTK objects
            if (vtkSource != null)
                vtkSource.Delete();
        }

        @Override
        protected boolean isSmall(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas)
        {
            if (isSelected())
                return false;

            return true;
        }

        @Override
        protected boolean isTiny(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas)
        {
            if (isSelected())
                return false;

            return true;
        }

        @Override
        public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas)
        {
            if (canvas instanceof IcyCanvas2D)
            {
                final Graphics2D g2 = (Graphics2D) g.create();

                if (isSelected() && !isReadOnly())
                {
                    // draw control point if selected
                    synchronized (controlPoints)
                    {
                        for (Anchor3D pt : controlPoints)
                            pt.paint(g2, sequence, canvas);
                    }
                }
                else
                {
                    final Point3D pos = getPoint();
                    final double ray = getAdjustedStroke(canvas);
                    final Ellipse2D ellipse = new Ellipse2D.Double(pos.getX() - ray, pos.getY() - ray, ray * 2,
                            ray * 2);

                    // get canvas Z position
                    final int cnvZ = canvas.getPositionZ();
                    // calculate z fade range
                    final double zRange = Math.min(10d, Math.max(3d, sequence.getSizeZ() / 8d));

                    // get Z pos
                    final double z = pos.getZ();
                    // get delta Z (difference between canvas Z position and point Z pos)
                    final double dz = Math.abs(z - cnvZ);

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

                    // ratio for size / opacity
                    final float ratio = 1f - (float) (dz / zRange);

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

                    // draw shape
                    g2.setColor(getDisplayColor());
                    g2.fill(ellipse);
                }

                g2.dispose();
            }
            else
                // just use parent method
                super.drawROI(g, sequence, canvas);
        }

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

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

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

            // 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).
         */
        @Override
        protected void rebuildVtkObjects()
        {
            final VtkCanvas canvas = canvas3d.get();
            // canvas was closed
            if (canvas == null)
                return;

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

            final Sequence seq = canvas.getSequence();
            // nothing to update
            if (seq == null)
                return;

            final Point3D pos = getPoint();

            // 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.SetRadius(getStroke() * scaling[0]);
                vtkSource.SetCenter(pos.getX() * scaling[0], pos.getY() * scaling[1], pos.getZ() * scaling[2]);
                polyMapper.Update();

                // vtkSource.SetRadius(getStroke());
                // vtkSource.SetCenter(pos.getX(), pos.getY(), curZ);
                // polyMapper.Update();
                // actor.SetScale(scaling);
            }
            finally
            {
                vtkPanel.unlock();
            }

            // need to repaint
            painterChanged();
        }

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

            final VtkCanvas cnv = canvas3d.get();
            final Color col = getDisplayColor();
            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);
            }
            finally
            {
                if (vtkPanel != null)
                    vtkPanel.unlock();
            }

            // need to repaint
            painterChanged();
        }
    }

    public static final String ID_POSITION = "position";

    private final Anchor3D position;

    /**
     * @deprecated
     */
    @Deprecated
    public ROI3DPoint(Point3D pt, boolean cm)
    {
        this(pt);
    }

    public ROI3DPoint(Point3D position)
    {
        super(new Line3D());

        this.position = createAnchor(position);
        this.position.setSelected(true);
        addPoint(this.position);

        // set icon
        setIcon(ResourceUtil.ICON_ROI_POINT);
    }

    /**
     * Generic constructor for interactive mode
     */
    public ROI3DPoint(Point5D pt)
    {
        this(pt.toPoint3D());
    }

    public ROI3DPoint(double x, double y, double z)
    {
        this(new Point3D.Double(x, y, z));
    }

    public ROI3DPoint()
    {
        this(new Point3D.Double());
    }

    @Override
    public String getDefaultName()
    {
        return "Point3D";
    }

    @Override
    protected ROI3DShapePainter createPainter()
    {
        return new ROI3DPointPainter();
    }

    public Line3D getLine()
    {
        return (Line3D) shape;
    }

    public Point3D getPoint()
    {
        return position.getPosition();
    }

    /**
     * Called when anchor overlay changed
     */
    @Override
    public void controlPointOverlayChanged(OverlayEvent event)
    {
        // we only mind about painter change from anchor...
        if (event.getType() == OverlayEventType.PAINTER_CHANGED)
        {
            // here we want to have ROI focused when point is selected (special case for ROIPoint)
            if (hasSelectedPoint())
                setFocused(true);

            // anchor changed --> ROI painter changed
            getOverlay().painterChanged();
        }
    }

    @Override
    public boolean contains(ROI roi)
    {
        return false;
    }

    @Override
    public boolean intersects(ROI r)
    {
        // special case of ROI3DPoint
        if (r instanceof ROI3DPoint)
            return onSamePos(((ROI3DPoint) r), false) && ((ROI3DPoint) r).getPoint().equals(getPoint());

        return super.intersects(r);
    }

    @Override
    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, boolean inclusive)
    {
        if ((width <= 0) || (height <= 0))
            return new boolean[0];

        final boolean[] result = new boolean[width * height];
        // 2D bounds
        final Rectangle bounds2d = new Rectangle(x, y, width, height);

        final Point3D pos = getPoint();

        // same Z ?
        if (Math.floor(pos.getZ()) == z)
        {
            // inside the mask ?
            if (bounds2d.contains(pos.toPoint2D()))
            {
                final int px = (int) Math.floor(pos.getX());
                final int py = (int) Math.floor(pos.getY());

                // set the pixel
                result[(px - x) + ((py - y) * width)] = true;
            }
        }

        return result;
    }

    /**
     * roi changed
     */
    @Override
    public void onChanged(CollapsibleEvent object)
    {
        final ROIEvent event = (ROIEvent) object;

        // do here global process on ROI change
        switch (event.getType())
        {
            case PROPERTY_CHANGED:
                final String property = event.getPropertyName();

                // stroke changed --> rebuild vtk object
                if (StringUtil.equals(property, PROPERTY_STROKE))
                    ((ROI3DShapePainter) getOverlay()).needRebuild = true;
                break;

            case SELECTION_CHANGED:
                // always select the control point when ROI was just selected
                if (isSelected())
                    position.setSelected(true);
                break;

            default:
                break;
        }

        super.onChanged(object);
    }

    @Override
    protected void updateShape()
    {
        final Point3D pt = getPoint();
        final double x = pt.getX();
        final double y = pt.getY();
        final double z = pt.getZ();

        getLine().setLine(x, y, z, x, y, z);

        // call super method after shape has been updated
        super.updateShape();
    }

    @Override
    public boolean canAddPoint()
    {
        // this ROI doesn't support point add
        return false;
    }

    @Override
    protected boolean removePoint(IcyCanvas canvas, Anchor3D pt)
    {
        if (canvas != null)
        {
            // remove point on this ROI remove the ROI from current sequence
            canvas.getSequence().removeROI(this);
            return true;
        }

        return false;
    }

    @Override
    public double computeNumberOfContourPoints()
    {
        return 0d;
    }

    @Override
    public double computeNumberOfPoints()
    {
        return 0d;
    }

    @Override
    public boolean loadFromXML(Node node)
    {
        beginUpdate();
        try
        {
            if (!super.loadFromXML(node))
                return false;

            position.loadPositionFromXML(XMLUtil.getElement(node, ID_POSITION));
        }
        finally
        {
            endUpdate();
        }

        return true;
    }

    @Override
    public boolean saveToXML(Node node)
    {
        if (!super.saveToXML(node))
            return false;

        position.savePositionToXML(XMLUtil.setElement(node, ID_POSITION));

        return true;
    }
}