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

/**
 * Math utilities class.
 * 
 * @author stephane
 */
public class MathUtil
{
    public static final String INFINITE_STRING = "\u221E";

    public static final double POW2_8_DOUBLE = Math.pow(2, 8);
    public static final float POW2_8_FLOAT = (float) POW2_8_DOUBLE;
    public static final double POW2_16_DOUBLE = Math.pow(2, 16);
    public static final float POW2_16_FLOAT = (float) POW2_16_DOUBLE;
    public static final double POW2_32_DOUBLE = Math.pow(2, 32);
    public static final float POW2_32_FLOAT = (float) POW2_32_DOUBLE;
    public static final double POW2_64_DOUBLE = Math.pow(2, 64);
    public static final float POW2_64_FLOAT = (float) POW2_64_DOUBLE;

    /**
     * @deprecated please use {@link UnitUtil#getBytesString(double)} instead.
     */
    @Deprecated
    public static String getBytesString(double value)
    {
        return UnitUtil.getBytesString(value);
    }

    public static double frac(double value)
    {
        return value - Math.floor(value);
    }

    /**
     * Normalize an array
     * 
     * @param array
     *        elements to normalize
     */
    public static void normalize(float[] array)
    {
        final float max = ArrayMath.max(array);

        if (max != 0)
            divide(array, max);
        else
        {
            final float min = ArrayMath.min(array);
            if (min != 0)
                divide(array, min);
        }
    }

    /**
     * Normalize an array
     * 
     * @param array
     *        elements to normalize
     */
    public static void normalize(double[] array)
    {
        final double max = ArrayMath.max(array);

        if (max != 0)
            divide(array, max);
        else
        {
            final double min = ArrayMath.min(array);
            if (min != 0)
                divide(array, min);
        }
    }

    /**
     * Replace all values in the array by their logarithm<br>
     * Be careful, all values should be >0 values
     * 
     * @param array
     *        elements to logarithm
     */
    public static void log(double[] array)
    {
        final int len = array.length;

        for (int i = 0; i < len; i++)
            array[i] = Math.log(array[i]);
    }

    /**
     * Replace all values in the array by their logarithm<br>
     * Be careful, all values should be >0 values
     * 
     * @param array
     *        elements to logarithm
     */
    public static void log(float[] array)
    {
        final int len = array.length;

        for (int i = 0; i < len; i++)
            array[i] = (float) Math.log(array[i]);
    }

    /**
     * Add the the specified value to all elements in an array
     * 
     * @param array
     *        elements to modify
     * @param value
     */
    public static void add(double[] array, double value)
    {
        final int len = array.length;

        for (int i = 0; i < len; i++)
            array[i] = array[i] + value;
    }

    /**
     * Add the the specified value to all elements in an array
     * 
     * @param array
     *        elements to modify
     * @param value
     */
    public static void add(float[] array, float value)
    {
        final int len = array.length;

        for (int i = 0; i < len; i++)
            array[i] = array[i] + value;
    }

    /**
     * Multiply and add all elements in an array by the specified values
     * 
     * @param array
     *        elements to modify
     * @param mulValue
     * @param addValue
     */
    public static void madd(double[] array, double mulValue, double addValue)
    {
        final int len = array.length;

        for (int i = 0; i < len; i++)
            array[i] = (array[i] * mulValue) + addValue;
    }

    /**
     * Multiply and add all elements in an array by the specified values
     * 
     * @param array
     *        elements to modify
     * @param mulValue
     * @param addValue
     */
    public static void madd(float[] array, float mulValue, float addValue)
    {
        final int len = array.length;

        for (int i = 0; i < len; i++)
            array[i] = (array[i] * mulValue) + addValue;
    }

    /**
     * Multiply all elements in an array by the specified value
     * 
     * @param array
     *        elements to modify
     * @param value
     *        value to multiply by
     */
    public static void mul(double[] array, double value)
    {
        final int len = array.length;

        for (int i = 0; i < len; i++)
            array[i] = array[i] * value;
    }

    /**
     * Multiply all elements in an array by the specified value
     * 
     * @param array
     *        elements to modify
     * @param value
     *        value to multiply by
     */
    public static void mul(float[] array, float value)
    {
        final int len = array.length;

        for (int i = 0; i < len; i++)
            array[i] = array[i] * value;
    }

    /**
     * Divides all elements in an array by the specified value
     * 
     * @param array
     *        elements to modify
     * @param value
     *        value used as divisor
     */
    public static void divide(double[] array, double value)
    {
        if (value != 0d)
        {
            final int len = array.length;

            for (int i = 0; i < len; i++)
                array[i] = array[i] / value;
        }
    }

