/**
 * 
 */
package icy.vtk;

import icy.image.colormap.IcyColorMap;
import icy.image.lut.LUT;
import icy.image.lut.LUT.LUTChannel;
import icy.math.Scaler;
import icy.type.DataType;
import vtk.vtkColorTransferFunction;
import vtk.vtkFixedPointVolumeRayCastMapper;
import vtk.vtkGPUVolumeRayCastMapper;
import vtk.vtkImageData;
import vtk.vtkOpenGLGPUVolumeRayCastMapper;
import vtk.vtkPiecewiseFunction;
import vtk.vtkRenderer;
import vtk.vtkVolume;
import vtk.vtkVolumeMapper;
import vtk.vtkVolumeProperty;
import vtk.vtkVolumeRayCastMapper;

/**
 * Class to represent a 3D image as a 3D VTK volume object.
 * 
 * @author Stephane
 */
public class VtkImageVolume
{
    /**
     * @deprecated
     */
    @Deprecated
    public static enum VtkVolumeMapperType
    {
        RAYCAST_CPU_FIXEDPOINT
        {
            @Override
            public String toString()
            {
                return "Raycaster (CPU)";
            }
        },
        RAYCAST_GPU_OPENGL
        {
            @Override
            public String toString()
            {
                return "Raycaster (OpenGL)";
            }
        },
        TEXTURE2D_OPENGL
        {
            @Override
            public String toString()
            {
                return "Texture 2D (OpenGL)";
            }
        },
        TEXTURE3D_OPENGL
        {
            @Override
            public String toString()
            {
                return "Texture 3D (OpenGL)";
            }
        };
    }

    public static enum VtkVolumeBlendType
    {
        COMPOSITE
        {
            @Override
            public String toString()
            {
                return "Composite";
            }
        },
        MAXIMUM_INTENSITY
        {
            @Override
            public String toString()
            {
                return "Maximum intensity";
            }
        },
        MINIMUM_INTENSITY
        {
            @Override
            public String toString()
            {
                return "Minimum intensity";
            }
        },

        ADDITIVE
        {
            @Override
            public String toString()
            {
                return "Additive";
            }
        };
    }

    /**
     * volume data
     */
    protected vtkVolumeMapper volumeMapper;
    protected vtkVolume volume;
    protected vtkVolumeProperty volumeProperty;
    protected vtkImageData imageData;

    public VtkImageVolume()
    {
        super();

        // build volume property object
        volumeProperty = new vtkVolumeProperty();
        // default volume setup
        volumeProperty.IndependentComponentsOn();
        volumeProperty.DisableGradientOpacityOn();
        setShade(false);
        setAmbient(0.5d);
        setDiffuse(0.4d);
        setSpecular(0.4d);
        setInterpolationMode(VtkUtil.VTK_LINEAR_INTERPOLATION);

        // build default volume mapper
        volumeMapper = new vtkFixedPointVolumeRayCastMapper();
        ((vtkFixedPointVolumeRayCastMapper) volumeMapper).IntermixIntersectingGeometryOn();
        setSampleResolution(0);

        // initialize volume data
        volume = new vtkVolume();
        volume.SetProperty(volumeProperty);
        // setup volume connection
        volume.SetMapper(volumeMapper);
        // volume should not be "pickable" by default
        volume.SetPickable(0);

        imageData = null;
    }

    public void release()
    {
        // delete all VTK objects
        volume.Delete();
        volumeMapper.RemoveAllInputs();
        volumeMapper.Delete();
        volumeProperty.Delete();

        if (imageData != null)
        {
            imageData.GetPointData().GetScalars().Delete();
            imageData.GetPointData().Delete();
            imageData.Delete();
        }

        // after Delete we need to release reference
        volume = null;
        volumeMapper = null;
        volumeProperty = null;
        imageData = null;
    }

    public vtkVolume getVolume()
    {
        return volume;
    }

    /**
     * Return the number of channel contained in image data.
     */
    protected int getChannelCount()
    {
        if (imageData != null)
            return imageData.GetNumberOfScalarComponents();

        // assume 1 by default
        return 1;
    }

