/*
 * 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.type;

import icy.math.MathUtil;
import icy.type.collection.array.ArrayDataType;
import icy.type.collection.array.ArrayUtil;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.awt.image.DataBuffer;

import loci.formats.FormatTools;
import ome.xml.model.enums.PixelType;

/**
 * @author stephane
 */
public class TypeUtil
{
    /**
     * Tag for byte data (use DataBuffer reference)
     * 
     * @deprecated use {@link DataType#BYTE} instead
     */
    @Deprecated
    public static final int TYPE_BYTE = DataBuffer.TYPE_BYTE;

    /**
     * Tag for short data (use DataBuffer reference)
     * 
     * @deprecated use {@link DataType#SHORT} instead
     */
    @Deprecated
    public static final int TYPE_SHORT = DataBuffer.TYPE_SHORT;

    /**
     * Tag for int data (use DataBuffer reference)
     * 
     * @deprecated use {@link DataType#INT} instead
     */
    @Deprecated
    public static final int TYPE_INT = DataBuffer.TYPE_INT;

    /**
     * Tag for float data (use DataBuffer reference)
     * 
     * @deprecated use {@link DataType#FLOAT} instead
     */
    @Deprecated
    public static final int TYPE_FLOAT = DataBuffer.TYPE_FLOAT;

    /**
     * Tag for double data (use DataBuffer reference)
     * 
     * @deprecated use {@link DataType#DOUBLE} instead
     */
    @Deprecated
    public static final int TYPE_DOUBLE = DataBuffer.TYPE_DOUBLE;

    /**
     * Tag for undefined data (use DataBuffer reference)
     * 
     * @deprecated use {@link DataType#UNDEFINED} instead
     */
    @Deprecated
    public static final int TYPE_UNDEFINED = DataBuffer.TYPE_UNDEFINED;

    /**
     * Return the size (in byte) of the specified dataType
     * 
     * @deprecated use {@link DataType} method instead
     */
    @Deprecated
    public static int sizeOf(int dataType)
    {
        switch (dataType)
        {
            case TYPE_BYTE:
                return 1;
            case TYPE_SHORT:
                return 2;
            case TYPE_INT:
                return 4;
            case TYPE_FLOAT:
                return 4;
            case TYPE_DOUBLE:
                return 8;
        }

        return 0;
    }

    /**
     * Return true if specified dataType is a float type
     * 
     * @deprecated use {@link DataType} method instead
     */
    @Deprecated
    public static boolean isFloat(int dataType)
    {
        return (dataType == TYPE_FLOAT) || (dataType == TYPE_DOUBLE);
    }

    /**
     * Convert dataType to String
     * 
     * @deprecated use {@link DataType} method instead
     */
    @Deprecated
    public static String toLongString(int dataType)
    {
        switch (dataType)
        {
            case TYPE_BYTE:
                return "byte (8 bpp)";
            case TYPE_SHORT:
                return "short (16 bpp)";
            case TYPE_INT:
                return "int (32 bpp)";
            case TYPE_FLOAT:
                return "float (32 bbp)";
            case TYPE_DOUBLE:
                return "double (64 bbp)";
        }

        return "undefined";
    }

    /**
     * convert dataType to String
     * 
     * @deprecated use {@link DataType} method instead
     */
    @Deprecated
    public static String toString(int dataType)
    {
        switch (dataType)
        {
            case TYPE_BYTE:
                return "byte";
            case TYPE_SHORT:
                return "short";
            case TYPE_INT:
                return "int";
            case TYPE_FLOAT:
                return "float";
            case TYPE_DOUBLE:
                return "double";
        }

        return "undefined";
    }

    public static String toLongString(int dataType, boolean signed)
    {
        if (isFloat(dataType))
            return toLongString(dataType);

        return toString(signed) + " " + toLongString(dataType);
    }

    public static String toString(int dataType, boolean signed)
    {
        if (isFloat(dataType))
            return toString(dataType);

        return toString(signed) + " " + toString(dataType);
    }

    public static String toString(boolean signed)
    {
        if (signed)
            return "signed";

        return "unsigned";
    }

