/*
GeoGebra - Dynamic Mathematics for Schools
Copyright Markus Hohenwarter and GeoGebra Inc.,  http://www.geogebra.org

This file is part of GeoGebra.

This program 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.

*/

package geogebra.kernel;

//import geogebra.kernel.AlgoElement;
import geogebra.euclidian.EuclidianView;
import geogebra.kernel.arithmetic.Function;
import geogebra.kernel.arithmetic.NumberValue;
import geogebra.kernel.roots.RealRootAdapter;
import geogebra.kernel.roots.RealRootUtil;
import geogebra.main.Application;

import java.util.ArrayList;

import org.apache.commons.math.analysis.solvers.UnivariateRealSolver;
import org.apache.commons.math.analysis.solvers.UnivariateRealSolverFactory;

//import org.apache.commons.math.analysis.solvers.UnivariateRealSolverFactory;


/**
 * Command:	Roots[ <function>, <left-x>, <right-x>]				      (TYPE 0)
 * and
 * Command: Intersect[ <function>, <function>, <left-x>, <right-x>]   (TYPE 1)
 * 			(just uses difference-function instead of one function)
 * 
 * Can be used elsewhere:
 *    public static final double[] findRoots(GeoFunction f,double l,double r,int samples)
 *    public static final double[] calcSingleRoot(GeoFunction f, double l, double r);
 * 
 * Extends AlgoGeoPointsFunction (abstract), with the label methods, 
 * which again extens AlgoElement.
 * 
 * @author 	Hans-Petter Ulven
 * @version 2011-03-08
 */

public class AlgoRoots extends AlgoGeoPointsFunction {
	
	// Constants
	private static final long 	serialVersionUID 		= 	1L;
	private static final int	TYPE_ROOTS				=	0;
	private static final int 	TYPE_INTERSECTIONS		=	1;
	private static final int	PIXELS_BETWEEN_SAMPLES	=	  5;		// Open for empirical adjustments
    private static final int   	MAX_SAMPLES				=	400;		// -"- (covers a screen up to 2000 pxs if 5-pix-convention)
    private static final int	MIN_SAMPLES				=	 50;		// -"- (covers up to 50 in a 250 pxs interval if 5-pix-convention)
    
    //Input-Output
//   	private GeoFunctionable 	function; 			// input
	private GeoFunction			f,f1,f2,diff;
	private NumberValue     	left;	  			// input
	private GeoElement			geoleft;
	private NumberValue			right;				// input
	private GeoElement			georight;
	
	// Vars
	private int					type	=	TYPE_ROOTS;

    /** 
     * Computes "all" Roots of f in <l,r>
     * TYPE_ROOTS 
     */
    public AlgoRoots(Construction cons, String[] labels, GeoFunction function,NumberValue left,NumberValue right) {
    	super(cons,labels,!cons.isSuppressLabelsActive(),function);			//Ancestor gets first function for points!
    	this.f=function;
    	this.left=left;
    	this.geoleft=left.toGeoElement();
    	this.right=right;
    	this.georight=right.toGeoElement();
    	
    	type=TYPE_ROOTS;
    	    	
    	setInputOutput();
    	
    	compute();
    	
    	showOneRootInAlgebraView();    	// Show at least one root point in algebra view
    	
    }//Constructor TYPE_ROOTS
    
    public AlgoRoots(Construction cons, GeoFunction function,NumberValue left,NumberValue right) {
    	super(cons,function);			//Ancestor gets first function for points!
    	this.f=function;
    	this.left=left;
    	this.geoleft=left.toGeoElement();
    	this.right=right;
    	this.georight=right.toGeoElement();
    	
    	type=TYPE_ROOTS;
    	    	
    	setInputOutput();
    	
    	compute();
    }//Constructor TYPE_ROOTS
    
    /** 
     * Computes "all" Roots of f in <l,r>
     * TYPE_INTERSECTIONS
     */
    public AlgoRoots(Construction cons, String[] labels, GeoFunction function,GeoFunction function2, NumberValue left, NumberValue right) {
    	super(cons,labels,!cons.isSuppressLabelsActive(),function);			//Ancestor gets first function for points!
    	this.f1=function;
    	this.f2=function2;
    	this.left=left;
    	this.geoleft=left.toGeoElement();
    	this.right=right;
    	this.georight=right.toGeoElement();
    	
    	type=TYPE_INTERSECTIONS;
    	    	
    	setInputOutput();
    	
    	compute();
    	
    	showOneRootInAlgebraView();    	// Show at least one root point in algebra view
    	
    }//Constructor TYPE_INTERSECTIONS    
    
    public String getClassName() {
        return "AlgoRoots";
    }//getClassName()
    
    public GeoPoint[] getRootPoints() {
    	return getPoints();					//Points in ancestor
    }//getRootPoints()