    /**
     * Sets the color map ({@link vtkColorTransferFunction}) used to render the specified channel of
     * image volume.
     */
    public void setColorMap(vtkColorTransferFunction map, int channel)
    {
        vtkColorTransferFunction oldMap = volumeProperty.GetRGBTransferFunction(channel);
        // global colormap, don't release it
        if (volumeProperty.GetRGBTransferFunction() == oldMap)
            oldMap = null;

        volumeProperty.SetColor(channel, map);
        // delete previous color transfer function if any
        if (oldMap != null)
            oldMap.Delete();
    }

    /**
     * Sets the opacity map ({@link vtkPiecewiseFunction}) used to render the specified channel of
     * image volume.
     */
    public void setOpacityMap(vtkPiecewiseFunction map, int channel)
    {
        vtkPiecewiseFunction oldMap = volumeProperty.GetScalarOpacity(channel);
        // global opacity, don't release it
        if (volumeProperty.GetScalarOpacity() == oldMap)
            oldMap = null;

        volumeProperty.SetScalarOpacity(channel, map);
        // delete previous opacity function if any
        if (oldMap != null)
            oldMap.Delete();
    }

    /**
     * Sets the {@link LUT} used to render the image volume.
     */
    public void setLUT(LUT value)
    {
        for (int channel = 0; channel < Math.min(value.getNumChannel(), getChannelCount()); channel++)
            setLUT(value.getLutChannel(channel), channel);
    }

    /**
     * Sets the {@link LUTChannel} used to render the specified channel of image volume.
     */
    public void setLUT(LUTChannel lutChannel, int channel)
    {
        final IcyColorMap colorMap = lutChannel.getColorMap();
        final Scaler scaler = lutChannel.getScaler();

        // SCALAR COLOR FUNCTION
        final vtkColorTransferFunction newColorMap = new vtkColorTransferFunction();

        newColorMap.SetRange(scaler.getLeftIn(), scaler.getRightIn());
        for (int i = 0; i < IcyColorMap.SIZE; i++)
        {
            newColorMap.AddRGBPoint(scaler.unscale(i), colorMap.getNormalizedRed(i), colorMap.getNormalizedGreen(i),
                    colorMap.getNormalizedBlue(i));
        }

        vtkColorTransferFunction oldColorMap = volumeProperty.GetRGBTransferFunction(channel);
        // global colormap, don't release it
        if (volumeProperty.GetRGBTransferFunction() == oldColorMap)
            oldColorMap = null;

        volumeProperty.SetColor(channel, newColorMap);
        // delete previous color transfer function if any
        if (oldColorMap != null)
            oldColorMap.Delete();

        // SCALAR OPACITY FUNCTION
        final vtkPiecewiseFunction newOpacity = new vtkPiecewiseFunction();

        if (colorMap.isEnabled())
        {
            for (int i = 0; i < IcyColorMap.SIZE; i++)
                newOpacity.AddPoint(scaler.unscale(i), colorMap.getNormalizedAlpha(i));
        }
        else
        {
            for (int i = 0; i < IcyColorMap.SIZE; i++)
                newOpacity.AddPoint(scaler.unscale(i), 0d);
        }

        vtkPiecewiseFunction oldOpacity = volumeProperty.GetScalarOpacity(channel);
        // global opacity, don't release it
        if (volumeProperty.GetScalarOpacity() == oldOpacity)
            oldOpacity = null;

        volumeProperty.SetScalarOpacity(channel, newOpacity);
        // delete previous opacity function if any
        if (oldOpacity != null)
            oldOpacity.Delete();
    }

