/*
 * Copyright 2010-2013 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.vtk;

import icy.image.colormap.IcyColorMap;
import icy.image.lut.LUT.LUTChannel;
import icy.math.Scaler;
import icy.system.IcyExceptionHandler;
import icy.type.DataType;
import icy.type.collection.array.Array2DUtil;
import icy.type.collection.array.ArrayUtil;
import vtk.vtkActor;
import vtk.vtkActor2D;
import vtk.vtkActor2DCollection;
import vtk.vtkActorCollection;
import vtk.vtkCellArray;
import vtk.vtkColorTransferFunction;
import vtk.vtkDataArray;
import vtk.vtkDoubleArray;
import vtk.vtkFloatArray;
import vtk.vtkIdTypeArray;
import vtk.vtkImageData;
import vtk.vtkIntArray;
import vtk.vtkLongArray;
import vtk.vtkPiecewiseFunction;
import vtk.vtkPoints;
import vtk.vtkProp;
import vtk.vtkPropCollection;
import vtk.vtkRenderer;
import vtk.vtkShortArray;
import vtk.vtkUnsignedCharArray;
import vtk.vtkUnsignedIntArray;
import vtk.vtkUnsignedLongArray;
import vtk.vtkUnsignedShortArray;

/**
 * @author Stephane
 */
public class VtkUtil
{
    // VTK type
    public final static int VTK_VOID = 0;
    public final static int VTK_BIT = 1;
    public final static int VTK_CHAR = 2;
    public final static int VTK_SIGNED_CHAR = 15;
    public final static int VTK_UNSIGNED_CHAR = 3;
    public final static int VTK_SHORT = 4;
    public final static int VTK_UNSIGNED_SHORT = 5;
    public final static int VTK_INT = 6;
    public final static int VTK_UNSIGNED_INT = 7;
    public final static int VTK_LONG = 8;
    public final static int VTK_UNSIGNED_LONG = 9;
    public final static int VTK_FLOAT = 10;
    public final static int VTK_DOUBLE = 11;
    public final static int VTK_ID = 12;

    // VTK interpolation
    public final static int VTK_NEAREST_INTERPOLATION = 0;
    public final static int VTK_LINEAR_INTERPOLATION = 1;
    public final static int VTK_CUBIC_INTERPOLATION = 2;

    // VTK bounding box
    public final static int VTK_FLY_OUTER_EDGES = 0;
    public final static int VTK_FLY_CLOSEST_TRIAD = 1;
    public final static int VTK_FLY_FURTHEST_TRIAD = 2;
    public final static int VTK_FLY_STATIC_TRIAD = 3;
    public final static int VTK_FLY_STATIC_EDGES = 4;

    public final static int VTK_TICKS_INSIDE = 0;
    public final static int VTK_TICKS_OUTSIDE = 1;
    public final static int VTK_TICKS_BOTH = 2;

    public final static int VTK_GRID_LINES_ALL = 0;
    public final static int VTK_GRID_LINES_CLOSEST = 1;
    public final static int VTK_GRID_LINES_FURTHEST = 2;

    /**
     * Returns the VTK type corresponding to the specified DataType
     */
    public static int getVtkType(DataType type)
    {
        switch (type)
        {
            default:
            case UBYTE:
            case BYTE:
                return VTK_UNSIGNED_CHAR;

                // FIXME: signed char not supported by VTK java wrapper ??
                // case BYTE:
                // return VTK_CHAR;
                // return VTK_SIGNED_CHAR;

            case USHORT:
                return VTK_UNSIGNED_SHORT;
            case SHORT:
                return VTK_SHORT;
            case UINT:
                return VTK_UNSIGNED_INT;
            case INT:
                return VTK_INT;
            case ULONG:
                return VTK_UNSIGNED_LONG;
            case LONG:
                return VTK_LONG;
            case FLOAT:
                return VTK_FLOAT;
            case DOUBLE:
                return VTK_DOUBLE;
        }
    }

    /**
     * Add an actor to the specified renderer.<br>
     * If the actor is already existing in the renderer then no operation is done.
     */
    public static void addProp(vtkRenderer renderer, vtkProp prop)
    {
        if ((renderer == null) || (prop == null))
            return;

        // actor not yet present in renderer ? --> add it
        if (!VtkUtil.findProp(renderer, prop))
            renderer.AddViewProp(prop);
    }

    /**
     * @deprecated Use {@link #addProp(vtkRenderer, vtkProp)} instead.
     */
    @Deprecated
    public static void addActor(vtkRenderer renderer, vtkActor actor)
    {
        if ((renderer == null) || (actor == null))
            return;

        // actor not yet present in renderer ? --> add it
        if (!VtkUtil.findActor(renderer, actor))
            renderer.AddActor(actor);
    }