    protected void setInputOutput(){
    	switch(type){
    		case TYPE_ROOTS:
    			input 	 = new GeoElement[3];
    			input[0] = f.toGeoElement();
    			input[1] = geoleft;
    			input[2] = georight;
    			break;
    		case TYPE_INTERSECTIONS:
    			input 	= new GeoElement[4];
    			input[0]=f1.toGeoElement();
    			input[1]=f2.toGeoElement();
    			input[2]=geoleft;
    			input[3]=georight;
    	}//switch
        
        output=getPoints();					//Points in ancestor

        noUndefinedPointsInAlgebraView(getPoints()); 
        
        setDependencies(); // done by AlgoElement
    }//setInputOutput()
    
    protected final void compute(){
    	boolean ok=false;
    	switch(type){
    		case TYPE_ROOTS:
    			ok= f.toGeoElement().isDefined() && geoleft.isDefined() && georight.isDefined();
    			break;
    		case TYPE_INTERSECTIONS:
    			ok=f1.toGeoElement().isDefined() && f2.toGeoElement().isDefined() && geoleft.isDefined() && georight.isDefined();
    			break;
    	}//switch
    	if(!ok){
    		setPoints(new double[1],0);					//debug("error in args");
    	}else{
    		if(type==TYPE_INTERSECTIONS){
    			diff=new GeoFunction(cons);
    			diff=GeoFunction.subtract(diff,f1,f2); //Make a difference geofunction for intersections
    			compute2(diff);
    		}else{
    			compute2(f);
    		}//if type
    	}//if ok input
    }//compute()

    private  final void compute2(GeoFunction f) {      

        double		l				=	left.getDouble();
        double		r				=	right.getDouble();
		double[] roots=new double[0];
		int      numberofroots=0;
		
		
		/*
    	if (    !f.toGeoElement().isDefined() || !geoleft.isDefined()    ||   
    		    !georight.isDefined() 				// || (right.getDouble()<=left.getDouble() )    
    	) {
    		setPoints(new double[1],0);		//0 flags no root=>undefined
    	}else {  	 
    	*/

    		if(l>r){double tmp=l;l=r;r=tmp;}		//Correct user input
    	    	
    		/// ---  Algorithm --- ///
    		int n = findNumberOfSamples(l,r);
    		int m = n;
    		try{	//To catch eventual wrong indexes in arrays...
    			//Adjust samples. Some research needed to find best factor in if(numberofroots<m*factor...
    			do{																			//debug("doing samples: "+m);
   					roots=findRoots(f,l,r,m);


    				if(roots==null){numberofroots=0;}else{numberofroots=roots.length;}		//debug("found xvalues: "+roots);
    				if(numberofroots<m/2) {
    					break;
    				}else{
    					m=m*2;
    				}//
    			}while( m<MAX_SAMPLES );
    			if(m>MAX_SAMPLES) Application.debug("We have probably lost some roots...");
    		}catch(Exception e){
    			Application.debug("Exception in compute() "+e.toString());
    		}//try-catch    
    	
    	//}//if
    	
    	if(numberofroots==0){
    		setPoints(new double[1],0);			//0 flags no root=>undefined
    	}else{
    		setPoints(roots,roots.length);
    	}//if
    }//compute()
    
    /** 
     * Main algorithm, public for eventual use by other commands
     * Finds a samplesize n depending on screen coordinates
     * Samples n intervals
     * Collects roots in intervals where y(l)*y(r)>0
     */
	public static final double[] findRoots(GeoFunction f,double l,double r,int samples){
		
		double[] y	=new double[samples+1];				//
		ArrayList<Double>	xlist=new ArrayList<Double>();
		double x,xval,yval;
		double	deltax	=(r-l)/samples;
		
		for(int i=0;i<=samples;i++) {
			x=l+i*deltax;
			y[i]=f.evaluate(x);
			//if left endpoint is root by pure luck...
			if(
					( Math.abs(y[i])<Kernel.MIN_PRECISION) &&
					( signChanged(f,x) )
			) {	//if left endpoint is root by pure luck...
				xlist.add(x);
			}//if
			if(i>0){
				if( 
					(	(y[i-1]<0.0d) && (y[i]>0.0d)	) ||			//or just y[i-1]*y[i]<0...
					(	(y[i-1]>0.0d) && (y[i]<0.0d)	)
				){
					xval=calcSingleRoot(f,x-deltax,x);
			        if (Math.abs(f.evaluate(xval)) < Kernel.MIN_PRECISION){  //=1E-5: Quite large, but less doesn't work in Apache lib...
			        	xlist.add(xval);
			        }//if check
				}//if possible root
			}//if both ends of interval
		}//for all endpoints
		if(xlist.size()>0) {
			double[] res=new double[xlist.size()];
			for(int i=0;i<xlist.size();i++) {
				res[i]=xlist.get(i);
			}//for all x in xlist
			removeDuplicates(res);			//new 08.03.11 to avoid (1,0.00000x) and (1,-0.00000x) ...
			return res;
		}else{
			return null;
		}//if valid
	}//findRoots(f,l,r)
    
    /// --- Private methods --- ///
    //  Make all private after testing...
    