    /**
     * Get the sample resolution of the raycaster volume rendering.<br>
     * <ul>
     * <li>0 = automatic</li>
     * <li>1 = finest (slow)</li>
     * <li>10 = coarse (fast)</li>
     * </ul>
     */
    public double getSampleResolution()
    {
        if (volumeMapper instanceof vtkFixedPointVolumeRayCastMapper)
        {
            final vtkFixedPointVolumeRayCastMapper mapper = (vtkFixedPointVolumeRayCastMapper) volumeMapper;

            if (mapper.GetAutoAdjustSampleDistances() != 0)
                return 0d;

            return mapper.GetImageSampleDistance();
        }
        else if (volumeMapper instanceof vtkVolumeRayCastMapper)
        {
            final vtkVolumeRayCastMapper mapper = (vtkVolumeRayCastMapper) volumeMapper;

            if (mapper.GetAutoAdjustSampleDistances() != 0)
                return 0d;

            return mapper.GetImageSampleDistance();
        }
        else if (volumeMapper instanceof vtkGPUVolumeRayCastMapper)
        {
            final vtkGPUVolumeRayCastMapper mapper = (vtkGPUVolumeRayCastMapper) volumeMapper;

            if (mapper.GetAutoAdjustSampleDistances() != 0)
                return 0d;

            return mapper.GetImageSampleDistance();
        }
        else if (volumeMapper instanceof vtkOpenGLGPUVolumeRayCastMapper)
        {
            final vtkOpenGLGPUVolumeRayCastMapper mapper = (vtkOpenGLGPUVolumeRayCastMapper) volumeMapper;

            if (mapper.GetAutoAdjustSampleDistances() != 0)
                return 0d;

            return mapper.GetImageSampleDistance();
        }

        return 0d;
    }

    /**
     * Set sample resolution for the raycaster volume rendering.<br>
     * <ul>
     * <li>0 = automatic</li>
     * <li>1 = finest (slow)</li>
     * <li>10 = coarse (fast)</li>
     * </ul>
     */
    public void setSampleResolution(double value)
    {
        if (volumeMapper instanceof vtkFixedPointVolumeRayCastMapper)
        {
            final vtkFixedPointVolumeRayCastMapper mapper = (vtkFixedPointVolumeRayCastMapper) volumeMapper;

            if (value == 0d)
                mapper.AutoAdjustSampleDistancesOn();
            else
            {
                mapper.AutoAdjustSampleDistancesOff();
                mapper.SetImageSampleDistance(value);
            }
        }
        else if (volumeMapper instanceof vtkVolumeRayCastMapper)
        {
            final vtkVolumeRayCastMapper mapper = (vtkVolumeRayCastMapper) volumeMapper;

            if (value == 0d)
                mapper.AutoAdjustSampleDistancesOn();
            else
            {
                mapper.AutoAdjustSampleDistancesOff();
                mapper.SetImageSampleDistance(value);
            }
        }
        else if (volumeMapper instanceof vtkGPUVolumeRayCastMapper)
        {
            final vtkGPUVolumeRayCastMapper mapper = (vtkGPUVolumeRayCastMapper) volumeMapper;

            if (value == 0d)
                mapper.AutoAdjustSampleDistancesOn();
            else
            {
                mapper.AutoAdjustSampleDistancesOff();
                mapper.SetImageSampleDistance(value);
            }
        }
        else if (volumeMapper instanceof vtkOpenGLGPUVolumeRayCastMapper)
        {
            final vtkOpenGLGPUVolumeRayCastMapper mapper = (vtkOpenGLGPUVolumeRayCastMapper) volumeMapper;

            if (value == 0d)
                mapper.AutoAdjustSampleDistancesOn();
            else
            {
                mapper.AutoAdjustSampleDistancesOff();
                mapper.SetImageSampleDistance(value);
            }
        }
    }

    public boolean isPickable()
    {
        return (volume.GetPickable() != 0) ? true : false;
    }

    public void setPickable(boolean value)
    {
        volume.SetPickable(value ? 1 : 0);
    }

    /**
     * Returns the XYZ scaling of the volume image
     */
    public double[] getScale()
    {
        return volume.GetScale();
    }

    /**
     * Sets the XYZ scaling of the volume image
     */
    public void setScale(double x, double y, double z)
    {
        volume.SetScale(x, y, z);
    }

    /**
     * Sets the XYZ scaling of the volume image
     */
    public void setScale(double[] xyz)
    {
        volume.SetScale(xyz);
    }

    /**
     * Returns <code>true</code> if shading is enabled (global)
     */
    public boolean getShade()
    {
        return (volumeProperty.GetShade() == 1) ? true : false;
    }

    /**
     * Returns <code>true</code> if shading is enabled for the specified component
     */
    // public boolean getShade(int index)
    // {
    // return (volumeProperty.GetShade(index) == 1) ? true : false;
    // }