    /**
     * @deprecated Use {@link #addProp(vtkRenderer, vtkProp)} instead.
     */
    @Deprecated
    public static void addActor2D(vtkRenderer renderer, vtkActor2D actor)
    {
        if ((renderer == null) || (actor == null))
            return;

        // actor not yet present in renderer ? --> add it
        if (!VtkUtil.findActor2D(renderer, actor))
            renderer.AddActor2D(actor);
    }

    /**
     * Remove an actor from the specified renderer.
     */
    public static void removeProp(vtkRenderer renderer, vtkProp actor)
    {
        renderer.RemoveViewProp(actor);
    }

    /**
     * Return true if the renderer contains the specified actor
     */
    public static boolean findProp(vtkRenderer renderer, vtkProp actor)
    {
        if ((renderer == null) || (actor == null))
            return false;

        final vtkPropCollection actors = renderer.GetViewProps();

        actors.InitTraversal();
        for (int i = 0; i < actors.GetNumberOfItems(); i++)
        {
            final vtkProp curActor = actors.GetNextProp();

            // already present --> exit
            if (curActor == actor)
                return true;
        }

        return false;
    }

    /**
     * @deprecated Use {@link #findProp(vtkRenderer, vtkProp)} instead.
     */
    @Deprecated
    public static boolean findActor(vtkRenderer renderer, vtkActor actor)
    {
        if ((renderer == null) || (actor == null))
            return false;

        final vtkActorCollection actors = renderer.GetActors();

        actors.InitTraversal();
        for (int i = 0; i < actors.GetNumberOfItems(); i++)
        {
            final vtkActor curActor = actors.GetNextActor();

            // already present --> exit
            if (curActor == actor)
                return true;

            // // search in sub actor
            // if (findActor(curActor, actor))
            // return true;
        }

        return false;
    }

    /**
     * @deprecated Use {@link #findProp(vtkRenderer, vtkProp)} instead.
     */
    @Deprecated
    public static boolean findActor2D(vtkRenderer renderer, vtkActor2D actor)
    {
        if ((renderer == null) || (actor == null))
            return false;

        final vtkActor2DCollection actors = renderer.GetActors2D();

        actors.InitTraversal();
        for (int i = 0; i < actors.GetNumberOfItems(); i++)
        {
            final vtkActor2D curActor = actors.GetNextActor2D();

            // already present --> exit
            if (curActor == actor)
                return true;

            // // search in sub actor
            // if (findActor2D(curActor, actor))
            // return true;
        }

        return false;
    }

    /**
     * Return a 1D cells array from a 2D indexes array
     */
    public static int[] prepareCells(int[][] indexes)
    {
        final int len = indexes.length;

        int total_len = 0;
        for (int i = 0; i < len; i++)
            total_len += indexes[i].length + 1;

        final int[] result = new int[total_len];

        int offset = 0;
        for (int i = 0; i < len; i++)
        {
            final int[] s_cells = indexes[i];
            final int s_len = s_cells.length;

            result[offset++] = s_len;
            for (int j = 0; j < s_len; j++)
                result[offset++] = s_cells[j];
        }

        return result;
    }

    /**
     * Return a 1D cells array from a 1D indexes array and num vertex per cell (polygon)
     */
    public static int[] prepareCells(int numVertexPerCell, int[] indexes)
    {
        final int num_cells = indexes.length / numVertexPerCell;
        final int[] result = new int[num_cells * (numVertexPerCell + 1)];

        int off_dst = 0;
        int off_src = 0;
        for (int i = 0; i < num_cells; i++)
        {
            result[off_dst++] = numVertexPerCell;

            for (int j = 0; j < numVertexPerCell; j++)
                result[off_dst++] = indexes[off_src + j];

            off_src += numVertexPerCell;
        }

        return result;
    }

    public static vtkDataArray getVtkArray(Object array, boolean signed)
    {
        switch (ArrayUtil.getDataType(array))
        {
            case BYTE:
                return getUCharArray((byte[]) array);
            case SHORT:
                if (signed)
                    return getUShortArray((short[]) array);
                return getShortArray((short[]) array);
            case INT:
                if (signed)
                    return getUIntArray((int[]) array);
                return getIntArray((int[]) array);
            case LONG:
                if (signed)
                    return getULongArray((long[]) array);
                return getLongArray((long[]) array);
            case FLOAT:
                return getFloatArray((float[]) array);
            case DOUBLE:
                return getDoubleArray((double[]) array);
            default:
                return null;
        }
    }