    /**
     * Return all data type as String items (can be used for ComboBox)
     * 
     * @deprecated use {@link DataType#getItems(boolean, boolean, boolean)} instead
     */
    @Deprecated
    public static String[] getItems(boolean longString, boolean wantUndef)
    {
        final String[] result;

        if (wantUndef)
            result = new String[6];
        else
            result = new String[5];

        if (longString)
        {
            result[0] = toLongString(TYPE_BYTE);
            result[1] = toLongString(TYPE_SHORT);
            result[2] = toLongString(TYPE_INT);
            result[3] = toLongString(TYPE_FLOAT);
            result[4] = toLongString(TYPE_DOUBLE);
            if (wantUndef)
                result[5] = toLongString(TYPE_UNDEFINED);
        }
        else
        {
            result[0] = toString(TYPE_BYTE);
            result[1] = toString(TYPE_SHORT);
            result[2] = toString(TYPE_INT);
            result[3] = toString(TYPE_FLOAT);
            result[4] = toString(TYPE_DOUBLE);
            if (wantUndef)
                result[5] = toString(TYPE_UNDEFINED);
        }

        return result;
    }

    /**
     * Return the dataType of specified array (passed as Object)
     * 
     * @deprecated use {@link ArrayDataType#getArrayDataType(Object)} instead
     */
    @Deprecated
    public static ArrayTypeInfo getTypeInfo(Object value)
    {
        final ArrayTypeInfo result = new ArrayTypeInfo(TYPE_UNDEFINED, 0);

        if (value instanceof byte[])
        {
            result.type = TYPE_BYTE;
            result.dim = 1;
        }
        else if (value instanceof short[])
        {
            result.type = TYPE_SHORT;
            result.dim = 1;
        }
        else if (value instanceof int[])
        {
            result.type = TYPE_INT;
            result.dim = 1;
        }
        else if (value instanceof float[])
        {
            result.type = TYPE_FLOAT;
            result.dim = 1;
        }
        else if (value instanceof double[])
        {
            result.type = TYPE_DOUBLE;
            result.dim = 1;
        }
        else if (value instanceof byte[][])
        {
            result.type = TYPE_BYTE;
            result.dim = 2;
        }
        else if (value instanceof short[][])
        {
            result.type = TYPE_SHORT;
            result.dim = 2;
        }
        else if (value instanceof int[][])
        {
            result.type = TYPE_INT;
            result.dim = 2;
        }
        else if (value instanceof float[][])
        {
            result.type = TYPE_FLOAT;
            result.dim = 2;
        }
        else if (value instanceof double[][])
        {
            result.type = TYPE_DOUBLE;
            result.dim = 2;
        }
        else if (value instanceof byte[][][])
        {
            result.type = TYPE_BYTE;
            result.dim = 3;
        }
        else if (value instanceof short[][][])
        {
            result.type = TYPE_SHORT;
            result.dim = 3;
        }
        else if (value instanceof int[][][])
        {
            result.type = TYPE_INT;
            result.dim = 3;
        }
        else if (value instanceof float[][][])
        {
            result.type = TYPE_FLOAT;
            result.dim = 3;
        }
        else if (value instanceof double[][][])
        {
            result.type = TYPE_DOUBLE;
            result.dim = 3;
        }
        else if (value instanceof byte[][][][])
        {
            result.type = TYPE_BYTE;
            result.dim = 4;
        }
        else if (value instanceof short[][][][])
        {
            result.type = TYPE_SHORT;
            result.dim = 4;
        }
        else if (value instanceof int[][][][])
        {
            result.type = TYPE_INT;
            result.dim = 4;
        }
        else if (value instanceof float[][][][])
        {
            result.type = TYPE_FLOAT;
            result.dim = 4;
        }
        else if (value instanceof double[][][][])
        {
            result.type = TYPE_DOUBLE;
            result.dim = 4;
        }
        else if (value instanceof byte[][][][][])
        {
            result.type = TYPE_BYTE;
            result.dim = 5;
        }
        else if (value instanceof short[][][][][])
        {
            result.type = TYPE_SHORT;
            result.dim = 5;
        }
        else if (value instanceof int[][][][][])
        {
            result.type = TYPE_INT;
            result.dim = 5;
        }
        else if (value instanceof float[][][][][])
        {
            result.type = TYPE_FLOAT;
            result.dim = 5;
        }
        else if (value instanceof double[][][][][])
        {
            result.type = TYPE_DOUBLE;
            result.dim = 5;
        }

        return result;
    }

