package math.equation;

import awt.Draw;
import math.Maths;
import math.geometry.Point2D;
import math.matrix.Matrix;
import math.matrix.Matrix2D;
import math.matrix.Vector;
import math.matrix.VectorComplex;
import math.number.NumberComplex;
import exception.ComplexException;
import exception.IllegalDimensionException;
import exception.NoSquareException;
import exception.SingularException;

/**
 * @author Daniel Weschke
 */
public class Polynomial{
	protected String symbolic = "x";
	
	/**
	 * coefficients a<sub>i</sub> , ordered by
	 * a<sub>n</sub><sup>n</sup> + a<sub>n-1</sub><sup>n-1</sup> + ... + a<sub>2</sub>x<sup>2</sup> + a<sub>1</sub>x + a<sub>0</sub>
	 */
    private double[] coef;
    
    /**
     * integration constants c<sub>i</sub> ordered by
     * c<sub>n</sub><sup>n</sup> + c<sub>n-1</sub><sup>n-1</sup> + ... + c<sub>2</sub>x<sup>2</sup> + c<sub>1</sub>x + c<sub>0</sub>
     */
    private double[] constI;

    /**
     * Construct a zero polynomial, 0.
     */
    public Polynomial(){
    	this(0,0);
    }
    
    /**
     * Construct a zero polynomial,
     * 0x<sup>0</sup> + 0x<sup>1</sup> + ... + 0x<sup>n</sup>.
     * @param n number of zero coefficient
     */
    public Polynomial(int n){
    	coef = new double[n];
    }
    
    /**
     * Construct a constant polynomial,
     * ax<sup>0</sup>.
     * @param a constant
     */
    public Polynomial(double a){
    	coef = new double[]{a};
    }
    
    /**
     * Construct a polynomial,
     * ax<sup>b</sup>.
     * @param a coefficient
     * @param b exponent
     */
    public Polynomial(double a, int b) {
        coef = new double[b+1];
        coef[b] = a;
    }
    
    /**
     * Construct a linear polynomial,
     * a<sub>1</sub>x + a<sub>0</sub>.
     * @param a1 coefficient
     * @param a0 coefficient
     */
    public Polynomial(double a1, double a0) {
        coef = new double[]{a0, a1};
    }
    
    /**
     * Construct a quadratic polynomial,
     * a<sub>2</sub>x<sup>2</sup> + a<sub>1</sub>x + a<sub>0</sub>.
     * @param a2 coefficient
     * @param a1 coefficient
     * @param a0 coefficient
     */
    public Polynomial(double a2, double a1, double a0) {
        coef = new double[]{a0, a1, a2};
    }
    
    /**
     * Construct a cubic polynomial,
     * a<sub>3</sub>x<sup>3</sup> + a<sub>2</sub>x<sup>2</sup> + a<sub>1</sub>x + a<sub>0</sub>.
     * @param a3 coefficient
     * @param a2 coefficient
     * @param a1 coefficient
     * @param a0 coefficient
     */
    public Polynomial(double a3, double a2, double a1, double a0) {
        coef = new double[]{a0, a1, a2, a3};
    }
    
    /**
     * Construct a polynomial with an array of coefficients.
     * Ordered by: a<sub>n</sub><sup>n</sup> + a<sub>n-1</sub><sup>n-1</sup> + ... + a<sub>2</sub>x<sup>2</sup> + a<sub>1</sub>x + a<sub>0</sub>
     * @param a coefficients
     */
    public Polynomial(double[] a){
    	this(a, true);
    }
    
    /**
     * Construct a polynomial with an array of coefficients.
     * Ordered by: a<sub>n</sub><sup>n</sup> + a<sub>n-1</sub><sup>n-1</sup> + ... + a<sub>2</sub>x<sup>2</sup> + a<sub>1</sub>x + a<sub>0</sub>
     * @param a coefficients
     * @param reverse order
     */
    public Polynomial(double[] a, boolean reverse){
    	int size = a.length;
    	coef = new double[size];
    	int i;
    	if(reverse)
    		for(i=0; i<size; i++)
    			coef[size-i-1] = a[i];
    	else
    		for(i=0; i<size; i++)
    			coef[i] = a[i];
    }
    
    /**
     * Construct a polynomial with an array of coefficients.
     * Ordered by: a<sub>n</sub><sup>n</sup> + a<sub>n-1</sub><sup>n-1</sup> + ... + a<sub>2</sub>x<sup>2</sup> + a<sub>1</sub>x + a<sub>0</sub>
     * @param a coefficients
     */
    public Polynomial(Object... a){
    	this(true, a);
    }
    
    /**
     * Construct a polynomial with an array of coefficients.
     * Ordered by: a<sub>n</sub><sup>n</sup> + a<sub>n-1</sub><sup>n-1</sup> + ... + a<sub>2</sub>x<sup>2</sup> + a<sub>1</sub>x + a<sub>0</sub>
     * @param a coefficients
     */
    public Polynomial(boolean reverse, Object[] a){
    	int size = a.length;
    	coef = new double[size];
    	int i;
    	if(reverse)
    		for(i=0; i<size; i++)
    			coef[size-i-1] = Double.parseDouble(a[i].toString());
    	else
    		for(i=0; i<size; i++)
    			coef[i] = Double.parseDouble(a[i].toString());
    }
    
    /**
     * Construct a polynomial with a string of coefficients.
     * Separated by space and ordered by:
     * a<sub>n</sub><sup>n</sup> + a<sub>n-1</sub><sup>n-1</sup> + ... + a<sub>2</sub>x<sup>2</sup> + a<sub>1</sub>x + a<sub>0</sub>
     * @param a coefficients
     */
    public Polynomial(String a){
    	this(0,0);
    	int i;
    	
    	String[] p = a.trim().split("\\s+");
    	int size = p.length;
    	
    	if(size > 0){
        	coef = new double[size];
        	for(i=0; i<size; i++)
        		coef[size-i-1] = Double.valueOf(p[i]);
    	}
    }
    