    public static vtkUnsignedCharArray getUCharArray(byte[] array)
    {
        final vtkUnsignedCharArray result = new vtkUnsignedCharArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkUnsignedShortArray getUShortArray(short[] array)
    {
        final vtkUnsignedShortArray result = new vtkUnsignedShortArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkUnsignedIntArray getUIntArray(int[] array)
    {
        final vtkUnsignedIntArray result = new vtkUnsignedIntArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkUnsignedLongArray getULongArray(long[] array)
    {
        final vtkUnsignedLongArray result = new vtkUnsignedLongArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkShortArray getShortArray(short[] array)
    {
        final vtkShortArray result = new vtkShortArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkIntArray getIntArray(int[] array)
    {
        final vtkIntArray result = new vtkIntArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkLongArray getLongArray(long[] array)
    {
        final vtkLongArray result = new vtkLongArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkFloatArray getFloatArray(float[] array)
    {
        final vtkFloatArray result = new vtkFloatArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkDoubleArray getDoubleArray(double[] array)
    {
        final vtkDoubleArray result = new vtkDoubleArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkIdTypeArray getIdTypeArray(int[] array)
    {
        final vtkIdTypeArray result = new vtkIdTypeArray();
        final vtkIntArray iarray = getIntArray(array);

        result.DeepCopy(iarray);

        return result;
    }

    public static int[] getArray(vtkIdTypeArray array)
    {
        final vtkIntArray iarray = new vtkIntArray();

        iarray.DeepCopy(array);

        return iarray.GetJavaArray();
    }

    /**
     * Get vtkPoints from double[]
     */
    public static vtkPoints getPoints(double[] points)
    {
        final vtkPoints result = new vtkPoints();
        final vtkDoubleArray array = getDoubleArray(points);

        array.SetNumberOfComponents(3);
        result.SetData(array);

        return result;
    }

    /**
     * Get vtkPoints from double[][3]
     */
    public static vtkPoints getPoints(double[][] points)
    {
        return getPoints(Array2DUtil.toDoubleArray1D(points));
    }

    /**
     * Get vtkPoints from float[]
     */
    public static vtkPoints getPoints(float[] points)
    {
        final vtkPoints result = new vtkPoints();
        final vtkFloatArray array = getFloatArray(points);

        array.SetNumberOfComponents(3);
        result.SetData(array);

        return result;
    }

    /**
     * Get vtkPoints from float[][3]
     */
    public static vtkPoints getPoints(float[][] points)
    {
        return getPoints(Array2DUtil.toFloatArray1D(points));
    }

    /**
     * Get vtkCellArray from a 1D prepared cells array ( {n, i1, i2, ..., n, i1, i2,...} )
     */
    public static vtkCellArray getCells(int numCell, int[] cells)
    {
        final vtkCellArray result = new vtkCellArray();

        result.SetCells(numCell, getIdTypeArray(cells));

        return result;
    }

    /**
     * Creates and returns a {@link vtkImageData} object from the specified 1D array data.
     */
    public static vtkImageData getImageData(Object data, DataType dataType, int sizeX, int sizeY, int sizeZ, int sizeC)
    {
        final vtkImageData result;
        final vtkDataArray array;

        // create a new image data structure
        result = new vtkImageData();
        result.SetDimensions(sizeX, sizeY, sizeZ);
        result.SetExtent(0, sizeX - 1, 0, sizeY - 1, 0, sizeZ - 1);
        // pre-allocate data
        result.AllocateScalars(getVtkType(dataType), sizeC);
        // get array structure
        array = result.GetPointData().GetScalars();

        switch (dataType)
        {
            case UBYTE:
            case BYTE:
                ((vtkUnsignedCharArray) array).SetJavaArray((byte[]) data);
                break;
            case USHORT:
                ((vtkUnsignedShortArray) array).SetJavaArray((short[]) data);
                break;
            case SHORT:
                ((vtkShortArray) array).SetJavaArray((short[]) data);
                break;
            case UINT:
                ((vtkUnsignedIntArray) array).SetJavaArray((int[]) data);
                break;
            case INT:
                ((vtkIntArray) array).SetJavaArray((int[]) data);
                break;
            case FLOAT:
                ((vtkFloatArray) array).SetJavaArray((float[]) data);
                break;
            case DOUBLE:
                ((vtkDoubleArray) array).SetJavaArray((double[]) data);
                break;
        }

        return result;
    }

    /**
     * Creates and returns the color map in {@link vtkColorTransferFunction} format from the
     * specified {@link LUTChannel}.
     */
    public static vtkColorTransferFunction getColorMap(LUTChannel lutChannel)
    {
        final IcyColorMap colorMap = lutChannel.getColorMap();
        final Scaler scaler = lutChannel.getScaler();

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

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

        return result;
    }

    /**
     * Creates and returns the opacity map in {@link vtkPiecewiseFunction} format from the specified
     * {@link LUTChannel}.
     */
    public static vtkPiecewiseFunction getOpacityMap(LUTChannel lutChannel)
    {
        final IcyColorMap colorMap = lutChannel.getColorMap();
        final Scaler scaler = lutChannel.getScaler();

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

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

        return result;
    }
}