    /**
     * Return the dataType of specified array (passed as Object)
     * 
     * @deprecated use {@link ArrayUtil#getDataType(Object)} method instead
     */
    @Deprecated
    public static int getDataType(Object value)
    {
        return getTypeInfo(value).type;
    }

    /**
     * Return the number of dimension specified array (passed as Object)
     * 
     * @deprecated use {@link ArrayUtil#getDim(Object)} method instead
     */
    @Deprecated
    public static int getNumDimension(Object value)
    {
        return getTypeInfo(value).dim;
    }

    /**
     * Return the dataType from string
     * 
     * @deprecated use {@link DataType#getDataType(String)} method instead
     */
    @Deprecated
    public static int getDataType(String value)
    {
        final String s = value.toLowerCase();

        if (toString(TYPE_BYTE).equals(s) || toLongString(TYPE_BYTE).equals(s))
            return TYPE_BYTE;
        if (toString(TYPE_SHORT).equals(s) || toLongString(TYPE_SHORT).equals(s))
            return TYPE_SHORT;
        if (toString(TYPE_INT).equals(s) || toLongString(TYPE_INT).equals(s))
            return TYPE_INT;
        if (toString(TYPE_FLOAT).equals(s) || toLongString(TYPE_FLOAT).equals(s))
            return TYPE_FLOAT;
        if (toString(TYPE_DOUBLE).equals(s) || toLongString(TYPE_DOUBLE).equals(s))
            return TYPE_DOUBLE;

        return TYPE_UNDEFINED;
    }

    /**
     * Return the dataType corresponding to the specified DataBuffer type
     * 
     * @deprecated use {@link DataType#getDataTypeFromDataBufferType(int)} instead
     */
    @Deprecated
    public static int dataBufferTypeToDataType(int dataBufferType)
    {
        switch (dataBufferType)
        {
            case DataBuffer.TYPE_BYTE:
                return TypeUtil.TYPE_BYTE;

            case DataBuffer.TYPE_SHORT:
            case DataBuffer.TYPE_USHORT:
                return TypeUtil.TYPE_SHORT;

            case DataBuffer.TYPE_INT:
                return TypeUtil.TYPE_INT;

            case DataBuffer.TYPE_FLOAT:
                return TypeUtil.TYPE_FLOAT;

            case DataBuffer.TYPE_DOUBLE:
                return TypeUtil.TYPE_DOUBLE;

            default:
                return TypeUtil.TYPE_UNDEFINED;
        }
    }

    /**
     * Return true if specified DataBuffer type is considered as signed type
     */
    public static boolean isSignedDataBufferType(int type)
    {
        switch (type)
        {
            case DataBuffer.TYPE_BYTE:
                // assume byte is unsigned
                return false;

            case DataBuffer.TYPE_SHORT:
                return true;

            case DataBuffer.TYPE_USHORT:
                return false;

            case DataBuffer.TYPE_INT:
                // assume int is unsigned
                return false;

            case DataBuffer.TYPE_FLOAT:
                return true;

            case DataBuffer.TYPE_DOUBLE:
                return true;

            default:
                return false;
        }
    }

    /**
     * Return the data type corresponding to the specified FormatTools type
     * 
     * @deprecated use {@link DataType#getDataTypeFromFormatToolsType(int)} instead
     */
    @Deprecated
    public static int formatToolsTypeToDataType(int type)
    {
        switch (type)
        {
            case FormatTools.INT8:
            case FormatTools.UINT8:
                return TypeUtil.TYPE_BYTE;

            case FormatTools.INT16:
            case FormatTools.UINT16:
                return TypeUtil.TYPE_SHORT;

            case FormatTools.INT32:
            case FormatTools.UINT32:
                return TypeUtil.TYPE_INT;

            case FormatTools.FLOAT:
                return TypeUtil.TYPE_FLOAT;

            case FormatTools.DOUBLE:
                return TypeUtil.TYPE_DOUBLE;

            default:
                return TypeUtil.TYPE_UNDEFINED;
        }
    }