	/** Brent's algo
	 * Copied from AlgoRootInterval.java.
	 */
	public final static double calcSingleRoot(GeoFunction f,double left,double right){
		UnivariateRealSolver rootFinder=UnivariateRealSolverFactory.newInstance().newBrentSolver();   //Apache lib

       if (!f.isDefined())	return Double.NaN;
	        
        double root = Double.NaN;
        Function fun = f.getFunction();
  
	    try {
	    	// Brent's method
	        root = rootFinder.solve(new RealRootAdapter(fun), left, right);          
	    } catch (Exception e) {        	
	        try {                               	        	
	       	// Let's try again by searching for a valid domain first
	          	double [] borders = RealRootUtil.getDefinedInterval(fun,left, right);            	
	           	root = rootFinder.solve(new RealRootAdapter(fun), borders[0], borders[1]);                
	        } catch (Exception ex) {   
	           	root = Double.NaN;
	        }//try-catch
	    }//try-catch
	        
        return root;
	}//calcSingleRoot(f,l,r)
	
    public final int findNumberOfSamples(double l,double r){
    	//Find visible area of graphic screen: xmin,xmax,ymin,ymax
    	//pixels_in_visible_interval=...
    	//n=pixels_in_visible_interval/PIXELS_BETWEEN_SAMPLES;
    	
    	EuclidianView ev	=	app.getEuclidianView();
    	double visiblemax	=	ev.getXmax();
    	double visiblemin	=	ev.getXmin();
    	double visiblepixs	=	ev.toScreenCoordXd(visiblemax)-ev.toScreenCoordXd(visiblemin);
    	//debug("Visible pixels: "+visiblepixs);
    	double pixsininterval=	visiblepixs*(r-l)/(visiblemax-visiblemin);
    	//debug("Pixels in interval: "+pixsininterval);
    	int n=(int)Math.round(Math.max(Math.min(pixsininterval/PIXELS_BETWEEN_SAMPLES,MAX_SAMPLES),MIN_SAMPLES));
    	
    	//debug("Samples: "+n);    
    	return n;
    	
    }//findNumberOfSamples()
    
    private static final boolean signChanged(GeoFunction f,double x) {
    	double delta=Kernel.MIN_PRECISION*10;		//Used in AlgoRootsPolynomial
    	double left,right,lefty,righty;
    	boolean signChanged;
    	left=x-delta;right=x+delta;
    	int count=0;
    	while(Math.abs(lefty=f.evaluate(left)) < delta && count++ < 100)left=left-delta;
    	count=0;
    	while(Math.abs(righty=f.evaluate(right))< delta && count++ < 100) right=right+delta;
    	signChanged=lefty*righty<0.0d;
    	return signChanged;
    }//signChanged(f,x,deltax)

// * //--- SNIP (after debugging and testing) -------------------------   
    /// --- Test interface --- ///
    //  Running testcases from external testscript Test_Extremum.bsh from plugin scriptrunner.
	//  algo=new AlgoRoots(cons)
	//  listArray(double[])
	//  listLabels(String[])
	//  listPoints(GeoPoint[])
	//  
    
    /* Constructor for externel test of static functions */
    public AlgoRoots(Construction cons){
    	super(cons);	
    }//Test Constructor

    
	private static final boolean DEBUG	= true;
	
    private final static void debug(String s) {
        if(DEBUG) {
        	Application.debug(s);
        }//if()
    }//debug()       
    
    public final static void listArray(double[] a){
    	if(a!=null){
    	int l=a.length;
    	System.out.println("Length: "+l);
    	for(int i=0;i<l;i++){
    		System.out.println("a["+i+"]: "+a[i]);
    	}//for
    	}else{
    		debug("array was null!");
    	}//if not null
    }//listArray(a)
    
    public final static void listLabels(String[] a){
    	if(a!=null){
    	int l=a.length;
    	System.out.println("Length: "+l);
    	for(int i=0;i<l;i++){
    		System.out.println("Label["+i+"]: "+a[i]);
    	}//for
    	}else{
    		debug("labels was null!");
    	}//if not null
    }//listLabels(a)
    
    public final static void listPoints(GeoPoint[] gpts) {
    	if (gpts!=null) {
    	int n=gpts.length;
    	System.out.println("Length: "+n);
    	for(int i=0;i<n;i++) {
    		System.out.println("Label: "+gpts[i].getLabel()+"     pt["+i+"]: ("+gpts[i].x+","+gpts[i]+")");
    	}//for
    	}else{
    		debug("gpts was null!");
    	}//if not null
    }//listPoints(GeoPoint[])
 
    public final  static String testGeoPointX(GeoPoint geo,double answer){
    	double  value;
    	boolean ok=false;
   		value=geo.getX();
    	if(Math.abs(value-answer)<Kernel.MIN_PRECISION){
    		return " === GOOD ===";
    	} else {
    		return " ****************** WRONG *******************\n"+
    			   " ERROR: "+Math.abs(value-answer)+"  for: "+geo.getNameDescription()+"\n"+
    		       " ********************************************\n";
    	}//if near enough...
    }//test(label,answer)
    
// */ //--- SNIP end ---------------------------------------    
    
}//class AlgoRoots