    /**
     * Constant function.
     * f(x) = a.<br />
     * f(p<sub>x</sub>) = a = p<sub>y</sub>.
     * @param p
     */
    public Polynomial(Point2D p){
    	coef = new double[]{p.y()};
    }
    
    /**
     * Linear function.
     * f(x) = a<sub>0</sub> + a<sub>1</sub>x .<br />
     * f(p<sub>1x</sub>) = a<sub>0</sub> + a<sub>1</sub>p<sub>1x</sub> = p<sub>1y</sub><br />
     * f(p<sub>2x</sub>) = a<sub>0</sub> + a<sub>1</sub>p<sub>2x</sub> = p<sub>2y</sub> 
     * @param p1
     * @param p2
     */
    public Polynomial(Point2D p1, Point2D p2){
    	Matrix A = new Matrix(new double[][]{{1, p1.x()}, {1, p2.x()}});
    	Vector b = new Vector(new double[]{p1.y(), p2.y()});
    	coef = A.solve(b).getArray();
    }
    
    public Polynomial(Point2D p, double alpha){
    	this(p, alpha, false);
    }
    
    public Polynomial(Point2D p, double alpha, boolean degree){
    	double a = alpha;
    	if(degree) a = Maths.cosd(alpha);
    	coef = new double[]{p.y()-a*p.x(), a};
    }
    
    /**
     * Copy constructor
     * @param a polynomial
     */
    public Polynomial(Polynomial a){
    	coef = a.coef;
    	constI = a.constI;
    }
	
	/**
	 * Binomial (1+x)<sup>n</sup>
	 * <p>
	 * n = 0 : 1x^0<br />
	 * n = 3 : 1x^3 + 3x^2 + 3x^1 + 1x^0<br />
	 * n = 6 : 1x^6 + 6x^5 + 15x^4 + 20x^3 + 15x^2 + 6x^1 + 1x^0<br />
	 * @param n
	 * @return (1+x)<sup>n</sup>
	 */
	public static Polynomial binomial(int n){
		Polynomial one      = new Polynomial(1, 0);   // 1
		Polynomial x        = new Polynomial(1, 1);   // x
		Polynomial binomial = one;                    // 1
		int i;
		for(i=0; i<n; i++)
			binomial = binomial.times(x.plus(one));
		return binomial;
	}
	
	/**
	 * n-th Chebyshev polynomial of the first kind.
	 * <p>
	 * T<sub>0</sub> = 1<br />
	 * T<sub>1</sub> = x<br />
	 * T<sub>n</sub> = 2x * T<sub>n-1</sub> - T<sub>n-2</sub><br />
	 * <p>
	 * n = 1: 1x<sup>0</sup><br />
	 * n = 2: 1x<sup>1</sup><br />
	 * n = 9: 256x<sup>9</sup> - 576x<sup>7</sup> + 432x<sup>5</sup> - 120x<sup>3</sup> + 9x<sup>1</sup><br />
	 * @param n th Chebyshev polynomial
	 * @return T<sub>n</sub> = 2x * T<sub>n-1</sub> - T<sub>n-2</sub>
	 * @see Polynomials#chebyshevT(int)
	 */
	public static Polynomial chebyshevT(int n){
        if(n == 0) return new Polynomial(1, 0); // 1
        else if(n == 1) return new Polynomial(1, 1); // x
        else return new Polynomial(2, 1).times(chebyshevT(n-1)).minus(chebyshevT(n-2));
	}
	
	/**
	 * n-th Chebyshev polynomial of the second kind.
	 * <p>
	 * U<sub>0</sub> = 1<br />
	 * U<sub>1</sub> = 2x<br />
	 * U<sub>n</sub> = 2x * U<sub>n-1</sub> - U<sub>n-2</sub><br />
	 * <p>
	 * n = 1: 1x<sup>0</sup><br />
	 * n = 2: 2x<sup>1</sup><br />
	 * n = 9: 512x<sup>9</sup> - 1024x<sup>7</sup> + 672x<sup>5</sup> - 160x<sup>3</sup> + 10x<sup>1</sup><br />
	 * @param n th Chebyshev polynomial
	 * @return U<sub>n</sub> = 2x * U<sub>n-1</sub> - U<sub>n-2</sub>
	 * @see Polynomials#chebyshevU(int)
	 */
	public static Polynomial chebyshevU(int n){
        if(n == 0) return new Polynomial(1, 0); // 1
        else if(n == 1) return new Polynomial(2, 1); // 2x
        else return new Polynomial(2, 1).times(chebyshevU(n-1)).minus(chebyshevU(n-2));
	}
	
	/**
	 * n-th Hermite polynomial.
	 * <p>
	 * H<sub>0</sub> = 1<br />
	 * H<sub>1</sub> = 2x<br />
	 * H<sub>n</sub> = 2x * H<sub>n-1</sub> - 2(n-1) * H<sub>n-2</sub><br />
	 * <p>
	 * n = 1: 1x<sup>0</sup><br />
	 * n = 2: 2x<sup>1</sup><br />
	 * n = 9: 512x<sup>9</sup> - 9216x<sup>7</sup> + 48384x<sup>5</sup> - 80640x<sup>3</sup> + 30240x<sup>1</sup><br />
	 * @param n th Hermite polynomial
	 * @return H<sub>n</sub> = 2x * H<sub>n-1</sub> - 2(n-1) * H<sub>n-2</sub>
	 * @see Polynomials#hermite(int)
	 */
	public static Polynomial hermite(int n){
        if(n == 0) return new Polynomial(1, 0); // 1
        else if(n == 1) return new Polynomial(2, 1); // 2x
        else return new Polynomial(2, 1).times(hermite(n-1)).minus(hermite(n-2).times(2*(n-1)));
	}
	
