/*
 * @(#)MathUtilities.java    2.6.0   13 June 2007
 *
 * Copyright 2007
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.util;

/**
 * <p>The class <code>MathUtilities</code> collects several useful
 * static mathematical functions.  The class may be viewed as an
 * extension of the tools found in <code>java.lang.Math</code>.</p>
 *
 * <p>In 2.6.0, added the <i>hyperbolic</i> functions.</p>
 * 
 * <p>In 2.6.0, added <code>getTileIndexLimits</code> which is a
 * tool for computing tilings.</p>
 * 
 * @author Richard Rasala
 * @version 2.6.0
 * @since   1.2
 */
public class MathUtilities {

    // Random numbers in a specified range

    /**
     * Return a random int r in the range
     * between 0 and a inclusive.
     * 
     * @param a an endpoint of a range
     */
    public static int randomInt(int a) {
        return randomInt(0, a);
    }
    
    
    /**
     * Return a random int r in the range
     * between a and b inclusive.
     * 
     * @param a endpoint 1 of a range
     * @param b endpoint 2 of a range
     */
    public static int randomInt(int a, int b) {
        // To prevent overflow in the calculation, switch to long
        // Also, sort the parameters
        long u;
        long v;
        
        if (a <= b) {
            u = a;
            v = b;
        }
        else {
            u = b;
            v = a;
        }
        
        long r = u + (long)((v - u + 1) * Math.random());
        
        // To prevent round off problems, make sure that r <= v
        if (r > v)
            r = v;
        
        // Now return the int result
        return (int) r;
    }
    
    
    /**
     * Return a random double r in the range
     * between 0 and x.
     * 
     * @param x an endpoint of a range
     */
    public static double randomDouble(double x) {
        return Math.random() * x;
    }
    
    
    /**
     * Return a random double r in the range
     * between x and y.
     * 
     * @param x endpoint 1 of a range
     * @param y endpoint 2 of a range
     */
    public static double randomDouble(double x, double y) {
        // Sort the parameters
        double a;
        double b;
        
        if (x <= y) {
            a = x;
            b = y;
        }
        else {
            a = y;
            b = x;
        }
        
        // Obtain a random value between 0 and 1
        double t = Math.random();
        
        // Perform affine interpolation to
        // obtain a random value between a and b
        double r = a + t * (b - a);
        
        // To prevent round off problems, make sure that a <= r <= b
        if (r < a)
            r = a;
        else if (r > b)
            r = b;
        
        // Now return the result
        return r;
    }
    
    
    // Trigonometric functions in degrees
    
    /** Return the sine of the given angle specified in degrees. */
    public static double sindeg(double degrees) {
        return Math.sin(Math.toRadians(degrees));
    }
    
    
    /** Return the cosine of the given angle specified in degrees. */
    public static double cosdeg(double degrees) {
        return Math.cos(Math.toRadians(degrees));
    }
    
    
    /** Return the tangent of the given angle specified in degrees. */
    public static double tandeg(double degrees) {
        return Math.tan(Math.toRadians(degrees));
    }
    
    
    /**
     * Return the arc sine in degrees of the specified input value
     * in the range -1 to +1.
     */
    public static double asindeg(double value) {
        return Math.toDegrees(Math.asin(value));
    }
    
    
    /**
     * Return the arc cosine in degrees of the specified input value
     * in the range -1 to +1.
     */
    public static double acosdeg(double value) {
        return Math.toDegrees(Math.acos(value));
    }
    
    
    /** Return the arc tangent in degrees of the specified input value. */
    public static double atandeg(double value) {
        return Math.toDegrees(Math.atan(value));
    }
    
    
    /**
     * Return the polar angle in degrees of the point (x, y) where the
     * y-coordinate is given first and the x-coordinate second.
     *
     * @param y the y-coordinate of the point
     * @param x the x-coordinate of the point
     */
    public static double atan2deg(double y, double x) {
        return Math.toDegrees(Math.atan2(y, x));
    }
    
    
    // Hyperbolic functions
    
