package org.geogebra.common.kernel.implicit;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.algos.Algos;
import org.geogebra.common.kernel.arithmetic.Equation;
import org.geogebra.common.kernel.arithmetic.EquationValue;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.ExpressionValue;
import org.geogebra.common.kernel.arithmetic.FunctionNVar;
import org.geogebra.common.kernel.arithmetic.FunctionalNVar;
import org.geogebra.common.kernel.arithmetic.Polynomial;
import org.geogebra.common.kernel.geos.GeoConic;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoLine;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.util.debug.Log;
/**
* Dependent implicit polynomial (or line / conic)
*/
public class AlgoDependentImplicitPoly extends AlgoElement {
private ExpressionValue[][] coeff; // input
private GeoElementND geoElement; // output (will be a implicitPoly, line or
// conic)
// private FunctionNVar[] dependentFromFunctions;
private Set<FunctionNVar> dependentFromFunctions;
private Equation equation;
/**
* Creates new implicit polynomial from equation. This algo may also return
* line or conic.
*
* @param c
* construction
* @param simplify
* whether we can evaluate the coefficients
* @param equ
* equation
* @param definition
* definition node
*/
public AlgoDependentImplicitPoly(Construction c, Equation equ,
ExpressionNode definition, boolean simplify) {
super(c, false);
equation = equ;
Polynomial lhs = equ.getNormalForm();
coeff = lhs.getCoeff();
try {
PolynomialUtils.checkNumericCoeff(coeff, simplify);
} catch (RuntimeException e) {
Log.error("RuntimeException " + e.getMessage());
}
c.addToConstructionList(this, false);
int deg = equ.preferredDegree();
if (!equ.mayBePolynomial()) {
deg = -1;
}
switch (deg) {
// linear equation -> LINE
case 1:
geoElement = new GeoLine(c);
break;
// quadratic equation -> CONIC
case 2:
geoElement = new GeoConic(c);
break;
default:
geoElement = kernel.newImplicitPoly(c);
}
geoElement.setDefinition(definition);
setInputOutput(); // for AlgoElement
compute(true);
}
@Override
public void compute() {
compute(false);
}
/**
* Replace output element with new one; needed if changes e.g. from line to
* conic
*
* @param newElem
* replacement element
*/
protected void replaceGeoElement(GeoElementND newElem) {
String label = geoElement.getLabelSimple();
newElem.setVisualStyle(geoElement.toGeoElement());
geoElement.doRemove();
geoElement = newElem;
setInputOutput();
geoElement.setLabel(label);
}
/**
* @return equation
*/
public Equation getEquation() {
return (Equation) geoElement.getDefinition().unwrap();
}
private void compute(boolean first) {
// Equation equation = (Equation) geoElement.getDefinition().unwrap();
if (!first) {
boolean recomputeCoeff = false;
if (equation != geoElement.getDefinition().unwrap()) {
equation = ((EquationValue) geoElement.getDefinition()
.evaluate(StringTemplate.defaultTemplate))
.getEquation();
equation.setFunctionDependent(true);
recomputeCoeff = true;
}
if (equation.isFunctionDependent()) {
// boolean functionChanged=false;
Set<FunctionNVar> functions = new HashSet<FunctionNVar>();
addAllFunctionalDescendents(this, functions,
new TreeSet<AlgoElement>());
if (!functions.equals(dependentFromFunctions)
|| equation.hasVariableDegree() || recomputeCoeff) {
equation.initEquation();
coeff = equation.getNormalForm().getCoeff();
dependentFromFunctions = functions;
}
} else if (equation.hasVariableDegree()) {
equation.initEquation();
coeff = equation.getNormalForm().getCoeff();
}
}
if (equation.getNormalForm() == null) {
equation.initEquation();
}
ExpressionNode def = geoElement.getDefinition();
// use the forced behavior here
int degree = equation.preferredDegree();
if (!equation.isPolynomial()) {
degree = 3;
}
switch (degree) {
// linear equation -> LINE
case 1:
if (geoElement instanceof GeoLine) {
setLine();
} else {
if (geoElement.hasChildren()) {
geoElement.setUndefined();
} else {
replaceGeoElement(new GeoLine(getConstruction()));
setLine();
}
}
break;
// quadratic equation -> CONIC
case 2:
if (geoElement instanceof GeoConic) {
setConic();
} else {
if (geoElement.hasChildren()) {
geoElement.setUndefined();
} else {
replaceGeoElement(new GeoConic(getConstruction()));
setConic();
}
}
break;
default:
if (geoElement instanceof GeoImplicit) {
((GeoImplicit) geoElement).setDefined();
((GeoImplicit) geoElement).fromEquation(equation, null);
if (equation.isPolynomial()) {
((GeoImplicit) geoElement).setCoeff(coeff);
} else {
((GeoImplicit) geoElement).setCoeff((double[][]) null);
}
} else {
if (geoElement.hasChildren()) {
geoElement.setUndefined();
} else {
replaceGeoElement(
kernel.newImplicitPoly(getConstruction()));
((GeoImplicit) geoElement).setDefined();
((GeoImplicit) geoElement).fromEquation(equation, null);
if (equation.isPolynomial()) {
((GeoImplicit) geoElement).setCoeff(coeff);
} else {
((GeoImplicit) geoElement).setCoeff((double[][]) null);
}
}
}
}
geoElement.setDefinition(def);
}
private void setLine() {
ExpressionValue[] expr = new ExpressionValue[3];
expr[2] = expr[1] = expr[0] = null;
if (coeff.length > 0) {
if (coeff[0].length > 0) {
expr[2] = coeff[0][0];
if (coeff[0].length > 1) {
expr[1] = coeff[0][1];
}
}
if (coeff.length > 1) {
if (coeff[1].length > 0) {
expr[0] = coeff[1][0];
}
}
}
Log.debug(expr[0]);
double[] dCoeff = new double[expr.length];
for (int i = 0; i < expr.length; i++) {
if (expr[i] != null) {
dCoeff[i] = expr[i].evaluateDouble();
} else {
dCoeff[i] = 0;
}
}
((GeoLine) geoElement).setCoords(dCoeff[0], dCoeff[1], dCoeff[2]);
}
private void setConic() {
ExpressionValue[] expr = new ExpressionValue[6];
for (int i = 0; i < 6; i++) {
expr[i] = null;
}
if (coeff.length > 0) {
if (coeff[0].length > 0) {
expr[5] = coeff[0][0];
if (coeff[0].length > 1) {
expr[4] = coeff[0][1];
if (coeff[0].length > 2) {
expr[2] = coeff[0][2];
}
}
}
if (coeff.length > 1) {
if (coeff[1].length > 0) {
expr[3] = coeff[1][0];
if (coeff[1].length > 1) {
expr[1] = coeff[1][1];
}
}
if (coeff.length > 2) {
if (coeff[2].length > 0) {
expr[0] = coeff[2][0];
}
}
}
}
double[] dCoeff = new double[expr.length];
for (int i = 0; i < expr.length; i++) {
if (expr[i] != null) {
dCoeff[i] = expr[i].evaluateDouble();
} else {
dCoeff[i] = 0;
}
}
((GeoConic) geoElement).setDefined();
((GeoConic) geoElement).setCoeffs(dCoeff);
}
/**
* Adds all functions from inputs of algo and its ancestors to destination
* set
*
* @param algo
* algo whose input functions need adding
* @param set
* destination set
* @param algos
* set of algorithms that were already processed
*/
protected void addAllFunctionalDescendents(AlgoElement algo,
Set<FunctionNVar> set, Set<AlgoElement> algos) {
GeoElement[] in = algo.getInput();
for (int i = 0; i < in.length; i++) {
AlgoElement p = in[i].getParentAlgorithm();
if (p != null && !algos.contains(p)) {
algos.add(p);
addAllFunctionalDescendents(p, set, algos);
}
if (in[i] instanceof FunctionalNVar) {
set.add(((FunctionalNVar) in[i]).getFunction());
}
}
}
@Override
protected void setInputOutput() {
if (input == null) {
input = geoElement.getDefinition().getGeoElementVariables();
dependentFromFunctions = new HashSet<FunctionNVar>();
addAllFunctionalDescendents(this, dependentFromFunctions,
new TreeSet<AlgoElement>());
}
if (getOutputLength() == 0) {
setOutputLength(1);
}
setOutput(0, geoElement.toGeoElement());
setDependencies(); // done by AlgoElement
}
@Override
public Algos getClassName() {
return Algos.Expression;
}
/**
* @return resulting poly, conic or line
*/
public GeoElement getGeo() {
return geoElement.toGeoElement();
// if (type==GeoElement.GEO_CLASS_IMPLICIT_POLY)
// return (GeoImplicitPoly)geoElement;
// else
// return null;
}
@Override
public final String toString(StringTemplate tpl) {
return geoElement.getDefinition().toString(tpl);
}
@Override
protected String toExpString(StringTemplate tpl) {
return geoElement.getLabel(tpl) + ": "
+ geoElement.getDefinition().toString(tpl);
}
}