	/**
	 * First derivative of n-th Hermite polynomial.
	 * <p>
	 * H'<sub>0</sub> = 0<br />
	 * H'<sub>n</sub> = 2n * H<sub>n-1</sub><br />
	 * <p>
	 * n = 1: 2x<sup>0</sup><br />
	 * n = 2: 8x<sup>1</sup><br />
	 * n = 9: 4608x<sup>8</sup> - 64512x<sup>6</sup> + 241920x<sup>4</sup> - 241920x<sup>2</sup> + 30240x<sup>0</sup><br />
	 * @param n th Hermite polynomial
	 * @return H'<sub>n</sub> = 2n * H<sub>n-1</sub>
	 * @see #hermiteD(int, int)
	 */
	public static Polynomial hermiteD(int n){
        if(n == 0) return new Polynomial(0, 0); // 0
        else return hermite(n-1).times(2*n);
	}
	
	/**
	 * m-th derivative of n-th Hermite polynomial.
	 * <p>
	 * n = 9, m = 2: 36864x<sup>7</sup> - 387072x<sup>5</sup> + 967680x<sup>3</sup> - 483840x<sup>1</sup><br />
	 * @param n th Hermite polynomial
	 * @param m derivative
	 * @return H<sup>(m)</sup><sub>n</sub> = 2n * H<sup>(m-1)</sup><sub>n-1</sub>
	 * @see #hermiteD(int)
	 */
	public static Polynomial hermiteD(int n, int m){
        if(m == 0) return hermite(n);
        if(n < m) return  new Polynomial(0, 0); // 0
        if(m == 1) return hermiteD(n);
        else return hermiteD(n-1,m-1).times(2*n);
	}
	
	/**
	 * n-th Hermite polynomial (probabilist).
	 * <p>
	 * He<sub>0</sub> = 1<br />
	 * He<sub>1</sub> = x<br />
	 * He<sub>n</sub> = x * He<sub>n-1</sub> - (n-1) * He<sub>n-2</sub><br />
	 * <p>
	 * n = 1: 1x<sup>0</sup><br />
	 * n = 2: x<sup>1</sup><br />
	 * n = 9: x<sup>9</sup> - 36x<sup>7</sup> + 378x<sup>5</sup> - 1260x<sup>3</sup> + 945x<sup>1</sup><br />
	 * @param n th Hermite polynomial
	 * @return He<sub>n</sub> = x * He<sub>n-1</sub> - (n-1) * He<sub>n-2</sub>
	 * @see Polynomials#hermiteE(int)
	 */
	public static Polynomial hermiteE(int n){
        if(n == 0) return new Polynomial(1, 0); // 1
        else if(n == 1) return new Polynomial(1, 1); // x
        else return new Polynomial(1, 1).times(hermiteE(n-1)).minus(hermiteE(n-2).times(n-1));
	}
	
	/**
	 * First derivative of n-th Hermite polynomial (probabilist).
	 * <p>
	 * He'<sub>0</sub> = 0<br />
	 * He'<sub>n</sub> = n * He<sub>n-1</sub><br />
	 * <p>
	 * n = 1: 1x<sup>0</sup><br />
	 * n = 2: 2x<sup>1</sup><br />
	 * n = 9: 9x<sup>8</sup> - 252x<sup>6</sup> + 1890x<sup>4</sup> - 3780x<sup>2</sup> + 945x<sup>0</sup><br />
	 * @param n th Hermite polynomial
	 * @return He'<sub>n</sub> = n * He<sub>n-1</sub>
	 * @see #hermiteED(int, int)
	 */
	public static Polynomial hermiteED(int n){
        if(n == 0) return new Polynomial(0, 0); // 0
        else return hermiteE(n-1).times(n);
	}
	
	/**
	 * m-th derivative of n-th Hermite polynomial (probabilist).
	 * <p>
	 * n = 9, m = 2: 72x<sup>7</sup> - 1512x<sup>5</sup> + 7560x<sup>3</sup> - 7560x<sup>1</sup><br />
	 * @param n th Hermite polynomial
	 * @param m derivative
	 * @return He<sup>(m)</sup><sub>n</sub> = n * He<sup>(m-1)</sup><sub>n-1</sub>
	 * @see #hermiteED(int)
	 */
	public static Polynomial hermiteED(int n, int m){
        if(m == 0) return hermiteE(n);
        if(n < m) return  new Polynomial(0, 0); // 0
        if(m == 1) return hermiteED(n);
        else return hermiteED(n-1,m-1).times(n);
	}

    /**
     * 
     * @return the degree of this polynomial (0 for the zero polynomial)
     */
    public int degree(){
        int d = 0, i;
        if(coef != null)
        	for(i=0; i<coef.length; i++)
        		if(coef[i] != 0) d = i;
        return d;
    }

    /**
     * 
     * @return the degree of integration constants of this polynomial (0 for the zero polynomial)
     */
    private int degreeConstI(){
        int d = 0, i;
        if(constI != null)
        	for(i=0; i<constI.length; i++)
        		if(constI[i] != 0) d = i;
        return d;
    }
    
    /**
     * 
     * @return the coefficients of this polynomial
     */
    public double[] getCoef(){
    	return coef;
    }
    
    /**
     * 
     * @param i index
     * @return the indexed coefficient of this polynomial
     */
    public double getCoef(int i){
    	return coef[i];
    }
    
    /**
     * 
     * @return the symbolic of all polynomials
     */
    public String getSymbolic(){
    	return symbolic;
    }
    
    /**
     * set the coefficients of this polynomial
     * @param a coefficients array
     */
    public void setCoef(double[] a){
    	coef = a;
    }
    
