/* File: DasMath.java
 * Copyright (C) 2002-2003 The University of Iowa
 * Created by: Jeremy Faden <jbf@space.physics.uiowa.edu>
 *             Jessica Swanner <jessica@space.physics.uiowa.edu>
 *             Edward E. West <eew@space.physics.uiowa.edu>
 *
 * This file is part of the das2 library.
 *
 * das2 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.das2.util;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author  jbf
 */

public class DasMath {
    
    /** Creates a new instance of DasMath */
    public DasMath() {
    }
    
    private static final double log10=Math.log(10);
    
    public static double log10(double x) {
        return Math.log(x)/log10;
    }
    
    public static double exp10(double x) {
        double result= Math.pow(10,x);
        return result;
    }
    
    public static double exp10(int x) {
        double result= Math.pow(10,x);       
        return result;
    }
    
    /**
     * round the decimal to the number of fractional decimal places.  For example,
     * <code>DasMath.roundNFractionalDigits(1.2345678,3)==1.23</code>
     * Note the name implies that there will be three decimals in the fractional
     * part, but this implementation will return only two.
     * 
     * @param x a double
     * @param n number of decimal places
     * @return the number rounded to the number of decimal places.
     * @see #roundNSignificantDigits(double, int) 
     * @see https://github.com/autoplot/dev/blob/master/demos/2023/20230910/roundNDigits.jy
     */
    public static double roundNFractionalDigits(double x,int n) {                
        double tenToN= Math.pow(10,n-1);
        return Math.round( x * tenToN ) / tenToN;        
    }
    
    /**
     * Round the double to n significant digits by removing the exponent to make the number order one, 
     * then rounding the mantissa, then replacing the exponent.
     * @param x a double
     * @param n number of decimal places
     * @return the number rounded to the number of decimal places.
     * @see #roundNFractionalDigits(double, int) 
     * @see https://github.com/autoplot/dev/blob/master/demos/2023/20230910/roundNDigits.jy
     */
    public static double roundNSignificantDigits(double x,int n) {
        double sign= x<0 ? -1 : 1;
        double exp= Math.pow(10,Math.floor(Math.log10(sign*x)));
        double mant= x/exp;
        double tenToN= Math.pow(10,n-1);
        mant= Math.round( mant * tenToN ) / tenToN;
        return mant*exp;
    }
    
    public static double tanh( double x ) {
        double sinh = ( Math.exp(x) - Math.exp(-x) )/2;
        double cosh = ( Math.exp(x) + Math.exp(-x) )/2;
        double tanh = sinh / cosh;
        return tanh;
    }
    
    /** Interpolate just one point */
    public static double interpolate( double[] datay, double findex ) {
        int index= (int)findex;
        double alpha= findex - index;
        double result;
        if ( findex<0. ) return datay[0];
        if ( findex>datay.length-1. ) return datay[datay.length-1];
        if ( alpha == 0. ) { // optimization and handle findex=(data.length-1);
            result= datay[index];
        } else {
            result= datay[index] * ( 1.0 - alpha ) + datay[index+1] * alpha ;
        }
        return result;
    }
    
    /** interpolate an array of points. */
    public static double[] interpolate( double[] datay, double[] findex ) {
        double[] result= new double[ findex.length ];
        for ( int i=0; i<findex.length; i++ ) {
            result[i]= interpolate(datay, findex[i]);
        }
        return result;
    }
    
    /** Returns the "floating point indeces" of x within array datax.
     * A floating point index (or "findex") indicates the indeces
     * of the points that bracket and the weights of each bracketing
     * point to use.  A findex of 4.5 indicates that x is half-way
     * between index 4 and 5, so equal weights should be used.
     * floor( findex ) is the "left" bracket, ceil(findex) is the
     * "right" bracket, and fp( findex ) is the weight for the
     * right bracket.  See interpolate for an example of its use.
     */
    public static double[] findex( double[] datax, double[] x ) {
        double[] result= new double[x.length];
        int index= 0;
        for ( int i=0; i<result.length; i++ ) {
            result[i]= findex( datax, x[i], index );
            index= (int) result[i];
        }
        return result;
    }
    
    public final static double findex( double[] datax, double x, int guess ) {
        int index= Math.max( Math.min( guess, datax.length-1 ), 0 );
        while ( index<datax.length-1 && datax[index+1]<x ) index++;
        while ( index>0 && datax[index]>x ) index--;
        if ( index==datax.length-1 ) index--;
        return index + ( x - datax[index] ) / ( datax[index+1] - datax[index] );        
    }
    