    /**
     * Divides all elements in an array by the specified value
     * 
     * @param array
     *        elements to modify
     * @param value
     *        value used as divisor
     */
    public static void divide(float[] array, float value)
    {
        if (value != 0d)
        {
            final int len = array.length;

            for (int i = 0; i < len; i++)
                array[i] = array[i] / value;
        }
    }

    /**
     * @deprecated use {@link ArrayMath#min(Object, boolean)} instead
     */
    @Deprecated
    public static double min(Object array, boolean signed)
    {
        return ArrayMath.min(array, signed);
    }

    /**
     * @deprecated use {@link ArrayMath#min(byte[], boolean)} instead
     */
    @Deprecated
    public static int min(byte[] array, boolean signed)
    {
        return ArrayMath.min(array, signed);
    }

    /**
     * @deprecated use {@link ArrayMath#min(short[], boolean)} instead
     */
    @Deprecated
    public static int min(short[] array, boolean signed)
    {
        return ArrayMath.min(array, signed);
    }

    /**
     * @deprecated use {@link ArrayMath#min(int[], boolean)} instead
     */
    @Deprecated
    public static long min(int[] array, boolean signed)
    {
        return ArrayMath.min(array, signed);
    }

    /**
     * @deprecated use {@link ArrayMath#min(float[])} instead
     */
    @Deprecated
    public static float min(float[] array)
    {
        return ArrayMath.min(array);
    }

    /**
     * @deprecated use {@link ArrayMath#min(double[])} instead
     */
    @Deprecated
    public static double min(double[] array)
    {
        return ArrayMath.min(array);

    }

    /**
     * @deprecated use {@link ArrayMath#max(Object, boolean)} instead
     */
    @Deprecated
    public static double max(Object array, boolean signed)
    {
        return ArrayMath.max(array, signed);
    }

    /**
     * @deprecated use {@link ArrayMath#max(byte[], boolean)} instead
     */
    @Deprecated
    public static int max(byte[] array, boolean signed)
    {
        return ArrayMath.max(array, signed);
    }

    /**
     * @deprecated use {@link ArrayMath#max(short[], boolean)} instead
     */
    @Deprecated
    public static int max(short[] array, boolean signed)
    {
        return ArrayMath.max(array, signed);
    }

    /**
     * @deprecated use {@link ArrayMath#max(int[], boolean)} instead
     */
    @Deprecated
    public static long max(int[] array, boolean signed)
    {
        return ArrayMath.max(array, signed);
    }

    /**
     * @deprecated use {@link ArrayMath#max(float[])} instead
     */
    @Deprecated
    public static float max(float[] array)
    {
        return ArrayMath.max(array);
    }

    /**
     * @deprecated use {@link ArrayMath#max(double[])} instead
     */
    @Deprecated
    public static double max(double[] array)
    {
        return ArrayMath.max(array);
    }

    /**
     * Round specified value to specified number of significant digit.<br>
     * If keepInteger is true then integer part of number is entirely conserved.<br>
     * If <i>numDigit</i> is <= 0 then the value stay unchanged.
     */
    public static double roundSignificant(double d, int numDigit, boolean keepInteger)
    {
        if ((numDigit <= 0) ||(d == 0d)) 
            return d;

        final double digit = Math.ceil(Math.log10(Math.abs(d)));
        if ((digit >= numDigit) && keepInteger)
            return Math.round(d);

        return round(d, numDigit - (int) digit);
    }

    /**
     * Round specified value to specified number of significant digit.
     */
    public static double roundSignificant(double d, int numDigit)
    {
        return roundSignificant(d, numDigit, false);
    }

    /**
     * Round specified value to specified number of decimal.
     */
    public static double round(double d, int numDecimal)
    {
        final double pow = Math.pow(10, numDecimal);
        return Math.round(d * pow) / pow;
    }

    /**
     * Return the previous multiple of "mul" for the specified value
     * <ul>
     * <li>prevMultiple(200, 64) = 192</li>
     * </ul>
     * 
     * @param value
     * @param mul
     */
    public static double prevMultiple(double value, double mul)
    {
        if (mul == 0)
            return 0d;

        return Math.floor(value / mul) * mul;
    }

    /**
     * Return the next multiple of "mul" for the specified value
     * <ul>
     * <li>nextMultiple(200, 64) = 256</li>
     * </ul>
     * 
     * @param value
     * @param mul
     */
    public static double nextMultiple(double value, double mul)
    {
        if (mul == 0)
            return 0d;

        return Math.ceil(value / mul) * mul;
    }