    /**
     * Enable / Disable the shading (global)
     */
    public void setShade(boolean value)
    {
        final int num = getChannelCount();
        for (int ch = 0; ch < num; ch++)
            volumeProperty.SetShade(ch, value ? 1 : 0);

        volumeProperty.SetShade(value ? 1 : 0);
    }

    /**
     * Enable / Disable the shading for the specified component
     */
    // public void setShade(int index, boolean value)
    // {
    // volumeProperty.SetShade(index, value ? 1 : 0);
    // }

    /**
     * Returns the ambient lighting coefficient (global)
     */
    public double getAmbient()
    {
        return volumeProperty.GetAmbient();
    }

    /**
     * Returns the ambient lighting coefficient for the specified component
     */
    // public double getAmbient(int index)
    // {
    // return volumeProperty.GetAmbient(index);
    // }

    /**
     * Sets the ambient lighting coefficient (global)
     */
    public void setAmbient(double value)
    {
        final int num = getChannelCount();
        for (int ch = 0; ch < num; ch++)
            volumeProperty.SetAmbient(ch, value);

        volumeProperty.SetAmbient(value);
    }

    /**
     * Sets the ambient lighting coefficient for the specified component
     */
    // public void setAmbient(int index, double value)
    // {
    // volumeProperty.SetAmbient(index, value);
    // }

    /**
     * Returns the diffuse lighting coefficient (global)
     */
    public double getDiffuse()
    {
        return volumeProperty.GetDiffuse();
    }

    /**
     * Returns the diffuse lighting coefficient for the specified component
     */
    // public double getDiffuse(int index)
    // {
    // return volumeProperty.GetDiffuse(index);
    // }

    /**
     * Sets the diffuse lighting coefficient (global)
     */
    public void setDiffuse(double value)
    {
        final int num = getChannelCount();
        for (int ch = 0; ch < num; ch++)
            volumeProperty.SetDiffuse(ch, value);

        volumeProperty.SetDiffuse(value);
    }

    /**
     * Sets the diffuse lighting coefficient for the specified component
     */
    // public void setDiffuse(int index, double value)
    // {
    // volumeProperty.SetDiffuse(index, value);
    // }

    /**
     * Returns the specular lighting coefficient (global)
     */
    public double getSpecular()
    {
        return volumeProperty.GetSpecular();
    }

    /**
     * Returns the specular lighting coefficient for the specified component
     */
    // public double getSpecular(int index)
    // {
    // return volumeProperty.GetSpecular(index);
    // }

    /**
     * Sets the specular lighting coefficient (global)
     */
    public void setSpecular(double value)
    {
        final int num = getChannelCount();
        for (int ch = 0; ch < num; ch++)
            volumeProperty.SetSpecular(ch, value);

        volumeProperty.SetSpecular(value);
    }

    /**
     * Sets the specular lighting coefficient for the specified component
     */
    // public void setSpecular(int index, double value)
    // {
    // volumeProperty.SetSpecular(index, value);
    // }

    /**
     * Returns the specular power (global)
     */
    public double getSpecularPower()
    {
        return volumeProperty.GetSpecularPower();
    }

    /**
     * Returns the specular power for the specified component
     */
    // public double getSpecularPower(int index)
    // {
    // return volumeProperty.GetSpecularPower(index);
    // }

    /**
     * Sets the specular power (global)
     */
    public void setSpecularPower(double value)
    {
        final int num = getChannelCount();
        for (int ch = 0; ch < num; ch++)
            volumeProperty.SetSpecularPower(ch, value);

        volumeProperty.SetSpecularPower(value);
    }

    /**
     * Sets the specular power for the specified component
     */
    // public void setSpecularPower(int index, double value)
    // {
    // volumeProperty.SetSpecularPower(index, value);
    // }

    /**
     * Returns the interpolation method for rendering.<br>
     * Possible values are:
     * <ul>
     * <li>VTK_NEAREST_INTERPOLATION</li>
     * <li>VTK_LINEAR_INTERPOLATION</li>
     * <li>VTK_CUBIC_INTERPOLATION</li>
     * </ul>
     */
    public int getInterpolationMode()
    {
        return volumeProperty.GetInterpolationType();
    }