    public static void main(String[] args) {
        double x= 1e-18;
        Logger.getLogger("das2.anon").log(Level.FINEST, "x:{0}", x);
        Logger.getLogger("das2.anon").log(Level.FINEST, "roundNDigits:{0}", roundNSignificantDigits(x,3));
        
        double[] x1= new double[] { 1,2,3,4,5 };
        double[] y1= new double[] { 4,6,7,3,1 };
        double[] interpx= new double [] { 1.0, 1.5, 4.5, 5.0, 1.5 };
        double[] interpy= interpolate( y1, findex( x1, interpx ) );
        for ( int i=0; i<interpx.length; i++ ) {
            System.out.println( ""+interpx[i]+ " " + interpy[i] );
        }
    }
    
    /**
     * just like modulo (%) function, but negative numbers return positive phase. 
     * @param x the numerator
     * @param t the devisor
     * @return the modulo, which will always be positive
     */
    public static double modp(double x, double t) {
        double result= x % t;
        return result >= 0 ? result : t + result;
    }

    /**
     * just like modulo (%) function, but negative numbers return positive phase.
     * @param x the numerator
     * @param t the devisor
     * @return the modulo, which will always be positive
     */
    public static int modp( int x, int t) {
        int result= x % t;
        return result >= 0 ? result : t + result;
    }
    
    /**
     * returns the bigger number of two numbers
     * @param x1 a number 
     * @param x2 a number
     * @return the greater of the two numbers
     */
    public static double biggerOf(double x1, double x2) {
        return ( x1>x2 ) ? x1 : x2;
    }
    
    private static double gcd( double a, double d, double error ) {
        
        if ( error>0 ) {
            a= Math.round( a/error );
            d= Math.round( d/error );
        }
        
        if ( a<d ) {
            double t= a;
            a= d;
            d= t;
        }
        
        if ( d==0 ) {
            if ( error>0 ) {
                return a * error;
            } else {
                return a;
            }
        }
        
        double r= a % d;                
        
        int iterations=0;
        
        while ( r > 0 && iterations<15 ) {
            d= r;
            r= a % d;
            iterations++;
        }
        
        if ( error>0 ) {
            return d * error;
        } else {
            return d;
        }
    }
    
    
   /**
    * Returns the greatest common divisor of a group of numbers.  This is useful for
    * a number of visualization techniques, for instance when you need to integerize
    * your data, the binsize should be the gcd.  An error parameter is provided to
    * avoid numerical noise, and in case there is a granularity that needn't be 
    * surpassed.
    *
    * org.das2.datum.DatumUtil has a private copy of this code.
    * @param A the array of numbers
    * @param error the limit of the error to deal with numerical noise
    * @return the greatest common divisor (GCD)
    */
    public static double gcd( double[] A, double error ) {
        double guess= A[0];
        
        double result= guess;
        
        for ( int i=1; i<A.length; i++ ) {
            result= gcd( result, A[i], error );
        }
        
        return result;
    }
    
    /**
     * return the mean of the list. The first element's magnitude is
     * removed from each accumulated value so that items with large offset 
     * values (for example times) can be averaged.  There is no checking
     * for NaNs or fill values.
     * @param A
     * @return the mean of the list.
     */
    public static double mean( double[] A ) {        
        double avgGuess= A[0];
        double sum=0.;
        
        for ( int i=0; i<A.length; i++ ) {
            sum+= A[i] - avgGuess;
        }
        
        return sum / A.length + avgGuess;        
    }
    
    /**
     * return the median of the list length N, which is the value at index N/2 of 
     * the sorted list.  This does not return the average for even-length lists.
     * and there is no checking for NaNs or fill values.
     * @param A the list of numbers
     * @return the median of the list
     */
    public static double median( double[] A ) {
        double[] sorted= sort( A );
        return sorted[A.length/2];
    }
    
    /**
     * return the maximum of the list
     * @param A the list of numbers
     * @return the maximum of the list
     */
    public static double max( double[] A ) {
        double max= A[0];
        for ( int i=0; i<A.length; i++ ) {
            max= ( max > A[i] ? max : A[i] );
        }
        return max;
    }
    
    /**
     * return the minimum of the list
     * @param A the list
     * @return the minimum of the list
     */
    public static double min( double[] A ) {
        double min= A[0];
        for ( int i=0; i<A.length; i++ ) {
            min= ( min < A[i] ? min : A[i] );
        }
        return min;
    }

    /**
     * return a sorted version of the array.  The original array is not
     * modified.
     * @param A
     * @return return a sorted version of the array.
     */
    public static double[] sort( double[] A ) {
        double[] copy= new double[A.length];
        System.arraycopy(A, 0, copy, 0, A.length);
        java.util.Arrays.sort( copy );
        return copy;
    }
        
}
