/* 
 
Copyright 2006 Rene Grothmann, modified by Eric Hakenholz

This file is part of C.a.R. software.

    C.a.R. is a 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, version 3 of the License.

    C.a.R. 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, see <http://www.gnu.org/licenses/>.
 
 */
 
 
 package rene.zirkel.objects;

import rene.util.xml.XmlWriter;
import rene.zirkel.construction.Construction;
import rene.zirkel.expression.Secant;

/**
 * An intersection between two object that can bind points on them. The
 * intersection is computed by projecting the point to both objects and
 * predicting the intersection point.
 * 
 * @author Rene
 */
public class PointonObjectIntersectionObject extends IntersectionObject {

	public double Eps = 1e-5;
	private boolean xAxisFunctionIntersection = false,
	yAxisFunctionIntersection = false;
	private FunctionObject F = null;

	public PointonObjectIntersectionObject(final Construction c,
			final ConstructionObject p1, final ConstructionObject p2) {
		super(c, p1, p2);
		if ((p1 instanceof AxisObject) && (p2 instanceof FunctionObject)) {

			F = (FunctionObject) p2;
			if (F.getEX().toString().equals("x")) {
				final AxisObject ax = ((AxisObject) p1);
				if (ax.DX == 1) {
					xAxisFunctionIntersection = true;
				} else {
					yAxisFunctionIntersection = true;
				}
			}

		} else if ((p2 instanceof AxisObject) && (p1 instanceof FunctionObject)) {
			F = (FunctionObject) p1;
			if (F.getEX().toString().equals("x")) {
				final AxisObject ax = ((AxisObject) p2);
				if (ax.DX == 1) {
					xAxisFunctionIntersection = true;
				} else {
					yAxisFunctionIntersection = true;
				}
			}
		}

	}

	@Override
	public void validate(final double x, final double y) {
		setXY(x, y);
		validate();
	}

	@Override
	public void printArgs(final XmlWriter xml) {
		xml.printArg("first", P1.getName());
		xml.printArg("second", P2.getName());
		xml.printArg("x", "" + getX());
		xml.printArg("y", "" + getY());
		if (getAway() != null) {
			if (StayAway) {
				xml.printArg("awayfrom", getAway().getName());
			} else {
				xml.printArg("closeto", getAway().getName());
			}
		}
		printType(xml);
		if (!Restricted) {
			xml.printArg("valid", "true");
		}
	}

	/**
	 * Troublesome function to intersect two objects. This is done by projecting
	 * the intersection point to each of the objects in turn.
	 * 
	 * To speed up the convergence, a line intersection is computed, where the
	 * lines approximate the objects.
	 */
	@Override
	public void validate() {
//        if ((P1==null)||(P2==null)) {
//            System.out.println(this.getName());
//        }


		if (!P1.valid() || !P2.valid()) {
			Valid = false;
		} else {
			Valid = true;
		}
		if (!Valid) {
			return;
		}

		if (yAxisFunctionIntersection) {
			try {
				setXY(0, F.evaluateF(0));
				return;
			} catch (final Exception ex) {
			}
		}

		if (xAxisFunctionIntersection) {
			try {

				double xn = 0;
				for (int i = 0; i < 5; i++) {
					double distold = projectOnce();
					if (!Valid) {
						return;
					}
					final double dist1 = projectOnce();
					if (dist1 >= distold && dist1 < Eps) {
						break;
					}
					distold = dist1;
					double a = x1 - x;
					double b = y1 - y;
					double s = Math.max(Math.abs(a), Math.abs(b));
					if (Math.abs(s) > 1e-13) {
						a /= s;
					}
					b /= s;
					double c = X - x1;
					double d = Y - y1;
					s = Math.max(Math.abs(c), Math.abs(d));
					if (Math.abs(s) > 1e-13) {
						c /= s;
					}
					d /= s;
					final double e = a * x1 + b * y1;
					final double f = c * X + d * Y;
					final double det = a * d - c * b;
					if (Math.abs(det) > 1e-13) {
						xn = (e * d - f * b) / det;
						final double dist2 = projectOnce();
						if (dist2 < dist1) {
							distold = dist2;
						}
					}
				}

				setXY(Secant.compute(F, xn - 1e-1, xn + 1e-1, 1e-10), 0);

				return;
			} catch (final Exception ex) {
			}
		}

		double distold = projectOnce();
		if (!Valid) {
			return;
		}
		for (int i = 0; i < 10; i++) {
			final double dist1 = projectOnce();
			if (dist1 >= distold && dist1 < Eps) {
				return;
			}
			distold = dist1;
			double a = (x1 - x), b = (y1 - y);
			double s = Math.max(Math.abs(a), Math.abs(b));
			if (Math.abs(s) > 1e-13) {
				a /= s;
			}
			b /= s;
			double c = (X - x1), d = (Y - y1);
			s = Math.max(Math.abs(c), Math.abs(d));
			if (Math.abs(s) > 1e-13) {
				c /= s;
			}
			d /= s;
			final double e = a * x1 + b * y1, f = c * X + d * Y;
			final double det = a * d - c * b;
			if (Math.abs(det) > 1e-13) {
				final double xn = (e * d - f * b) / det;
				final double yn = (a * f - c * e) / det;
				final double xold = X, yold = Y;
				setXY(xn, yn);
				final double dist2 = projectOnce();
				if (dist2 > dist1) // interpolation does not work
				{
					setXY(xold, yold);
				} else {
					distold = dist2;
				}
			}

		}

		Valid = false;
	}

	double x, y, x1, y1;

	public double projectOnce() {
		x = X;
		y = Y;
		// System.out.println("Before: "+X+" "+Y);
		((PointonObject) P1).project(this);
		// System.out.println("Projected to "+P1.getName()+": "+X+" "+Y);
		final double dist = Math.max(Math.abs(X - x), Math.abs(Y - y));
		x1 = X;
		y1 = Y;
		((PointonObject) P2).project(this);
		// System.out.println("Projected to "+P2.getName()+": "+X+" "+Y);
		final double dist1 = Math.max(Math.abs(X - x1), Math.abs(Y - y1));
		return Math.max(dist, dist1);
	}

	@Override
	public boolean moveable() {
		return true;
	}
}