    /**
     * set the indexed coefficient of this polynomial
     * @param a coefficient
     * @param i index
     */
    public void setCoef(double a, int i){
    	coef[i] = a;
    }
    
    /**
     * set the symbolic of all polynomials
     * @param symb symbolic
     */
    public void setSymbolic(String symb){
    	symbolic = symb;
    }

    /**
     * 
     * @param b g(x)
     * @return h(x) = f(x) + g(x)
     */
    public Polynomial plus(Polynomial b){
        Polynomial
        	a = this,
        	c = new Polynomial(0, Math.max(a.degree(), b.degree()));
        for(int i = 0; i <= a.degree(); i++) c.coef[i] += a.coef[i];
        for(int i = 0; i <= b.degree(); i++) c.coef[i] += b.coef[i];
        return c;
    }

    /**
     * 
     * @param b g(x)
     * @return h(x) = f(x) - g(x)
     */
    public Polynomial minus(Polynomial b){
        return plus(b.times(-1));
    }
    
    /**
     * scalar multiplication
     * @param a scalar
     * @return the scalar multiplication of a polynomial. g(x) = a * f(x)
     */
    public Polynomial times(double a){
    	Polynomial c = new Polynomial(0,degree());
        for(int i=0; i<=degree(); i++) c.coef[i] = a * coef[i];
		return c;
    }

    /**
     * 
     * @param b g(x)
     * @return the polynomial multiplication. h(x) = f(x) * g(x)
     */
    public Polynomial times(Polynomial b){
        Polynomial a = this;
        Polynomial c = new Polynomial(0, a.degree() + b.degree());
        for(int i=0; i<=a.degree(); i++)
            for(int j=0; j<=b.degree(); j++)
                c.coef[i+j] += (a.coef[i] * b.coef[j]);
        return c;
    }

    /**
     * compute using Horner's method
     * @param b g(x)
     * @return h(x) = f(g(x))
     */
    public Polynomial compose(Polynomial b){
        Polynomial a = this;
        Polynomial c = new Polynomial(0, 0);
        for(int i=a.degree(); i>=0; i--) {
            Polynomial term = new Polynomial(a.coef[i], 0);
            c = term.plus(b.times(c));
        }
        return c;
    }

    /**
     * use Horner's method to compute and return the polynomial evaluated at x
     * @param x
     * @return the polynomial evaluated at x
     */
    public double evaluate(double x){
    	int i;
        double p = 0;
        for(i=degree(); i>=0; i--)
            p = coef[i] + (x * p);
        return p;
    }
    
    /**
     * Compute the polynomial evaluation at x<sub>i</sub> using Horner's method.
     * @param x
     * @return the polynomial evaluation at x<sub>i</sub>
     */
    public double[] evaluate(double[] x){
    	int i;
    	double[] result = new double[x.length];
    	for(i=0;i<x.length;i++)
    		result[i] = evaluate(x[i]);
    	return result;
    }
    
    /**
     * Compute the polynomial evaluation at x<sub>i</sub> using Horner's method.
     * @param x
     * @return the polynomial evaluation at x<sub>i</sub>
     * @throws IllegalDimensionException x must no be null
     */
    public Vector evaluate(Vector x) throws IllegalDimensionException{
    	if(x == null) throw new IllegalDimensionException();
    	int i;
    	Vector result = new Vector(x.n());
    	for(i=0;i<x.n();i++)
    		result.set(i, evaluate(x.get(i)));
    	return result;
    }
    
    /**
     * Compute the polynomial evaluation at x<sub>i</sub> using Horner's method.
     * @param start
     * @param increment
     * @param end
     * @return the polynomial evaluation at x<sub>i</sub>
     * @throws IllegalDimensionException x must no be null
     */
    public Matrix2D evaluateAsMatrix2D(double start, double increment, double end) throws IllegalDimensionException{
        Vector x = Vector.fill(start, increment, end);
    	return evaluateAsMatrix2D(x);
    }
    
    /**
     * Compute the polynomial evaluation at x<sub>i</sub> using Horner's method.
     * @param x
     * @return the polynomial evaluation at x<sub>i</sub>
     * @throws IllegalDimensionException x must no be null
     */
    public Matrix2D evaluateAsMatrix2D(Vector x) throws IllegalDimensionException{
    	if(x == null) throw new IllegalDimensionException();
    	int i;
    	Matrix2D result = new Matrix2D(x.n());
//    	System.out.println(x.length());
//    	System.out.println(result.getM() + " * " + result.getN());
    	for(i=0;i<x.n();i++)
    		result.set(i, x.get(i), evaluate(x.get(i)));
    	return result;
    }

    /**
     * derive this polynomial and return it
     * @return f'(x)
     */
    public Polynomial derive(){
    	Polynomial deriv;
        if(degree() == 0) deriv = new Polynomial(0, 0);
        else {
	        deriv = new Polynomial(0, degree() - 1);
	        for(int i=0; i<degree(); i++)
	            deriv.coef[i] = (i + 1) * coef[i + 1];
        }
    	if(constI != null){
    		deriv.constI = new double[degreeConstI()];
            for(int i=0; i<degreeConstI(); i++)
                deriv.constI[i] = (i + 1) * constI[i + 1];
    	}
        return deriv;
    }

    /**
     * derive this polynomial n times and return it
     * @param n
     * @return f^n(x)
     */
    public Polynomial derive(int n){
    	Polynomial deriv = new Polynomial(this);
    	if(n>0) deriv = derive().derive(n-1);
        return deriv;
    }

    /**
     * derive this polynomial
     * @param x
     * @return f'(x)
     */
    public double derive(double x){
        return derive().evaluate(x);
    }

    /**
     * derive this polynomial n times
     * @param n
     * @param x
     * @return f^n(x)
     */
    public double derive(int n, double x){
        return derive(n).evaluate(x);
    }
    