    /**
     * <p>Hyperbolic sine.</p>
     * 
     * <p>Returns <code>(exp(x) - 1/exp(x))/2</code>
     * but computes the exponential only once.</p>
     * 
     * @param x the argument
     */
    public static double sinh(double x) {
        boolean positive = x >= 0;
        double z;
        double w;
        
        if (positive) {
            z = Math.exp(x);
            w = 1/z;
            
            return (z - w)/2;
        }
        else {
            z = Math.exp(-x);
            w = 1/z;
            
            return (w - z)/2;
        }
    }
    
    
    /**
     * <p>Hyperbolic cosine.</p>
     * 
     * <p>Returns <code>(exp(x) + 1/exp(x))/2</code>
     * but computes the exponential only once.</p>
     * 
     * @param x the argument
     */
    public static double cosh(double x) {
        double z = (x >= 0) ? Math.exp(x) : Math.exp(-x);
        
        return (z + 1/z)/2;
    }
    
    
    /**
     * <p>Hyperbolic tangent.</p>
     * 
     * <p>Effectively returns <code>sinh(x)/cosh(x)</code>
     * but computes only one exponential.</p>
     * 
     * @param x the argument
     */
    public static double tanh(double x) {
        boolean positive = x >= 0;
        double z;
        double w;
        
        if (positive) {
            z = Math.exp(x);
            w = 1/z;
            
            return (z - w) / (z + w);
        }
        else {
            z = Math.exp(-x);
            w = 1/z;
            
            return (w - z) / (z + w);
        }
    }
    
    
    /**
     * <p>Inverse hyperbolic sine.</p>
     * 
     * <p>Returns <code>log(s + sqrt(s*s + 1))</code>.</p>
     * 
     * @param s the sinh value to invert
     */
    public static double asinh(double s) {
        return Math.log(s + Math.sqrt(s*s + 1));
    }
    
    
    /**
     * <p>Inverse hyperbolic cosine.</p>
     * 
     * <p>Returns <code>log(c + sqrt(c*c - 1))</code>.
     * This chooses the <i>positive branch</i> of <code>acosh</code>,
     * that is, the return value is always &gt;=0.</p>
     * 
     * <p>If <code>c &lt; 1</code> returns <code>Nan</code>.</p>
     * 
     * @param c the cosh value to invert
     */
    public static double acosh(double c) {
        if (c < 1)
            return Double.NaN;
        
        return Math.log(c + Math.sqrt(c*c - 1));
    }
    
    
    /**
     * <p>Inverse hyperbolic tangent.</p>
     * 
     * <p>Returns <code>log(sqrt((1 + t)/(1 - t)))</code>.</p>
     * 
     * <p>If <code>abs(t) &gt;= 1</code> returns <code>Nan</code>.</p>
     * 
     * @param t the tanh value to invert
     */
    public static double atanh(double t) {
        if (Math.abs(t) >= 1)
            return Double.NaN;
        
        return Math.log(Math.sqrt((1 + t)/(1 - t)));
    }
    
    
    // Greatest Common Divisor
    
    /**
     * <p>Returns the greatest common divisor of the int inputs a and b.</p>
     *
     * <p>The return value will be positive or zero and will be zero only if
     * both a and b are zero.</p>
     */
    public static int GCD(int a, int b) {
        if (b == 0)
            return Math.abs(a);
        
        return GCD(b, a % b);
    }
    
    
    /**
     * <p>Returns the greatest common divisor of the long inputs a and b.</p>
     *
     * <p>The return value will be positive or zero and will be zero only if
     * both a and b are zero.</p>
     */
    public static long GCD(long a, long b) {
        if (b == 0)
            return Math.abs(a);
        
        return GCD(b, a % b);
    }
    
    
    // Least Common Multiple
    
    /**
     * <p>Returns the least common multiple of the int inputs a and b.</p>
     *
     * <p>The return value will be positive or zero and will be zero only if
     * both a and b are zero.</p>
     */
    public static int LCM(int a, int b) {
        if ((a == 0) && (b == 0))
            return 0;
        
        a = Math.abs(a);
        
        b = Math.abs(b);
        
        return a * (b / GCD(a, b));
    }
    
    
    /**
     * <p>Returns the least common multiple of the long inputs a and b.</p>
     *
     * <p>The return value will be positive or zero and will be zero only if
     * both a and b are zero.</p>
     */
    public static long LCM(long a, long b) {
        if ((a == 0) && (b == 0))
            return 0;
        
        a = Math.abs(a);
        
        b = Math.abs(b);
        
        return a * (b / GCD(a, b));
    }
    
    
    // Modulus
    