    /**
     * Return true if specified FormatTools type is a signed type
     */
    public static boolean isSignedFormatToolsType(int type)
    {
        switch (type)
        {
            case FormatTools.INT8:
            case FormatTools.INT16:
            case FormatTools.INT32:
            case FormatTools.FLOAT:
            case FormatTools.DOUBLE:
                return true;

            case FormatTools.UINT8:
            case FormatTools.UINT16:
            case FormatTools.UINT32:
                return false;

            default:
                return false;
        }
    }

    /**
     * Return the dataType corresponding to the specified PixelType
     * 
     * @deprecated use {@link DataType#getDataTypeFromPixelType(PixelType)} instead
     */
    @Deprecated
    public static int pixelTypeToDataType(PixelType type)
    {
        switch (type)
        {
            case INT8:
            case UINT8:
                return TypeUtil.TYPE_BYTE;

            case INT16:
            case UINT16:
                return TypeUtil.TYPE_SHORT;

            case INT32:
            case UINT32:
                return TypeUtil.TYPE_INT;

            case FLOAT:
                return TypeUtil.TYPE_FLOAT;

            case DOUBLE:
                return TypeUtil.TYPE_DOUBLE;

            default:
                return TypeUtil.TYPE_UNDEFINED;
        }
    }

    /**
     * Return true if specified PixelType is signed
     */
    public static boolean isSignedPixelType(PixelType type)
    {
        switch (type)
        {
            case INT8:
            case INT16:
            case INT32:
            case FLOAT:
            case DOUBLE:
                return true;

            case UINT8:
            case UINT16:
            case UINT32:
                return false;

            default:
                return false;
        }
    }

    /**
     * Return the PixelType corresponding to the specified data type
     * 
     * @deprecated use {@link DataType#toPixelType()} instead
     */
    @Deprecated
    public static PixelType dataTypeToPixelType(int dataType, boolean signed)
    {
        switch (dataType)
        {
            case TypeUtil.TYPE_BYTE:
                if (signed)
                    return PixelType.INT8;
                return PixelType.UINT8;

            case TypeUtil.TYPE_SHORT:
                if (signed)
                    return PixelType.INT16;
                return PixelType.UINT16;

            case TypeUtil.TYPE_FLOAT:
                return PixelType.FLOAT;

            case TypeUtil.TYPE_DOUBLE:
                return PixelType.DOUBLE;

            default:
                if (signed)
                    return PixelType.INT32;
                return PixelType.UINT32;
        }
    }

    /**
     * Unsign the specified byte value and return it as int
     */
    public static int unsign(byte value)
    {
        return value & 0xFF;
    }

    /**
     * Unsign the specified short value and return it as int
     */
    public static int unsign(short value)
    {
        return value & 0xFFFF;
    }

    /**
     * Unsign the specified byte value and return it as long
     */
    public static long unsignL(byte value)
    {
        return value & 0xFFL;
    }

    /**
     * Unsign the specified short value and return it as long
     */
    public static long unsignL(short value)
    {
        return value & 0xFFFFL;
    }

    /**
     * Unsign the specified int value and return it as long
     */
    public static long unsign(int value)
    {
        return value & 0xFFFFFFFFL;
    }

    /**
     * Unsign the specified long value and return it as double (possible information loss)
     */
    public static double unsign(long value)
    {
        final double result = value;
        if (result < 0d)
            return MathUtil.POW2_64_DOUBLE + result;

        return result;
    }

    /**
     * Unsign the specified long value and return it as float (possible information loss)
     */
    public static float unsignF(long value)
    {
        final float result = value;
        if (result < 0f)
            return MathUtil.POW2_64_FLOAT + result;

        return result;
    }