    /**
     * integrate this polynomial and return it
     * @return F(x)
     */
    public Polynomial integrate(){
    	Polynomial integ;
        if(degree() == 0 && getCoef(0) == 0){
        	integ = new Polynomial(coef.length+1);
        } else {
	        integ = new Polynomial(0, degree() + 1);
	    	for(int i=0; i<=degree(); i++){
	    		integ.coef[i+1] = 1./(i+1) * coef[i];
	//        	System.out.println("1/"+(i+1)+"*"+coef[i] + "x^" + (i+1) + " : " + (1./(i+1) * coef[i]));
	    	}
        }
    	if(constI == null)
    		integ.constI = new double[]{1};
    	else {
//    		System.out.println(degreeConstI()+2);
    		integ.constI = new double[degreeConstI()+1+1]; // degree +1 and +1 for beginning null in array
//            for(int i = degreeConstI(); i >= 0; i--) {
//            	integ.constI[i+1] = 1./(i+1) * constI[i];
//            	System.out.println(integ.constI[i]);
//            	System.out.println(integ.constI[i+1]);
//            }
            for(int i = 0; i <= degreeConstI(); i++) {
            	integ.constI[i+1] = 1./(i+1) * constI[i];
            }
            integ.constI[0] = 1;
//        	System.out.println(integ.constI[0]);
    	}
//    	System.out.println(integ.constI[0]);
    	return integ;
    }
    
    /**
     * integrate this polynomial and return the value
     * @param left
     * @param right
     * @return the value
     */
    public double integrate(double left, double right){
    	double result = 0;
    	for(int i=0; i<=degree(); i++){
    		result += coef[i]/(i+1)*(Math.pow(right, (i+1)) - Math.pow(left, (i+1)));
    	}
    	return result;
    }
    
    /**
     * Include one condition for the integration constant of the constant term or
     * for a polynomial with only one integration constant.
     * @param position or value of the independent variable
     * @param value of the condition
     * @return the polynomial with the included condition
     * @throws IllegalDimensionException 
     */
    public Polynomial condition(double position, double value) throws IllegalDimensionException{
        Polynomial result = new Polynomial(this);
        // TODO: verify, seems good
    	if(degreeConstI() == 0 || (position == 0 && constI[0]>0) || countI()==1){
    		int ic = 0; // the constant term case
    		if(countI()==1) ic = new Vector(constI).indexOfMax(); // the only one integration constant case
    		result.constI[ic] = 0; // delete the integration constant
    		if(result.getCoef(ic)>0) // if there is already a value
    			result.setCoef(ic,0); // integrate it to C (set the value to null / delete it)
    		result.setCoef((value-result.evaluate(position)/Math.pow(position,ic)), ic);
    	} else throw new IllegalDimensionException("Illegal integration constant dimensions.");
    	return result;
    }
    
    /**
     * Include exactly the same number of conditions as integrations constants.
     * @param conditions triple of
     * number of derivatives,
     * the position or value of the independent variable and
     * the value of the condition.
     * @return the polynomial with the included conditions
     * @throws IllegalDimensionException
     * @throws SingularException 
     * @throws NoSquareException 
     */
    public Polynomial condition(double[][] conditions) throws IllegalDimensionException, NoSquareException, SingularException{
    	int countI = countI();
    	int countC = conditions.length;
    	if(countI != countC)
    		throw new IllegalDimensionException("The number of integration constants and the number of conditions must be the same.");
    	// TODO: verify, seems good
    	Matrix A = new Matrix(countI,countI);
    	Polynomial P = new Polynomial(this);
    	Vector b = new Vector(countI);
    	int i,j,k;
    	for(i=0;i<countC;i++){ // the number of conditions
			k = (int)conditions[i][0]; // the number of derivatives
			if(k>0) P = this.derive(k);
//			System.out.println(P);
			if(P.constI!=null)
	    		for(j=0;j<P.constI.length;j++) // the number of integration constants
	    			if(P.constI[j]>0)
	    				A.set(i, countI-1-k++, P.constI[j]*Math.pow(conditions[i][1],j));
//    		System.out.println(conditions[i][2] + " - " + P.evaluate(conditions[i][1]));
    		b.set(i, conditions[i][2] - P.evaluate(conditions[i][1]));
    	}
    	Vector x = A.inverse().times(b);
//		System.out.println(A); System.out.println(b); System.out.println(x);

    	Polynomial result = new Polynomial(this);
//		System.out.println(result.coef.length + " " + result.constI.length);
		j = 0;
    	for(i=0;i<constI.length;i++){ // the number of conditions
//    		System.out.println(countC-1-j + " " + i);
    		result = addConstIValue(x.get(countC-1-j++), i);
    	}
    	return result;
    }
    
    /**
     * Replace an integration constant with a value and
     * add it with the respective term.
     * @param value
     * @param index
     * @return replaced polynomial
     */
    public Polynomial addConstIValue(double value, int index){
    	Polynomial result = new Polynomial(this);
//		System.out.println(index + " " + value);
//		System.out.println(result);
    	result.setCoef(result.constI[index]*value, index);
    	result.constI[index] = 0;
//		System.out.println(result);
//		System.out.println();
    	return result;
    }
    
    /**
     * count integration constants (unequal null)
     * @return number of integration constants
     */
    public int countI(){
        if(constI == null) return 0;
    	int count = 0, i;
    	for(i=0; i<constI.length; i++)
    		if(constI[i]!=0) count++;
    	return count;
    }
	
    /**
     * replace the polynomial with the monic form,
     * e.q. ax<sup>2</sup> + bx + c (where a != 0)
     * will be replaced by x<sup>2</sup> + px + q 
     * with p = b/a and q = c/a
	 * @return monic polynomial
     */
	public Polynomial monicForm(){
		if(coef.length>1 && coef[coef.length-1]!=1){
			int i;
			double[] monic = new double[coef.length];
			for(i=0; i<coef.length; i++){
				monic[coef.length-1-i] = coef[i]/coef[coef.length-1];
			}
			return new Polynomial(monic);
		} else return this;
	}