    /**
     * Sets the interpolation method for rendering.<br>
     * Possible values are:
     * <ul>
     * <li>VTK_NEAREST_INTERPOLATION</li>
     * <li>VTK_LINEAR_INTERPOLATION</li>
     * <li>VTK_CUBIC_INTERPOLATION</li>
     * </ul>
     */
    public void setInterpolationMode(int value)
    {
        volumeProperty.SetInterpolationType(value);
    }

    /**
     * Returns true if selected volume mapper is the GPU accelerated raycaster.
     */
    public boolean getGPURendering()
    {
        return (volumeMapper instanceof vtkOpenGLGPUVolumeRayCastMapper);
    }

    /**
     * Enable GPU volume rendering.
     * 
     * @param value
     *        if <code>true</code> then the GPU accelerated raycaster will be used otherwise the classical CPU
     *        rayscaster is used.
     */
    public boolean setGPURendering(boolean value)
    {
        // volume mapper changed ?
        if (getGPURendering() != value)
        {
            // save the parameters as they can be modified when mapper change
            final VtkVolumeBlendType blendingMode = getBlendingMode();
            final double sampleResolution = getSampleResolution();
            final vtkVolumeMapper newMapper;

            if (value)
            {
                // GPU raycaster
                newMapper = new vtkOpenGLGPUVolumeRayCastMapper();
            }
            else
            {
                // CPU raycaster
                newMapper = new vtkFixedPointVolumeRayCastMapper();
                ((vtkFixedPointVolumeRayCastMapper) newMapper).IntermixIntersectingGeometryOn();
            }

            // setup volume connection
            volume.SetMapper(newMapper);

            // release previous mapper if any
            if (volumeMapper != null)
            {
                volumeMapper.RemoveAllInputs();
                volumeMapper.Delete();
            }

            // update volume mapper
            volumeMapper = newMapper;
            // and connect the image data
            if (imageData != null)
                newMapper.SetInputData(imageData);

            // restore blending and sample resolution
            setBlendingMode(blendingMode);
            setSampleResolution(sampleResolution);

            return true;
        }

        return false;
    }

    /**
     * @deprecated Should always return true now.
     */
    @Deprecated
    public static boolean isMapperSupported(vtkRenderer renderer)
    {
        return true;
    }

    /**
     * Returns the blending method for rendering.
     */
    public VtkVolumeBlendType getBlendingMode()
    {
        return VtkVolumeBlendType.values()[volumeMapper.GetBlendMode()];
    }

    /**
     * Sets the blending method for rendering.
     */
    public void setBlendingMode(VtkVolumeBlendType value)
    {
        volumeMapper.SetBlendMode(value.ordinal());
    }

    /**
     * Get the current volume image data object.
     * 
     * @see VtkUtil#getImageData(Object, DataType, int, int, int, int)
     */
    public vtkImageData getVolumeData()
    {
        return imageData;
    }

    /**
     * Set the volume image data.
     * 
     * @see VtkUtil#getImageData(Object, DataType, int, int, int, int)
     */
    public void setVolumeData(vtkImageData data)
    {
        if (imageData != data)
        {
            // set connection
            volumeMapper.SetInputData(data);

            // release previous volume data memory
            if (imageData != null)
            {
                imageData.GetPointData().GetScalars().Delete();
                imageData.GetPointData().Delete();
                imageData.Delete();
            }

            // set to new image data
            imageData = data;
        }

        updateChannelProperties();
    }

    /**
     * Refresh channel properties
     */
    protected void updateChannelProperties()
    {
        setShade(getShade());
        setAmbient(getAmbient());
        setDiffuse(getDiffuse());
        setSpecular(getSpecular());
        setSpecularPower(getSpecularPower());
    }

    /**
     * Sets the visible state of the image volume object
     */
    public void setVisible(boolean value)
    {
        volume.SetVisibility(value ? 1 : 0);
    }

    /**
     * @return visible state of the image volume object
     */
    public boolean isVisible()
    {
        return (volume.GetVisibility() != 0) ? true : false;
    }
}