/* 
GeoGebra - Dynamic Mathematics for Everyone
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.

*/

/*
 * AlgoSimpleRootsPolynomial.java
 *
 * Created on 28.07.2010, 13:20
 */
package geogebra.kernel.implicit;

import geogebra.euclidian.EuclidianConstants;
import geogebra.kernel.AlgoRoots;
import geogebra.kernel.AlgoSimpleRootsPolynomial;
import geogebra.kernel.Construction;
import geogebra.kernel.GeoFunction;
import geogebra.kernel.GeoLine;
import geogebra.kernel.GeoNumeric;
import geogebra.kernel.GeoPoint;
import geogebra.kernel.Kernel;
import geogebra.kernel.parser.ParseException;
import geogebra.main.MyError;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.math.analysis.polynomials.PolynomialFunction;

/**
 * Algorithm to intersect Implicit polynomials with either lines or polynomials
 */
public class AlgoIntersectImplicitpolyParametric extends
		AlgoSimpleRootsPolynomial {
	
	private PolynomialFunction tx;
	private PolynomialFunction ty;
	private GeoImplicitPoly p;
	private GeoLine l;
	private GeoFunction f;
	private GeoPoint[] tangentPoints;

	public AlgoIntersectImplicitpolyParametric(Construction c,GeoImplicitPoly p,GeoLine l) {
		this(c,null,false,p,l);
	}
	
	public AlgoIntersectImplicitpolyParametric(Construction c,GeoImplicitPoly p,GeoFunction f) {
		this(c,null,false,p,f);
	}

	public AlgoIntersectImplicitpolyParametric(Construction c, String[] labels,
			boolean setLabels, GeoImplicitPoly p, GeoLine l) {
		super(c,labels,setLabels,p,l);
		this.p=p;
		this.l=l;
		compute();
	}

	public AlgoIntersectImplicitpolyParametric(Construction c, String[] labels,
			boolean setLabels, GeoImplicitPoly p, GeoFunction f) {
		super(c,labels,setLabels,p,f);
		this.p=p;
		this.f=f;
		compute();
	}

	@Override
	protected double getYValue(double t) {
		return ty.value(t);
	}
	

	@Override
	protected double getXValue(double t) {
		return tx.value(t);
	}

	@Override
	protected void compute() {

		if (!p.isDefined()){
			return;
		}
		
		double maxT;
		double minT;
		if (f!=null){
			if (!f.isDefined()){
				return;
			}
			
			if (!f.isPolynomialFunction(false)) {
				
				
				Kernel ker = cons.getKernel();
				
				ker.setSilentMode(true);
				
				GeoFunction paramEquation = new GeoFunction(cons, p, null, f);
				
				AlgoRoots algo = new AlgoRoots(cons, paramEquation, 
						new GeoNumeric(cons, f.getMinParameter()),
						new GeoNumeric(cons, f.getMaxParameter()));
				
				GeoPoint[] points = algo.getRootPoints();
				List<double[]> valPairs=new ArrayList<double[]>();
				for (int i=0;i<points.length;i++){
					double t = points[i].getX();
					valPairs.add(new double[]{t,f.evaluate(t)});
				}
				
				ker.setSilentMode(false);
				setPoints(valPairs);
				return;
			}
			tx=new PolynomialFunction(new double[]{0,1}); //x=t
			ty=new PolynomialFunction(f.getFunction().getNumericPolynomialDerivative(0).getCoeffs()); //y=f(t)
			maxT = f.getMaxParameter();
			minT = f.getMinParameter();
		}else if (l!=null){
			if (!l.isDefined()){
				points.adjustOutputSize(0);
				return;
			}
			//get parametrisation of line
			double startP[]=new double[2];
			l.getInhomPointOnLine(startP);
			tx=new PolynomialFunction(new double[]{startP[0],l.getY()}); //x=p1+t*r1
			ty=new PolynomialFunction(new double[]{startP[1],-l.getX()}); //y=p2+t*r2
			maxT = l.getMaxParameter();
			minT = l.getMinParameter();
			
			if (l.getParentAlgorithm() instanceof AlgoTangentImplicitpoly) {
				tangentPoints = ((AlgoTangentImplicitpoly)l.getParentAlgorithm()).getTangentPoints();
			}
		}else{
			return;
		}
		PolynomialFunction sum=null;
		PolynomialFunction zs=null;
		//Insert x and y (univariat)polynomials via the Horner-scheme
		double[][] coeff=p.getCoeff();
		if (coeff!=null)
			for (int i=coeff.length-1;i>=0;i--){
				zs=new PolynomialFunction(new double[]{coeff[i][coeff[i].length-1]});
				for (int j=coeff[i].length-2;j>=0;j--){
					zs=zs.multiply(ty).add(new PolynomialFunction(new double[]{coeff[i][j]}));//y*zs+coeff[i][j];
				}
				if (sum==null)
					sum=zs;
				else
					sum=sum.multiply(tx).add(zs);//sum*x+zs;
			}
		
		setRootsPolynomialWithinRange(sum,minT,maxT);
		mergeWithTangentPoints();
	}
	
	private void mergeWithTangentPoints() {
		
		if (tangentPoints == null
				|| tangentPoints.length == 0)
			return;
		
		
		
		//assumption: tangent points are far apart from each other such that dist(tangent1,tangent2) > epsilon.
		boolean addTangent[] = new boolean[tangentPoints.length];
		int orgSize = points.size();
		while (!points.getElement(orgSize-1).isDefined())
			--orgSize;
		
		int newSize = orgSize;
		double EPS2 = Kernel.EPSILON;  //TODO: have a better guess of the error
		
		for (int i = 0; i<tangentPoints.length; ++i) {
			if (tangentPoints[i].getIncidenceList()!=null
					&& tangentPoints[i].getIncidenceList().contains(l)) {
				addTangent[i] = true;
				for (int j = 0; j<orgSize; ++j) {
					if (points.getElement(j).distanceSqr(tangentPoints[i])<EPS2) {
						if (addTangent[i]) {
							points.getElement(j).setUndefined();
							--newSize;
						} else {
							addTangent[i] = false;
							points.getElement(i).setCoords(tangentPoints[j]);
						}
						
					}
				}
				if (addTangent[i])
					++newSize;
				
			} else {
				addTangent[i] = false;
			}
		}
		
		
		int definedCount = 0;
		for (int i=0; i<orgSize; ++i) {
			if (points.getElement(i).isDefined()) {
				if (definedCount!=i)
					points.getElement(definedCount).setCoords(points.getElement(i));
				++definedCount;
			}
		}
		
		points.adjustOutputSize(newSize);
		
		for (int i=0; i<tangentPoints.length; ++i) {
			if (addTangent[i]) {
				points.getElement(definedCount++).setCoords(tangentPoints[i]);
			}
		}
		
		if (setLabels)
			points.updateLabels();
		
	}

	public String getClassName() {
        return "AlgoIntersectImplicitpolyParametric";
    }
	
	public int getRelatedModeID() {
    	return EuclidianConstants.MODE_INTERSECT;
    }
    
	
	
}