    /**
     * <p>Returns the modulus of int number relative to int base.</p>
     *
     * <p>Returns n such that n is congruent to number modulo base
     * and 0 &lt;= n &lt; Math.abs(base).</p>
     *
     * <p>Throws <code>ArithmeticException</code> if base is zero.</p>
     */
    public static int modulus(int number, int base) {
        if (base == 0)
            throw new ArithmeticException
                ("Error in modulus: base is zero");
        
        base = Math.abs(base);
        
        number = number % base;
        
        if (number < 0)
            number += base;
        
        return number;
    }
    
    
    /**
     * <p>Returns the modulus of long number relative to long base.</p>
     *
     * <p>Returns n such that n is congruent to number modulo base
     * and 0 &lt;= n &lt; Math.abs(base).</p>
     *
     * <p>Throws <code>ArithmeticException</code> if base is zero.</p>
     */
    public static long modulus(long number, long base) {
        if (base == 0)
            throw new ArithmeticException
                ("Error in modulus: base is zero");
        
        base = Math.abs(base);
        
        number = number % base;
        
        if (number < 0)
            number += base;
        
        return number;
    }
    
    
    /**
     * <p>Returns the n-th power of x, <code>x<sup>n</sup></code>, using
     * the given integer exponent n.</p>
     *
     * <p>Computes using ordinary arithmetic operations.</p>
     *
     * <p>Returns 1.0 regardless of x if n equals 0.</p>
     *
     * <p>Returns <code>Double.NaN</code> if n is negative and x is zero.</p>
     * 
     * <p>Note: Departs from the convention of the Java <code>Math</code>
     * class by calling the method <code>power</code> rather than simply
     * <code>pow</code>.</p>
     *
     * <p>Note added in 2.5.0: This method uses only multiplication and
     * division and does not use exp or log. Recents experiments showed
     * that this method may be <i>less accurate</i> than calling
     * <code>Math.pow</code>. Hence the use of this method is no longer
     * recommended.  It is retained in JPT to avoid breaking old code.</p>
     * 
     * @param x the base
     * @param n the exponent
     */
    public static double power(double x, int n) {
        if (n == 0)
            return 1.0;
            
        if ((n < 0) && (x == 0))
            return Double.NaN;
        
        if (n < 0) {
            n = -n;
            x = 1.0 / x;
        }
        
        // Loop invariant: The final result is the product of the current
        // result and the yet to be completed part of x-to-the-power-n.
        
        // Loop termination: n decreases in each loop cycle; when n == 0,
        // result holds the final value.
        
        double result = 1.0;
        
        while (n > 0) {
            if ((n % 2) != 0) {
                // add one factor of x to result and decrease
                // the exponent n by one
                result *= x;
                n--;
            }
            else {
                // square x and reduce the exponent n by half
                // result is unchanged in this step
                x = x * x;
                n /= 2;
            }
        }
        
        return result;
    }
    
    
    /**
     * <p>Returns the n-th root of x.</p>
     * 
     * <p>If n is 0, returns NaN.</p>
     * 
     * <p>If x is 0 and n &gt; 0, returns 0; otherwise returns NaN.</p>
     * 
     * <p>If x &gt; 0, returns Math.pow(x,1.0/n).</p>
     * 
     * <p>If x &lt; 0 and n is an odd integer, then, with z = -x,
     * returns -Math.pow(z,1.0/n); otherwise, returns NaN.</p>
     * 
     * @param x the base
     * @param n the inverse of the exponent
     */
    public static double root(double x, double n) {
        double NaN = Double.NaN;
        
        if (n == 0)
            return NaN;
        
        if (x == 0) {
            if (n > 0)
                return 0;
            else
                return NaN;
        }
        
        double y = 1.0 / n;
        
        if (x > 0)
            return Math.pow(x, y);
        
        int nn = (int) n;
        
        if (n == nn) {
            if ((nn % 2) != 0) {
                return - Math.pow(-x, y);
            }
        }
        
        return NaN;
    }
    
    
    /**
     * <p>Returns an integer array with indices <code>i</code>
     * and <code>j</code> that determine how a small interval
     * from <code>x0</code> to <code>x0+w0</code> may be tiled
     * to cover a large interval from <code>x</code> to
     * <code>x+w</code>.</p>
     * 
     * <p>Specifically:</p>
     * 
     * <ul>
     *   <li><code>i</code> is the largest integer such that
     *       <code>(x0+i*w0)&lt;=x</code>.</li>
     *   <li><code>j</code> is the smallest integer such that
     *       <code>(x+w)&lt;=(x0+j*w0)</code>.</li>
     * </ul>
     * 
     * <p>With these specifications, a loop on <code>k</code>,
     * starting at <code>k=i</code> and running as long
     * as <code>k<j</code>, will produce intervals
     * <code>(x0+k*w0,x0+k*w0+w0)</code> that cover the
     * interval <code>(x,x+w)</code>.</p>
     * 
     * <p>This utility is used in <code>AbstractPaintable</code>
     * to manage tiling but it is included here since it might
     * have wider value.</p>
     * 
     * <p>This function returns <code>null</code> if:</p>
     * 
     * <ul>
     *   <li><code>w&lt;=0</code></li>
     *   <li><code>w0&lt;=0</code></li>
     *   <li>The computations do not produce values that fall
     *       into the integer range.</li>
     * </ul>
     * 
     * <p>Although the discussion and parameter names focus
     * on the horizontal direction, the method can clearly
     * be used to find tiling indices for vertical tiling
     * as well.</p>
     * 
     * @param x  the left endpoint of the interval to cover
     * @param w  the width &lt; 0 of the interval to cover
     * @param x0 the left endpoint of the covering interval
     * @param w0 the width &lt; 0 of the covering interval
     */
    public static int[] getTileIndexLimits
        (double x, double w, double x0, double w0)
    {
        if ((w <= 0) || (w0 <= 0))
            return null;
        
        double dx = x - x0;
        
        double a = Math.floor(dx / w0);
        
        int i = (int) a;
        
        if (i != a)
            return null;
        
        double b = Math.ceil((dx + w) / w0);
        
        int j = (int) b;
        
        if (j != b)
            return null;
        
        return new int[] { i, j };
    }
    
}