	/**
	 * Calculate real roots of a real function.<br />
	 * Computation of cubic polynomials with Cardano's method if D>=0 for real roots,
	 * as a reminder:<br />
	 * monic form x<sup>3</sup>+ax<sup>2</sup>+bx+c=0, substitute x = z -a/3<br />
	 * z<sup>3</sup>+pz+q=0 with p=b-a<sup>2</sup>/3, q=2a<sup>3</sup>/27-a*b/3+c<br />
	 * u=cbrt(-q/2+sqrt(D)), v=cbrt(-q/2-sqrt(D))<br />
	 * e<sub>1</sub>=-1/2+1/2isqrt(3), e<sub>2</sub>=e<sub>1</sub><sup>2</sup>=-1/2-1/2isqrt(3)<br />
	 * z<sub>1</sub>=u+v, z<sub>2</sub>=ue<sub>1</sub>+ve<sub>2</sub>, z<sub>3</sub>=ue2+ve1
	 * @return the real roots/zeros of a function.
	 * @throws ComplexException
	 * @throws IllegalDimensionException
	 * @see Polynomial#roots()
	 */
	public Vector rootsRe() throws ComplexException, IllegalDimensionException{
		Vector roots = null;
		VectorComplex cr = roots();
		switch(degree()){
		case 1:
			roots = new Vector(cr.getRe(0));
			break;
		case 2:
			if(isC()) throw new ComplexException();
			roots = new Vector(cr.getRe(0),cr.getRe(1));
			break;
		case 3:
			if(!isC()) roots = new Vector(cr.getRe(0),cr.getRe(1),cr.getRe(2));
			else roots = new Vector(cr.getRe(0)); // x2, x3 complex
			break;
		default:
			break;
		}
		if(degree() > 0) roots.sort();
		return roots;
	}

	/**
	 * Calculate complex roots of a real function.<br />
	 * Computation of cubic polynomials with Cardano's method if D>=0 for real roots,
	 * as a reminder:<br />
	 * monic form x<sup>3</sup>+ax<sup>2</sup>+bx+c=0, substitute x = z -a/3<br />
	 * z<sup>3</sup>+pz+q=0 with p=b-a<sup>2</sup>/3, q=2a<sup>3</sup>/27-a*b/3+c<br />
	 * u=cbrt(-q/2+sqrt(D)), v=cbrt(-q/2-sqrt(D))<br />
	 * e<sub>1</sub>=-1/2+1/2isqrt(3), e<sub>2</sub>=e<sub>1</sub><sup>2</sup>=-1/2-1/2isqrt(3)<br />
	 * z<sub>1</sub>=u+v, z<sub>2</sub>=ue<sub>1</sub>+ve<sub>2</sub>, z<sub>3</sub>=ue2+ve1
	 * @return the real roots/zeros of a function.
	 * @throws IllegalDimensionException 
	 * @throws ComplexException 
	 */
	public VectorComplex roots() throws IllegalDimensionException{
		if(degree()>3) throw new IllegalDimensionException();
		VectorComplex roots = null;
		double a,b,c,D;
		NumberComplex x1, x2 = null, x3;
		switch(degree()){
		case 1:
			roots = new VectorComplex(-coef[0]/coef[1]);
			break;
		case 2:
			a = coef[2];
			b = coef[1];
			c = coef[0];
			D = b*b - 4*a*c;
			if(D>=0){
				x1 = new NumberComplex((-b - Math.sqrt(D)) / (2*a));
				x2 = new NumberComplex((-b + Math.sqrt(D)) / (2*a));
			} else {
				x1 = new NumberComplex(-b/(2*a),-Math.sqrt(-D)/(2*a));
				x2 = new NumberComplex(-b/(2*a), Math.sqrt(-D)/(2*a));
			}
			roots = new VectorComplex(x1,x2);
			break;
			// TODO: validate !!!
		case 3:
			Polynomial monic = monicForm();
			a = monic.coef[2];
			b = monic.coef[1];
			c = monic.coef[0];
//			System.out.println(this);
//			System.out.println(a+" "+b+" "+c);
			double p = b - a*a/3;
			double q = 2*a*a*a/27 - a*b/3 + c;
//			System.out.println(p+" "+q);
			D = q*q/4 + p*p*p/27;
//	        System.out.println("Polynomial:rootRe:D = " + D);
			NumberComplex z1 = null, z2 = null, z3 = null;
			if(p==0 && q==0){
				z1 = new NumberComplex();
				z2 = new NumberComplex();
				z3 = new NumberComplex();
			}
			else if(D==0){ // verified!
				double u = Math.cbrt(-q/2); // Math.cbrt(-q/2 +Math.sqrt(D))
				double v = Math.cbrt(-q/2); // Math.cbrt(-q/2 -Math.sqrt(D))
				z1 = new NumberComplex(u+v);
				z2 = new NumberComplex(-(u+v)/2); // -(u+v)/2 -((u-v)/2)*Math.sqrt(3)*i
				z3 = new NumberComplex(-(u+v)/2); // -(u+v)/2 +((u-v)/2)*Math.sqrt(3)*i
			}
			else if(D<0){
		        double roh = Math.sqrt(-p*p*p/27);
		        double phi = Math.acos(-q/(2*roh));
				z1 = new NumberComplex(2*Math.cbrt(roh)*Math.cos(phi/3));
				z2 = new NumberComplex(2*Math.cbrt(roh)*Math.cos((phi+2*Math.PI)/3));
				z3 = new NumberComplex(2*Math.cbrt(roh)*Math.cos((phi+4*Math.PI)/3));
			}
			else if(D>0){ // verified!
				double u = Math.cbrt(-q/2 + Math.sqrt(D));
				double v = Math.cbrt(-q/2 - Math.sqrt(D));
				z1 = new NumberComplex(u + v);
				z2 = new NumberComplex(-(u+v)/2, -((u-v)/2)*Math.sqrt(3));
				z3 = new NumberComplex(-(u+v)/2, +((u-v)/2)*Math.sqrt(3));
			}
			x1 = z1.minus(a/3);
			x2 = z2.minus(a/3);
			x3 = z3.minus(a/3);
			roots = new VectorComplex(x1,x2,x3);
			break;
		default:
			break;
		}
		return roots;
	}

