/* 
 
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;

// file: QuadricObject.java
import java.awt.Color;
import java.util.Enumeration;

import rene.gui.Global;
import rene.util.xml.XmlWriter;
import rene.gui.Global;
import rene.zirkel.ZirkelCanvas;
import rene.zirkel.construction.Construction;
import rene.zirkel.construction.Count;
import rene.zirkel.expression.Quartic;
import rene.zirkel.graphics.MyGraphics;
import rene.zirkel.graphics.PolygonDrawer;


public class QuadricObject extends ConstructionObject implements PointonObject,
MoveableObject {

	public PointObject P[];
	static Count N = new Count();
	public double X[];

	public QuadricObject(final Construction c, final PointObject p[]) {
		super(c);
		P = p;
		validate();
		updateText();
	}

	@Override
	public void setDefaults() {
		setShowName(Global.getParameter("options.circle.shownames", false));
		setShowValue(Global.getParameter("options.circle.showvalues", false));
		setColor(Global.getParameter("options.circle.color", 0), Global
				.getParameter("options.circle.pcolor", (Color) null));

		setColorType(Global.getParameter("options.circle.colortype", 0));
		setHidden(Cn.Hidden);
		setObtuse(Cn.Obtuse);
		setSolid(Cn.Solid);
		setLarge(Cn.LargeFont);
		setBold(Cn.BoldFont);
		setPartial(Cn.Partial);
	}

	@Override
	public void setTargetDefaults() {
		setShowName(Global.getParameter("options.circle.shownames", false));
		setShowValue(Global.getParameter("options.circle.showvalues", false));
		setColor(Global.getParameter("options.circle.color", 0), Global
				.getParameter("options.circle.pcolor", (Color) null));
		setColorType(Global.getParameter("options.circle.colortype", 0));
	}

	@Override
	public String getTag() {
		return "Quadric";
	}

	@Override
	public int getN() {
		return N.next();
	}

	@Override
	public void updateText() {
		try {
			final String Names[] = new String[P.length];
			for (int i = 0; i < P.length; i++) {
				Names[i] = P[i].getName();
			}
			setText(textAny(Global.name("text.quadric"), Names));
		} catch (final Exception e) {
		}
	}

	@Override
	public void validate() {
		for (int i = 0; i < P.length; i++) {
			if (!P[i].valid()) {
				Valid = false;
				return;
			}
		}
		Valid = true;

		// Baue Koeffizientenmatrix auf (x^2,y^2,x,y,xy,1):
		final double A[][] = new double[5][6];
		for (int i = 0; i < 5; i++) {
			final double x = P[i].getX(), y = P[i].getY();
			A[i][0] = x * x;
			A[i][1] = y * y;
			A[i][2] = x;
			A[i][3] = y;
			A[i][4] = x * y;
			A[i][5] = 1;
			double sum = 0;
			for (int j = 0; j < 6; j++) {
				sum += A[i][j] * A[i][j];
			}
			sum = Math.sqrt(sum);
			for (int j = 0; j < 6; j++) {
				A[i][j] /= sum;
			}
		}

		// Gauﬂverfahren, um auf untere Dreiecksmatrix zu kommen
		int r = 0;
		final int colindex[] = new int[6]; // Index der Stufe oder -1 (keine
		// Stufe)
		// Iteration ¸ber alle Spalten:
		for (int c = 0; c < 6; c++) {
			if (r >= 5) // Schema schon fertig
			{
				colindex[c] = -1;
				continue;
			}
			// Berechne Pivotelement mit spaltenweiser Maximumssuche
			double max = Math.abs(A[r][c]);
			int imax = r;
			for (int i = r + 1; i < 5; i++) {
				final double h = Math.abs(A[i][c]);
				if (h > max) {
					max = h;
					imax = i;
				}
			}
			if (max > 1e-13) { // Vertausche Zeilen:
				if (imax != r) {
					final double h[] = A[imax];
					A[imax] = A[r];
					A[r] = h;
				}
				// Mache restliche Spalte zu 0:
				for (int i = r + 1; i < 5; i++) {
					final double lambda = A[i][c] / A[r][c];
					for (int j = c + 1; j < 6; j++) {
						A[i][j] -= lambda * A[r][j];
					}
				}
				colindex[c] = r;
				r++;
			} else {
				colindex[c] = -1;
			}
		}
		// Berechne die x-Werte:
		X = new double[6];
		for (int j = 5; j >= 0; j--) {
			if (colindex[j] < 0) {
				X[j] = 1;
			} else {
				double h = 0;
				final int i = colindex[j];
				for (int k = j + 1; k < 6; k++) {
					h += A[i][k] * X[k];
				}
				X[j] = -h / A[i][j];
			}
		}
		// Normalisiere
		double sum = 0;
		for (int i = 0; i <= 5; i++) {
			sum += Math.abs(X[i]);
		}
		if (sum < 1e-10) {
			Valid = false;
		}
		for (int i = 0; i <= 5; i++) {
			X[i] /= sum;
		}
	}

	@Override
	public void paint(final MyGraphics g, final ZirkelCanvas zc) {
		if (!Valid || mustHide(zc)) {
			return;
		}

		g.setColor(this);
		// Draw the lower part of the quadrik (minus the root):
		final double start = zc.minX();
		double x = start;
		final double end = zc.maxX();
		final double h = zc.dx(1);
		boolean valid = false, ptext = false;
		double c0 = 0, r0 = 0;
		double ctext = 20, rtext = 20;

		final PolygonDrawer pd = new PolygonDrawer(g, this);

		// Draw the lower part of the quadric (plus the root):
		while (x <= end) {
			try {

				final double y = computeLower(x);
				final double c = zc.col(x), r = zc.row(y);
				if (valid) {
					pd.drawTo(c, r);
					if (!ptext && r0 - r > c - c0 && zc.isInside(x, y)) {
						ctext = c;
						rtext = r;
						ptext = true;
					}
				} else {

					pd.startPolygon(c, r);
				}
				c0 = c;
				r0 = r;
				valid = true;
			} catch (final RuntimeException e) {

				valid = false;
			}
			x += h;
		}
		pd.finishPolygon();
		// Draw the upper part of the quadric (plus the root):
		x = start - 2 * h;
		valid = false;
		while (x <= end + 2 * h) {
			try {
				final double y = computeUpper(x);
				final double c = zc.col(x), r = zc.row(y);
				if (valid) {
					pd.drawTo(c, r);
					// Try to find a position for the label:
					if (!ptext && r0 - r > c - c0 && zc.isInside(x, y)) {
						ctext = c;
						rtext = r;
						ptext = true;
					}
				} else // left edge of quadric, connect with lower part
				{
					try {
						final double y1 = computeLower(x);
						if (x >= start - h && x <= end + h) {
							g.drawLine(c, zc.row(y1), c, r, this);
						}
					} catch (final RuntimeException e) {
					}
					pd.startPolygon(c, r);
				}
				c0 = c;
				r0 = r;
				valid = true;
			} catch (final RuntimeException e) // no points in that range
			{
				if (valid) // we just left the right edge of the quadric
				{
					try {
						final double y1 = computeLower(x - h);
						if (x - h >= start - h && x - h <= end + h) {
							g.drawLine(c0, zc.row(y1), c0, r0, this);
						}
					} catch (final RuntimeException ex) {
					}
				}
				valid = false;
			}
			x += h;
		}
		pd.finishPolygon();
		final String s = getDisplayText();
		if (!s.equals("")) {
			g.setLabelColor(this);
			setFont(g);
			DisplaysText = true;
			TX1 = ctext + zc.col(XcOffset) - zc.col(0);
			TY1 = rtext + zc.row(YcOffset) - zc.row(0);
			drawLabel(g, s);
		}
	}

	static public String Tags[] = { "x^2", "y^2", "x", "y", "xy" };

	// public String getDisplayValue() {
	// String s="";
	// for (int i=0; i<5; i++) {
	// s=s+helpDisplayValue(i==0, -X[i], Tags[i]);
	// }
	// return s+"="+roundDisplay(X[5]);
	// }
	@Override
	public String getDisplayValue() {
		String s = "";
		s += "(" + Global.getLocaleNumber(-X[0], "length");
		s += "*";
		s += Tags[0] + ")";
		for (int i = 1; i < 5; i++) {
			s += "+";
			s += "(" + Global.getLocaleNumber(-X[i], "length");
			s += "*";
			s += Tags[i] + ")";

			// s=s+helpDisplayValue(i==0, -X[i], Tags[i]);
		}
		return s + "=" + Global.getLocaleNumber(X[5], "length");
		// return s+"="+roundDisplay(X[5]);
	}

	@Override
	public String getEquation() {
		return getDisplayValue();
	}

	@Override
	public boolean nearto(final int cc, final int rr, final ZirkelCanvas zc) {
		if (!displays(zc)) {
			return false;
		}
		final int size = (int) zc.selectionSize();
		final double start = zc.minX();
		double x = start;
		final double end = zc.maxX();
		final double h = zc.dx(1);
		while (x <= end) {
			try {
				final double y = computeUpper(x);
				final double c = zc.col(x), r = zc.row(y);
				if (Math.abs(cc - c) <= size * 3 / 2
						&& Math.abs(rr - r) <= size * 3 / 2) {
					return true;
				}
			} catch (final Exception e) {
			}
			try {
				final double y = computeLower(x);
				final double c = zc.col(x), r = zc.row(y);
				if (Math.abs(cc - c) <= size * 3 / 2
						&& Math.abs(rr - r) <= size * 3 / 2) {
					return true;
				}
			} catch (final Exception e) {
			}
			x += h;
		}
		return false;
	}



	public double computeUpper(final double x) {
		if (Math.abs(X[1]) > 1e-13) {
			final double p = (X[3] + x * X[4]) / X[1], q = (X[0] * x * x + X[2]
			                                                                 * x + X[5])
			                                                                 / X[1];
			final double h = p * p / 4 - q;
			if (h < 0) {
				throw new RuntimeException("");
			}
			return -p / 2 + Math.sqrt(h);
		} else {
			return -(X[0] * x * x + X[2] * x + X[5]) / (X[3] + X[4] * x);
		}
	}

	public double computeLower(final double x) {
		if (Math.abs(X[1]) > 1e-13) {
			final double p = (X[3] + x * X[4]) / X[1], q = (X[0] * x * x + X[2]
			                                                                 * x + X[5])
			                                                                 / X[1];
			final double h = p * p / 4 - q;
			if (h < 0) {
				throw new RuntimeException("");
			}
			return -p / 2 - Math.sqrt(h);
		} else {
			throw new RuntimeException("");
		}
	}

	@Override
	public void printArgs(final XmlWriter xml) {
		for (int i = 0; i < P.length; i++) {
			xml.printArg("point" + (i + 1), P[i].getName());
		}
	}

	@Override
	public Enumeration depending() {
		DL.reset();
		for (final PointObject element : P) {
			DL.add(element);
		}
		return DL.elements();
	}

	@Override
	public void translate() {
		for (int i = 0; i < P.length; i++) {
			P[i] = (PointObject) P[i].getTranslation();
		}
	}

	@Override
	public ConstructionObject copy(final double x, final double y) {
		try {
			final QuadricObject o = (QuadricObject) clone();
			setTranslation(o);
			o.P = new PointObject[P.length];
			for (int i = 0; i < P.length; i++) {
				o.P[i] = P[i];
			}
			o.translateConditionals();
			o.translate();
			o.setName();
			o.updateText();
			o.setBreak(false);
			o.setTarget(false);
			return o;
		} catch (final Exception e) {
			return null;
		}
	}

	@Override
	public boolean onlynearto(final int x, final int y, final ZirkelCanvas zc) {
		return false;
	}

	@Override
	public boolean equals(final ConstructionObject o) {
		if (!(o instanceof QuadricObject) || !o.valid()) {
			return false;
		}
		try {
			for (int i = 0; i < 6; i++) {
				if (!equals(X[i], ((QuadricObject) o).X[i])) {
					return false;
				}
			}
		} catch (final RuntimeException e) {
			return false;
		}
		return true;
	}

	@Override
	public boolean hasUnit() {
		return false;
	}

	public void keepBaricentricCoords(final PointObject P) {
		if (!P.isPointOn()) {
			return;
		}
		if (P.BarycentricCoordsInitialzed) {
			final PointObject AA = this.P[0];
			final PointObject BB = this.P[1];
			final PointObject CC = this.P[2];
			final double xa = AA.getX(), ya = AA.getY();
			final double xb = BB.getX(), yb = BB.getY();
			final double xc = CC.getX(), yc = CC.getY();

			final double xm = xa + P.Gx * (xb - xa) + P.Gy * (xc - xa);
			final double ym = ya + P.Gx * (yb - ya) + P.Gy * (yc - ya);

			P.move(xm, ym);
		} else {
			P.computeBarycentricCoords();
		}
	}

	@Override
	public int getDistance(final PointObject P) {
		final double a = X[0], b = X[1], c = X[2], d = X[3], e = X[4], r = X[5];
		final double xc = P.getX(), yc = P.getY();
		if (Math.abs(a * xc * xc + b * yc * yc + c * xc + d * yc + e * xc * yc
				+ r) < 1e-13) // close enough
		{
			return 0;
		}
		final double t[] = new double[5], s[] = new double[5];
		// Coefficients for fourth order polynomial for lambda (Lagrange factor)
		// Minimize (x-xc)^2+(y-yc)^2 with a*x^2+b*y^2+c*x+d*y+e*x*y+r=0
		// Computed with Maple
		t[0] = a * e * e * d * d - 4 * a * b * b * c * c + 4 * a * e * d * b
		* c - 4 * b * a * a * d * d + b * c * c * e * e - c
		* Math.pow(e, 3) * d + r * Math.pow(e, 4) - 8 * r * e * e * b
		* a + 16 * r * b * b * a * a;
		t[1] = 8 * b * b * c * c + 8 * a * a * d * d - 8 * e * d * b * c - 8
		* a * d * c * e + 8 * r * e * e * b + 8 * a * b * c * c + 8 * b
		* a * d * d + 8 * r * e * e * a - 32 * r * b * b * a - 32 * r
		* b * a * a;
		t[2] = 12 * e * d * c + 16 * r * b * b - 4 * b * d * d - 8 * r * e * e
		+ 4 * e * e * d * yc + 16 * b * b * xc * c - 16 * b * c * c
		- 16 * a * d * d - 4 * a * c * c + 16 * r * a * a + 16 * a * a
		* d * yc + 4 * xc * e * e * c - 8 * e * d * b * xc - 8 * e * yc
		* b * c - 8 * a * d * xc * e - 8 * a * yc * c * e + 16 * a * b
		* b * xc * xc - 4 * a * e * e * yc * yc + 16 * b * a * a * yc
		* yc - 4 * b * xc * xc * e * e + 4 * Math.pow(e, 3) * yc * xc
		+ 64 * r * b * a - 16 * a * b * xc * e * yc;
		t[3] = -32 * r * b + 8 * d * d + 8 * c * c + 16 * e * d * xc + 8 * e
		* e * yc * yc + 8 * xc * xc * e * e - 32 * r * a - 32 * b * xc
		* c + 16 * e * yc * c - 32 * a * d * yc - 32 * a * b * xc * xc
		- 32 * b * a * yc * yc;
		t[4] = 16 * b * yc * yc + 16 * d * yc + 16 * c * xc + 16 * xc * e * yc
		+ 16 * r + 16 * a * xc * xc;
		final int k = Quartic.solve(t, s);
		// System.out.println(k+"Solutions found.");
		double dmin = 1e30, xmin = xc, ymin = yc;
		for (int i = 0; i < k; i++) // Choose closest solution of Lagrange
			// equation
		{
			final double l = s[i];
			// Solve for x,y when lambda is known.
			// Computed with Maple
			final double px = -(-e * d + 4 * b * l * xc - 2 * e * l * yc - 4
					* l * l * xc + 2 * b * c - 2 * l * c)
					/ (-e * e + 4 * b * a - 4 * b * l - 4 * l * a + 4 * l * l);
			final double py = -(2 * a * d + 4 * a * l * yc - 2 * l * d - 4 * l
					* l * yc - 2 * l * xc * e - c * e)
					/ (-e * e + 4 * b * a - 4 * b * l - 4 * l * a + 4 * l * l);
			final double dist = (px - xc) * (px - xc) + (py - yc) * (py - yc);
			if (dist < dmin) {
				dmin = dist;
				xmin = px;
				ymin = py;
			}
		}

		final double dd = Math.sqrt((P.getX() - xmin) * (P.getX() - xmin)
				+ (P.getY() - ymin) * (P.getY() - ymin));
		return (int) Math.round(dd * Cn.getPixel());
	}

	public void project(final PointObject P) {
		keepBaricentricCoords(P);
		final double a = X[0], b = X[1], c = X[2], d = X[3], e = X[4], r = X[5];
		final double xc = P.getX(), yc = P.getY();
		if (Math.abs(a * xc * xc + b * yc * yc + c * xc + d * yc + e * xc * yc
				+ r) < 1e-13) // close enough
		{
			return;
		}
		final double t[] = new double[5], s[] = new double[5];
		// Coefficients for fourth order polynomial for lambda (Lagrange factor)
		// Minimize (x-xc)^2+(y-yc)^2 with a*x^2+b*y^2+c*x+d*y+e*x*y+r=0
		// Computed with Maple
		t[0] = a * e * e * d * d - 4 * a * b * b * c * c + 4 * a * e * d * b
		* c - 4 * b * a * a * d * d + b * c * c * e * e - c
		* Math.pow(e, 3) * d + r * Math.pow(e, 4) - 8 * r * e * e * b
		* a + 16 * r * b * b * a * a;
		t[1] = 8 * b * b * c * c + 8 * a * a * d * d - 8 * e * d * b * c - 8
		* a * d * c * e + 8 * r * e * e * b + 8 * a * b * c * c + 8 * b
		* a * d * d + 8 * r * e * e * a - 32 * r * b * b * a - 32 * r
		* b * a * a;
		t[2] = 12 * e * d * c + 16 * r * b * b - 4 * b * d * d - 8 * r * e * e
		+ 4 * e * e * d * yc + 16 * b * b * xc * c - 16 * b * c * c
		- 16 * a * d * d - 4 * a * c * c + 16 * r * a * a + 16 * a * a
		* d * yc + 4 * xc * e * e * c - 8 * e * d * b * xc - 8 * e * yc
		* b * c - 8 * a * d * xc * e - 8 * a * yc * c * e + 16 * a * b
		* b * xc * xc - 4 * a * e * e * yc * yc + 16 * b * a * a * yc
		* yc - 4 * b * xc * xc * e * e + 4 * Math.pow(e, 3) * yc * xc
		+ 64 * r * b * a - 16 * a * b * xc * e * yc;
		t[3] = -32 * r * b + 8 * d * d + 8 * c * c + 16 * e * d * xc + 8 * e
		* e * yc * yc + 8 * xc * xc * e * e - 32 * r * a - 32 * b * xc
		* c + 16 * e * yc * c - 32 * a * d * yc - 32 * a * b * xc * xc
		- 32 * b * a * yc * yc;
		t[4] = 16 * b * yc * yc + 16 * d * yc + 16 * c * xc + 16 * xc * e * yc
		+ 16 * r + 16 * a * xc * xc;
		final int k = Quartic.solve(t, s);
		// System.out.println(k+"Solutions found.");
		double dmin = 1e30, xmin = xc, ymin = yc;
		for (int i = 0; i < k; i++) // Choose closest solution of Lagrange
			// equation
		{
			final double l = s[i];
			// Solve for x,y when lambda is known.
			// Computed with Maple
			final double px = -(-e * d + 4 * b * l * xc - 2 * e * l * yc - 4
					* l * l * xc + 2 * b * c - 2 * l * c)
					/ (-e * e + 4 * b * a - 4 * b * l - 4 * l * a + 4 * l * l);
			final double py = -(2 * a * d + 4 * a * l * yc - 2 * l * d - 4 * l
					* l * yc - 2 * l * xc * e - c * e)
					/ (-e * e + 4 * b * a - 4 * b * l - 4 * l * a + 4 * l * l);
			final double dist = (px - xc) * (px - xc) + (py - yc) * (py - yc);
			if (dist < dmin) {
				dmin = dist;
				xmin = px;
				ymin = py;
			}
		}
		P.move(xmin, ymin);
	}

	public void project(final PointObject P, final double alpha) {
		project(P);
	}

	public void dragTo(final double x, final double y) {
		for (int i = 0; i < 5; i++) {
			P[i].move(xd[i] + (x - x1), yd[i] + (y - y1));
		}
	}

	@Override
	public void move(final double x, final double y) {
	}

	public boolean moveable() {
		for (int i = 0; i < 5; i++) {
			if (!P[i].moveable()) {
				return false;
			}
		}
		return true;
	}

	double xd[], yd[], x1, y1;

	public void startDrag(final double x, final double y) {
		if (xd == null) {
			xd = new double[5];
			yd = new double[5];
		}
		for (int i = 0; i < 5; i++) {
			xd[i] = P[i].getX();
			yd[i] = P[i].getY();
		}
		x1 = x;
		y1 = y;
	}

	public double getOldX() {
		return 0;
	}

	public double getOldY() {
		return 0;
	}

	@Override
	public void snap(final ZirkelCanvas zc) {
		if (moveable()) {
			for (int i = 0; i < 5; i++) {
				P[i].snap(zc);
			}
		}
	}

	public boolean canInteresectWith(final ConstructionObject o) {
		return true;
	}

	public void repulse(final PointObject P) {
		project(P);
	}
}