    public static int toShort(byte value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    public static int toInt(byte value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    public static int toInt(short value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    public static int toInt(float value)
    {
        // we have to cast to long before else value is limited to
        // [Integer.MIN_VALUE..Integer.MAX_VALUE] range
        return (int) (long) value;
    }

    public static int toInt(double value)
    {
        // we have to cast to long before else value is limited to
        // [Integer.MIN_VALUE..Integer.MAX_VALUE] range
        return (int) (long) value;
    }

    public static long toLong(byte value, boolean signed)
    {
        if (signed)
            return value;

        return unsignL(value);
    }

    public static long toLong(short value, boolean signed)
    {
        if (signed)
            return value;

        return unsignL(value);
    }

    public static long toLong(int value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    public static long toLong(float value)
    {
        // handle unsigned long type (else value is clamped to Long.MAX_VALUE)
        if (value > DataType.LONG_MAX_VALUE_F)
            return ((long) (value - DataType.LONG_MAX_VALUE_F)) + 0x8000000000000000L;

        return (long) value;
    }

    public static long toLong(double value)
    {
        // handle unsigned long type (else value is clamped to Long.MAX_VALUE)
        if (value > DataType.LONG_MAX_VALUE)
            return ((long) (value - DataType.LONG_MAX_VALUE)) + 0x8000000000000000L;

        return (long) value;
    }

    public static float toFloat(byte value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    public static float toFloat(short value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    public static float toFloat(int value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    public static float toFloat(long value, boolean signed)
    {
        if (signed)
            return value;

        return unsignF(value);
    }

    public static double toDouble(byte value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    public static double toDouble(short value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    public static double toDouble(int value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    public static double toDouble(long value, boolean signed)
    {
        if (signed)
            return value;

        return unsign(value);
    }

    /**
     * Safe integer evaluation from Integer object.<br>
     * Return <code>defaultValue</code> if specified object is null.
     */
    public static int getInt(Integer obj, int defaultValue)
    {
        if (obj == null)
            return defaultValue;

        return obj.intValue();
    }

    /**
     * Safe float evaluation from Float object.<br>
     * Return <code>defaultValue</code> if specified object is null.
     */
    public static float getFloat(Float obj, float defaultValue)
    {
        if (obj == null)
            return defaultValue;

        return obj.floatValue();
    }

    /**
     * Safe double evaluation from Double object.<br>
     * Return <code>defaultValue</code> if <code>obj</code> is null or equal to infinite with
     * <code>allowInfinite</code> set to false.
     */
    public static double getDouble(Double obj, double defaultValue, boolean allowInfinite)
    {
        if (obj == null)
            return defaultValue;

        final double result = obj.doubleValue();

        if ((!allowInfinite) && Double.isInfinite(result))
            return defaultValue;

        return result;
    }

    /**
     * Safe double evaluation from Double object.<br>
     * Return <code>defaultValue</code> if specified object is null.
     */
    public static double getDouble(Double obj, double defaultValue)
    {
        return getDouble(obj, defaultValue, true);
    }

    public static Point toPoint(Point2D p)
    {
        return new Point((int) p.getX(), (int) p.getY());
    }

    public static Point2D.Double toPoint2D(Point p)
    {
        return new Point2D.Double(p.x, p.y);
    }

    public static Point toPoint(Dimension d)
    {
        return new Point(d.width, d.height);
    }

    public static Point2D.Double toPoint2D(Dimension d)
    {
        return new Point2D.Double(d.width, d.height);
    }

    public static Dimension toDimension(Point p)
    {
        return new Dimension(p.x, p.y);
    }

    /**
     * Create an array of Point from the input integer array.<br>
     * <br>
     * The format of the input array should be as follow:<br>
     * <code>input.lenght</code> = number of point * 2.<br>
     * <code>input[(pt * 2) + 0]</code> = X coordinate for point <i>pt</i><br>
     * <code>input[(pt * 2) + 1]</code> = Y coordinate for point <i>pt</i><br>
     */
    public static Point[] toPoint(int[] input)
    {
        final Point[] result = new Point[input.length / 2];

        int pt = 0;
        for (int i = 0; i < input.length; i += 2)
            result[pt++] = new Point(input[i + 0], input[i + 1]);

        return result;
    }

    /**
     * Return the minimum value for the specified dataType
     * 
     * @deprecated use {@link DataType#getMinValue()} instead
     */
    @Deprecated
    public static double getMinValue(int dataType, boolean signed)
    {
        return getDefaultBounds(dataType, signed)[0];
    }

    /**
     * Return the maximum value for the specified dataType
     * 
     * @deprecated use {@link DataType#getMaxValue()} instead
     */
    @Deprecated
    public static double getMaxValue(int dataType, boolean signed)
    {
        return getDefaultBounds(dataType, signed)[1];
    }

    /**
     * Get the default bounds for the specified dataType
     * 
     * @deprecated use {@link DataType#getDefaultBounds()} instead
     */
    @Deprecated
    public static double[] getDefaultBounds(int dataType, boolean signed)
    {
        return DataType.getDataType(dataType, signed).getDefaultBounds();
    }
}