    /**
     * Run Newton's method to finds the roots of one variable.
     * Newton's method to find x* such that f(x*) = 0, starting at x
     * @param x
     * @return roots of one variable
     */
    public double rootNewton(double x) {
        while(Math.abs(evaluate(x) / derive(x)) > Maths.ε)
            x = x - evaluate(x) / derive(x);
        return x;
    }

    /**
     * Run Newton's method to finds the local min/max of
     * a twice-differentiable function of one variable.
     * Newton's method to find x* such that f'(x*) = 0, starting at x
     * @param x
     * @return the local min/max
     */
    public double optimumNewton(double x) {
        while(Math.abs(derive(x) / derive(2,x)) > Maths.ε)
            x = x - derive(x) / derive(2,x);
        return x;
    }
    
	/**
	 * Solve polynomial or computes intersections with polynomial
	 * @param b polynomial
	 * @return intersections
	 * @throws ComplexException
	 * @throws IllegalDimensionException
	 */
    public Vector solve(Polynomial b) throws ComplexException, IllegalDimensionException{
    	return this.minus(b).rootsRe();
    }
	
	/**
	 * Check if this polynomial is complex.
	 * @return boolean true or false
	 */
	public boolean isC(){
		double D;
		switch(degree()){
		case 1:
			return true;
		case 2:
			D = Math.pow(coef[1], 2) - 4*coef[0]*coef[2];
			if(D>=0) return false;
			else return true;
		case 3:
			Polynomial monic = monicForm();
			double a = monic.coef[2], b = monic.coef[1], c = monic.coef[0];
			double p = b - a*a/3, q = 2*a*a*a/27 - a*b/3 + c;
			D = q*q/4 + p*p*p/27;
			if(p==0 && q==0 || D<=0) return false;
			else return true;
		default:
			return true;
		}
	}

    /**
     * Is this Polynomial object equal to b?
     * if(this == b)
     * @param b g(x)
     * @return boolean f(x) =? g(x)
     */
    @Override
	public boolean equals(Object b) {
        if (b == null) return false;
        if (b.getClass() != this.getClass()) return false;
        Polynomial c = ((Polynomial) b).monicForm();
        Polynomial a = monicForm();
        if(a.degree() != c.degree()) return false;
        int i;
        for(i=0; i<=degree(); i++){
        	if(a.coef[i] != c.coef[i]) return false;
        }
        return true;
    }

	/**
	 * Convert to string representation
	 */
    @Override
	public String toString() {
    	int i;
        String s = "";
        double value;
        String[] zahlaufteilung;
		int pre = 1, post = 0, main = 0;
        for(i = degree(); i >= 0; i--) {
        	value = (coef[i] <= Maths.ε && coef[i] >= -Maths.ε) ? 0.0 : coef[i];
        	zahlaufteilung = String.valueOf(value).split("[.]"); // Am Komma trennen
    		if(zahlaufteilung.length>1 && !zahlaufteilung[1].equals("0"))
        		main = zahlaufteilung[1].length(); // max. Nachstellen
            if(main != 0) pre++;	  // false = +1 für Komma
    		post = main>3 ? 3 : main; // 3 = max Nachstellen in der Ausgabe; 
//        	System.out.println(Arrays.toString(zahlaufteilung) + " " + (pre+post)+"."+post);
            if      (i != degree() && value == 0) continue;
         	if      (i == degree() && value  < 0) s = s + "-";
         	else if (i == degree()) s = s + "";
         	else if (value  > 0) s = s + " + ";
            else if (value  < 0) s = s + " - ";
        	if      (i != 0 && (value == 1 || value == -1)) s = s + "";
//        	if      (i != 0 && value == 1) s = s + "";
//        	else if (i != 0 && value ==-1) s = s + "-";
//        	if (i == degree() || value  < 0) s = s + "-";
        	else if (i == degree() && value >= 0 || value > 0) s = s + String.format("%"+(pre+post)+"."+post+"f", value);
            else if (value  < 0) s = s + String.format("%"+(pre+post)+"."+post+"f",-value);
            if      (i == 1) s = s + symbolic;
            else if (i >  1) s = s + symbolic+"^" + i;
        }

    	if(constI != null){
    		String symbolicI = "c";
    		int j = 1;
            for(i = constI.length-1; i >= 0; i--) {
            	if(constI[i] > 0){
                    s = s + " + ";
                    if      (constI[i] != 1) s = s + ( constI[i] );
            		s = s + symbolicI + j++;
                	if      (i == 1) s = s + symbolic;
                	else if (i >  1) s = s + symbolic+"^" + i;
            	}
            }
    	}
        return s;
    }
    
    public String toStr(){
		StringBuffer output = new StringBuffer();
		for(int i = degree(); i >= 0; i--) output.append(coef[i]+" ");
		return output.toString();
    }
    
    public void println(){
    	System.out.println(toString());
    }
	
	public void plot(){
        Vector X = Vector.fill(0, 0.001, 1d);
      	Vector Y = new Vector(evaluate(X.getArray())).times(1);
      	Draw draw = new Draw();
      	draw.animate(X, Y);
	}