    /**
     * Return the next power of 2 for the specified value
     * <ul>
     * <li>nextPow2(17) = 32</li>
     * <li>nextPow2(16) = 32</li>
     * <li>nextPow2(-12) = -8</li>
     * <li>nextPow2(-8) = -4</li>
     * </ul>
     * 
     * @param value
     * @return next power of 2
     */
    public static long nextPow2(long value)
    {
        long result;

        if (value < 0)
        {
            result = -1;
            while (result > value)
                result <<= 1;
            result >>= 1;
        }
        else
        {
            result = 1;
            while (result <= value)
                result <<= 1;
        }

        return result;
    }

    /**
     * Return the next power of 2 mask for the specified value
     * <ul>
     * <li>nextPow2Mask(17) = 31</li>
     * <li>nextPow2Mask(16) = 31</li>
     * <li>nextPow2Mask(-12) = -8</li>
     * <li>nextPow2Mask(-8) = -4</li>
     * </ul>
     * 
     * @param value
     * @return next power of 2 mask
     */
    public static long nextPow2Mask(long value)
    {
        final long result = nextPow2(value);
        if (value > 0)
            return result - 1;

        return result;
    }

    /**
     * Return the previous power of 2 for the specified value
     * <ul>
     * <li>prevPow2(17) = 16</li>
     * <li>prevPow2(16) = 8</li>
     * <li>prevPow2(-12) = -16</li>
     * <li>prevPow2(-8) = -16</li>
     * </ul>
     * 
     * @param value
     * @return previous power of 2
     */
    public static long prevPow2(long value)
    {
        long result;

        if (value < 0)
        {
            result = -1;
            while (result >= value)
                result <<= 1;
        }
        else
        {
            result = 1;
            while (result < value)
                result <<= 1;
            result >>= 1;
        }

        return result;
    }

    /**
     * Return the next power of 10 for the specified value
     * <ul>
     * <li>nextPow10(0.0067) = 0.01</li>
     * <li>nextPow10(-28.7) = -10</li>
     * </ul>
     * 
     * @param value
     */
    public static double nextPow10(double value)
    {
        if (value == 0)
            return 0;
        else if (value < 0)
            return -Math.pow(10, Math.floor(Math.log10(-value)));
        else
            return Math.pow(10, Math.ceil(Math.log10(value)));
    }

    /**
     * Return the previous power of 10 for the specified value
     * <ul>
     * <li>prevPow10(0.0067) = 0.001</li>
     * <li>prevPow10(-28.7) = -100</li>
     * </ul>
     * 
     * @param value
     */
    public static double prevPow10(double value)
    {
        if (value == 0)
            return 0;
        else if (value < 0)
            return -Math.pow(10, Math.ceil(Math.log10(-value)));
        else
            return Math.pow(10, Math.floor(Math.log10(value)));
    }

    /**
     * Format the specified degree angle to stay in [0..360[ range
     */
    public static double formatDegreeAngle(double angle)
    {
        final double res = angle % 360d;

        if (res < 0)
            return 360d + res;

        return res;
    }

    /**
     * Format the specified degree angle to stay in [-180..180] range
     */
    public static double formatDegreeAngle2(double angle)
    {
        final double res = angle % 360d;

        if (res < -180d)
            return 360d + res;
        if (res > 180d)
            return res - 360d;

        return res;
    }

    /**
     * Format the specified degree angle to stay in [0..2PI[ range
     */
    public static double formatRadianAngle(double angle)
    {
        final double res = angle % (2 * Math.PI);

        if (res < 0)
            return (2 * Math.PI) + res;

        return res;
    }

    /**
     * Format the specified degree angle to stay in [-PI..PI] range
     */
    public static double formatRadianAngle2(double angle)
    {
        final double res = angle % (2 * Math.PI);

        if (res < -Math.PI)
            return (2 * Math.PI) + res;
        if (res > Math.PI)
            return res - (2 * Math.PI);

        return res;
    }

    /**
     * Return the value in <code>source</code> which is the closest to <code>value</code> Returns
     * <code>0</code> if source is null or empty.
     */
    public static double closest(double value, double[] source)
    {
        if ((source == null) || (source.length == 0))
            return 0d;

        double result = source[0];
        double minDelta = Math.abs(value - result);

        for (double d : source)
        {
            final double delta = Math.abs(value - d);

            if (delta < minDelta)
            {
                result = d;
                minDelta = delta;
            }
        }

        return result;
    }

    /**
     * Calculate the cubic root of the specified value.
     */
    public static double cubicRoot(double value)
    {
        return Math.pow(value, 1d / 3d);
    }
}