/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.math;

import lombok.experimental.UtilityClass;

/**
 * Math methods, obviously.
 */
@UtilityClass
public final class MathUtils {

    /**
     * Returns true if {@code min <= value <= max}
     *
     * @param value value
     * @param min min
     * @param max max
     * @return boolean
     */
    public static boolean between(int value, int min, int max) {
        return value >= min && value <= max;
    }

    /**
     * Returns true if {@code min <= value <= max}
     *
     * @param value value
     * @param min min
     * @param max max
     * @return boolean
     */
    public static boolean between(byte value, byte min, byte max) {
        return value >= min && value <= max;
    }

    /**
     * Returns true if {@code min <= value <= max}
     *
     * @param value value
     * @param min min
     * @param max max
     * @return boolean
     */
    public static boolean between(long value, long min, long max) {
        return value >= min && value <= max;
    }

    /**
     * Returns true if {@code min <= value <= max}
     *
     * @param value value
     * @param min min
     * @param max max
     * @return boolean
     */
    public static boolean between(double value, double min, double max) {
        return value >= min && value <= max;
    }

    /**
     * Returns value if {@code min <= value <= max}.
     * Returns min for smaller values, returns max for greater values.
     *
     * @param value value
     * @param min min
     * @param max max
     * @return number
     */
    public static int clip(int value, int min, int max) {
        assert min <= max;
        return value < min ? min : value > max ? max : value;
    }

    /**
     * Returns value if {@code min <= value <= max}.
     * Returns min for smaller values, returns max for greater values.
     *
     * @param value value
     * @param min min
     * @param max max
     * @return number
     */
    public static long clip(long value, long min, long max) {
        assert min <= max;
        return value < min ? min : value > max ? max : value;
    }

    /**
     * Returns value if {@code min <= value <= max}.
     * Returns min for smaller values, returns max for greater values.
     *
     * @param value value
     * @param min min
     * @param max max
     * @return number
     */
    public static double clip(double value, double min, double max) {
        assert min <= max;
        return value < min ? min : value > max ? max : value;
    }

    /**
     * Returns value if {@code min <= value <= max}.
     * Returns min for smaller values, returns max for greater values.
     *
     * @param value value
     * @param min min
     * @param max max
     * @return number
     */
    public static float clip(float value, float min, float max) {
        assert min <= max;
        return value < min ? min : value > max ? max : value;
    }

    /**
     * Returns rounded value if {@code min <= value <= max}.
     * Returns min for smaller values, returns max for greater values.
     *
     * @param value value
     * @param min min
     * @param max max
     * @return number
     */
    public static int round(double value, int min, int max) {
        assert min <= max;
        return (int)Math.round(value < min ? min : value > max ? max : value);
    }

    /**
     * Returns rounded value if {@code min <= value <= max}.
     * Returns min for smaller values, returns max for greater values.
     *
     * @param value value
     * @param min min
     * @param max max
     * @return number
     */
    public static long round(double value, long min, long max) {
        assert min <= max;
        return Math.round(value < min ? min : value > max ? max : value);
    }

    /**
     * Returns true signum: +1 for positives, -1 for negatives, 0 for zero.
     *
     * @param diff diff
     * @return int
     */
    public static int signum(int diff) {
        return Integer.signum(diff);
    }


    /**
     * Returns pseduo-signum.
     * Returns some positive int for diff &gt; 0
     * Returns some negative int for diff &lt; 0
     *
     * @param diff diff
     * @return int
     */
    public static int signum(long diff) {
        /*
         * Alternative to signum for use in compare. Not a true signum, since it
         * returns ints other than +/-1. Where there is any possibility of overflow,
         * you should compare two longs with &lt; rather than subtraction.
         * <p>
         * In Pentium assembler you could implement this algorthm with following code:
         * <pre>
         *  # diff = edx:eax
         *  # result = eax
         *  mov ebx,eax
         *  shl eax,1
         *  or  eax,ebx
         *  slr eax,1
         *  or  eax,edx
         * </pre>
         * which would take 5 cycles, 2 more that lohi.  However, JET did even better,
         * with code essentially this using a clever trick to implement piotr.
         * <pre>
         *   lea    ecx,0(eax,eax)  ; shifts lo left by doubling, keeps copy of
         *   lo
         *   or     eax,ecx
         *   shr    eax,1
         *   or     eax,edx
         * </pre>
         * This is 4 cycles, still one more than lohi. Why was Piotr so much
         * faster on JET? Peter has no pipeline-confounding jumps. Further, the lo
         * then high operands actually come from the ram-based stack. Piotr nicely
         * separates the accesses giving plenty of for pre-emptive fetch of hi.
         * lohi insists on having them both upfront, so it has to wait for memory
         * access. Piotr does not have to wait. Modern CPUS hurry up and wait for
         * RAM most of the time.
         *
         * @param diff number to be collapsed to an int preserving sign and
         * zeroness. usually represents the difference of two long.
         *
         * @return sign of {@code diff}, some {@code int < 0}, 0 or some {@code int > 0}.
         * @author Peter Kobzda
         */
        return (int) (diff >>> 32) | ((int) diff | (int) diff << 1) >>> 1;
    }

    /**
     * Calculates floor( log2(x) )
     *
     * @param x x
     * @return int
     */
    public static int log2floor(int x) {
        return 31 - Integer.numberOfLeadingZeros(x);
    }