    /**
     * test client
     * @param args
     */
    public static void main(String[] args) { 
        Polynomial zero = new Polynomial(0, 0);
        Polynomial con0 = new Polynomial(5, 0);

        Polynomial p1   = new Polynomial(4, 3);
        Polynomial p2   = new Polynomial(3, 2);
        Polynomial p3   = new Polynomial(2, 0);
        Polynomial p4   = new Polynomial(1, 1);
        
        // all 4 lines do the same thing
        Polynomial p    = p1.plus(p2).plus(p3).plus(p4);   // 4x^3 + 3x^2 + x + 2
        p = new Polynomial(new double[]{4,3,1,2});
        p = new Polynomial("  4   3  1 2");
        p = new Polynomial(4,3,1,2);

        Polynomial q1   = new Polynomial(3, 2);
        Polynomial q2   = new Polynomial(5, 0);
        Polynomial q    = q1.plus(q2);                     // 3x^2 + 5
        
        Polynomial r = new Polynomial("4 +8 1 -1");

        System.out.println("zero(x)     = " + zero);
        System.out.println("p(x)        = " + p);
        System.out.println("p(0)        = " + p.evaluate(0));
        System.out.println("q(x)        = " + q);
        System.out.println("p(x) + q(x) = " + p.plus(q));
        System.out.println("p(x) * q(x) = " + p.times(q));
        System.out.println("p(q(x))     = " + p.compose(q));
        System.out.println("0 - p(x)    = " + zero.minus(p));
        System.out.println("p(3)        = " + p.evaluate(3));
        System.out.println("p(x)        = " + p);
        System.out.println("p'(x)       = " + p.derive());
        System.out.println("p''(x)      = " + p.derive().derive());
        System.out.println("monic p''(x)= " + p.derive().derive().monicForm());
        System.out.println("-1 * p''(x) = " + p.derive().derive().monicForm().times(-1));
        System.out.println("q(x)        = " + q);
        System.out.println("Q(1,5)      = " + q.integrate(1, 5));
        System.out.println("Q           = " + q.integrate());
        System.out.println("int(Q)      = " + q.integrate().integrate());
        try {
			System.out.println("int(Q(0))=5 = " + q.integrate().integrate().condition(0, 5));
	        System.out.println("int(int(Q)) = " + q.integrate().integrate().condition(0, 5).integrate());
		} catch (IllegalDimensionException e) {
			e.printStackTrace();
		}
        System.out.println("deg(q)      = " + q.degree());
        System.out.println("a           = " + con0);
        System.out.println("a'          = " + con0.derive());
        System.out.println("p(x)        = " + p);
        try {
			System.out.println("roots(p)    = \n" + p.rootsRe());
	        System.out.println("r(x)        = " + r);
	        System.out.println("roots(r)    = \n" + r.rootsRe());
		} catch (ComplexException | IllegalDimensionException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
        System.out.println("p =? q      = " + p.equals(q));
        Polynomial w = new Polynomial(-1, 0).integrate().integrate().integrate().integrate();
        System.out.println("w           = " + w);
        System.out.println("w''''       = " + w.derive().derive().derive().derive());
        System.out.println("w''''       = " + w.derive(4));
        try {
			System.out.println("w           = " + w.condition(new double[][]{
					{0,0,0},{1,0,0},{2,1,0},{3,1,0}
					}));
		} catch (IllegalDimensionException | NoSquareException | SingularException e) {
			e.printStackTrace();
		}
//        Vector X = Vector.fill(0, 0.001, 1d);
//        Vector Y = new Vector(w.evaluate(X.get())).times(1);
//        Draw draw = new Draw();
//        draw.animate(X, Y);

        System.out.println();
        Polynomial T9  = Polynomial.chebyshevT(9);
        System.out.println(T9);
        Polynomial T9T = new Polynomial(256,0,-576,0,432,0,-120,0,9,0);
        System.out.println("Chebyshev T(9) test: "+T9.equals(T9T));
        Polynomial U9  = Polynomial.chebyshevU(9);
        System.out.println(U9);
        Polynomial U9T = new Polynomial(512,0,-1024,0,672,0,-160,0,10,0);
        System.out.println("Chebyshev U(9) test: "+U9.equals(U9T));
        
        Polynomial H9  = Polynomial.hermite(9);
        System.out.println(H9);
        Polynomial H9T = new Polynomial(512,0,-9216,0,48384,0,-80640,0,30240,0);
        System.out.println("Hermite H(9) test: "+H9.equals(H9T));
        Polynomial H9d  = Polynomial.hermiteD(9);
        System.out.println(H9d);
        Polynomial H9dT = new Polynomial(4608,0,-64512,0,241920,0,-241920,0,30240);
        System.out.println("Hermite H'(9) test: "+H9d.equals(H9dT));
        Polynomial H9d2  = Polynomial.hermiteD(9,2);
        System.out.println(H9d2);
        Polynomial H9d2T = new Polynomial(36864,0,-387072,0,967680,0,-483840,0);
        System.out.println("Hermite H''(9) test: "+H9d2.equals(H9d2T));
        
        Polynomial He9  = Polynomial.hermiteE(9);
        System.out.println(He9);
        Polynomial He9T = new Polynomial(1,0,-36,0,378,0,-1260,0,945,0);
        System.out.println("Hermite He(9) test: "+He9.equals(He9T));
        Polynomial He9d  = Polynomial.hermiteED(9);
        System.out.println(He9d);
        Polynomial He9dT = new Polynomial(9,0,-252,0,1890,0,-3780,0,945);
        System.out.println("Hermite He'(9) test: "+He9d.equals(He9dT));
        Polynomial He9d2  = Polynomial.hermiteED(9,2);
        System.out.println(He9d2);
        Polynomial He9d2T = new Polynomial(72,0,-1512,0,7560,0,-7560,0);
        System.out.println("Hermite He''(9) test: "+He9d2.equals(He9d2T));
   }

}