    /**
     * Calculates floor( log2(x) )
     *
     * @param x x
     * @return int
     */
    public static int log2floor(long x) {
        return 63 - Long.numberOfLeadingZeros(x);
    }

    /**
     * Calculates ceil( log2(x) )
     *
     * @param x x
     * @return int
     */
    public static int log2ceil(int x) {
        return 32 - Integer.numberOfLeadingZeros(x - 1);
    }

    /**
     * Calculates ceil( log2(x) )
     *
     * @param x x
     * @return int
     */
    public static int log2ceil(long x) {
        return 64 - Long.numberOfLeadingZeros(x - 1);
    }

    public static int pow(int a, int b) {
        return (int)Math.round(Math.pow(a,b));
    }

    public static long pow(long a, long b) {
        return Math.round(Math.pow(a,b));
    }

    /**
     * Return the least power of two greater than or equal to the specified value.
     *
     * <p>Note that this function will return 1 when the argument is 0.
     *
     * @param x an integer smaller than or equal to 2<sup>30</sup>.
     * @return the least power of two greater than or equal to the specified
     * value.
     */
    @SuppressWarnings({
        "PMD.AvoidReassigningParameters"
    })
    public static int pow2next(int x) {
        if (x == 0) {
            return 1;
        }
        x--;
        x |= x >> 1;
        x |= x >> 2;
        x |= x >> 4;
        x |= x >> 8;
        return (x | x >> 16) + 1;
    }

    /**
     * Return the least power of two greater than or equal to the specified value.
     *
     * <p>Note that this function will return 1 when the argument is 0.
     *
     * @param x a long integer smaller than or equal to 2<sup>62</sup>.
     * @return the least power of two greater than or equal to the specified
     * value.
     */
    @SuppressWarnings({
        "PMD.AvoidReassigningParameters"
    })
    public static long pow2next(long x) {
        if (x == 0) {
            return 1;
        }
        x--;
        x |= x >> 1;
        x |= x >> 2;
        x |= x >> 4;
        x |= x >> 8;
        x |= x >> 16;
        return (x | x >> 32) + 1;
    }

    /**
     * Calculates a * a
     *
     * @param a a
     * @return double
     */
    public static double square(double a) {
        return a * a;
    }

    /**
     * Calculates greates common divisor
     *
     * @param a a
     * @param b b
     * @return int
     */
    public static int gcd(int a, int b) {
        return b==0 ? a : gcd(b, a % b); 
    }

    /**
     * Calculates greates common divisor
     *
     * @param values values
     * @return int
     */
    public static int gcd(int... values) {
        int ret = gcd(values[0], values[1]);
        for(int i=2; i<values.length; i++) {
            ret = gcd(ret, values[i]);
        }
        return ret;
    }

    /**
     * Calculates greates common divisor
     *
     * @param a a
     * @param b b
     * @return long
     */
    public static long gcd(long a, long b) {
        return b==0 ? a : gcd(b, a % b); 
    }

    /**
     * Calculates greates common divisor
     *
     * @param values values
     * @return long
     */
    public static long gcd(long... values) {
        long ret = gcd(values[0], values[1]);
        for(int i=2; i<values.length; i++) {
            ret = gcd(ret, values[i]);
        }
        return ret;
    }

    /**
     * Calculates least common multiple
     *
     * @param a a
     * @param b a
     * @return int
     */
    public static int lcm(int a, int b) {
        return (a / gcd(a,b)) * b;
    }

    /**
     * Calculates least common multiple
     *
     * @param values values
     * @return int
     */
    public static int lcm(int... values) {
        int ret = lcm(values[0], values[1]);
        for(int i=2; i<values.length; i++) {
            ret = lcm(ret, values[i]);
        }
        return ret;
    }

    /**
     * Calculates least common multiple
     *
     * @param a a
     * @param b b
     * @return long
     */
    public static long lcm(long a, long b) {
        return (a/ gcd(a,b) ) * b;
    }

    /**
     * Calculates least common multiple
     *
     * @param values values
     * @return long
     */
    public static long lcm(long... values) {
        long ret = lcm(values[0], values[1]);
        for(int i=2; i<values.length; i++) {
            ret = lcm(ret, values[i]);
        }
        return ret;
    }

    /**
     * Saturated add. Returns MIN_VALUE or MAX_VALUE instead of overflow
     *
     * @param x x
     * @param y y
     * @return x+y
     */
    public static int adds(int x, int y) {
        int r = x + y;
        if (((r ^ x) & (r ^ y)) < 0) {
            return Integer.MIN_VALUE - (r >>> 31);
        }
        return r;
    }

    /**
     * Saturated add. Returns min or max instead of overflow.
     *
     * @param x x
     * @param y y
     * @param min min
     * @param max max
     * @return int
     */
    public static int adds(int x, int y, int min, int max) {
        return clip(adds(x,y), min, max);
    }

    /**
     * Saturated add. Returns MIN_VALUE or MAX_VALUE instead of overflow
     *
     * @param x x
     * @param y y
     * @return x+y
     */
    public static long adds(long x, long y) {
        long r = x + y;
        if (((r ^ x) & (r ^ y)) < 0) {
            return Long.MIN_VALUE - (r >>> 63);
        }
        return r;
    }

    /**
     * Saturated add. Returns min or max instead of overflow.
     *
     * @param x x
     * @param y y
     * @param min min
     * @param max max
     * @return long
     */
    public static long adds(long x, long y, long min, long max) {
        return clip(adds(x,y), min, max);
    }

}

