/* 
 
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: PrimitiveLineObject.java
import java.awt.Checkbox;
import java.awt.Color;
import java.awt.Frame;
import java.awt.TextField;
import java.awt.event.FocusEvent;
import java.util.Enumeration;

import javax.swing.JPanel;

import rene.gui.Global;
import rene.gui.IconBar;
import rene.gui.MyLabel;
import rene.gui.TextFieldAction;
import rene.util.xml.XmlWriter;
import rene.gui.Global;
import rene.zirkel.ZirkelCanvas;
import rene.zirkel.construction.Construction;
import rene.zirkel.graphics.MyGraphics;
import rene.zirkel.graphics.MyGraphics13;
import rene.zirkel.structures.Coordinates;



public class PrimitiveLineObject extends ConstructionObject implements
PointonObject {

	protected double X1, Y1, DX, DY;
	protected PointObject P1;
	PointObject Dep[];
	int NDep;
	boolean Partial = false;

	public PrimitiveLineObject(final Construction c) {
		super(c);
		setColor(ColorIndex, SpecialColor);
	}

	public PrimitiveLineObject(final Construction c, final String Nme) {
		super(c, Nme);
		setColor(ColorIndex, SpecialColor);
	}

	public void setP1DXDY(final PointObject p, final double dx, final double dy) {
		P1 = p;
		DX = dx;
		DY = dy;
		X1 = P1.getX();
		Y1 = P1.getY();
	}

	@Override
	public void setDefaults() {
		setShowName(Global.getParameter("options.line.shownames", false));
		setShowValue(Global.getParameter("options.line.showvalues", false));
		setColor(Global.getParameter("options.line.color", 0), Global
				.getParameter("options.line.pcolor", (Color) null));
		setColorType(Global.getParameter("options.line.colortype", 0));
		setHidden(Cn.Hidden);
		setObtuse(Cn.Obtuse);
		setSolid(Cn.Solid);
		setLarge(Global.getParameter("options.line.large", false));
		setBold(Global.getParameter("options.line.bold", false));
	}

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

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

	double k1, k2;
	boolean k12valid = false;

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

		// compute middle of the screen:
		final double xm = (zc.minX() + zc.maxX()) / 2, ym = (zc.minY() + zc
				.maxY()) / 2;
		// compute distance from middle to line:
		final double d = (xm - X1) * DY - (ym - Y1) * DX;
		// compute point with minimal distance
		final double x = xm - d * DY, y = ym + d * DX;
		// compute size of the screen
		final double a = Math.max(zc.maxX() - zc.minX(), zc.maxY() - zc.minY());
		if (Math.abs(d) > a) {
			return;
		}
		// compute distance from closest point to source
		final double b = (x - X1) * DX + (y - Y1) * DY;
		// compute the two visible endpoints
		k1 = b - a;
		k2 = b + a;
		k12valid = true;
		if (Partial && Dep != null && !zc.showHidden()) {
			final double dd = (zc.maxX() - zc.minX()) / 20;
			double dmin = -dd, dmax = +dd;
			if (Dep != null) {
				for (int i = 0; i < NDep; i++) {
					if (!Dep[i].valid() || Dep[i].mustHide(zc)) {
						continue;
					}
					final double s = project(Dep[i].getX(), Dep[i].getY());
					if (s - dd < dmin) {
						dmin = s - dd;
					} else if (s + dd > dmax) {
						dmax = s + dd;
					}
				}
			}
			if (k1 < dmin) {
				k1 = dmin;
			}
			if (k2 > dmax) {
				k2 = dmax;
			}
		}
		final double c1 = zc.col(X1 + k1 * DX), c2 = zc.col(X1 + k2 * DX), r1 = zc
		.row(Y1 + k1 * DY), r2 = zc.row(Y1 + k2 * DY);
		// paint:
		if (isStrongSelected() && g instanceof MyGraphics13) {
			((MyGraphics13) g).drawMarkerLine(c1, r1, c2, r2);
		}

		g.setColor(this);

		if (tracked())
			zc.UniversalTrack.drawTrackLine(this, c1, r1, c2, r2);

		g.drawLine(c1, r1, c2, r2, this);
		final String s = getDisplayText();
		if (!s.equals("")) {
			g.setLabelColor(this);
			setFont(g);
			DisplaysText = true;
			if (KeepClose) {
				final double side = (YcOffset < 0) ? 1 : -1;
				drawLabel(g, s, zc, X1 + XcOffset * DX, Y1 + XcOffset * DY,
						side * DX, side * DY, 0, 0);
			} else {
				drawLabel(g, s, zc, x + a / 5 * DX, y + a / 5 * DY, DX, DY,
						XcOffset, YcOffset);
			}
		}
	}

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

	@Override
	public void setKeepClose(final double x, final double y) {
		KeepClose = true;
		XcOffset = (x - X1) * DX + (y - Y1) * DY;
		YcOffset = (x - X1) * DY - (y - Y1) * DX;
	}

	@Override
	public boolean nearto(final int c, final int r, final ZirkelCanvas zc) {
		if (!displays(zc)) {
			return false;
		}
		// compute point at c,r
		final double x = zc.x(c), y = zc.y(r);
		// compute distance from x,y
		final double d = (x - X1) * DY - (y - Y1) * DX;
		// scale in screen coordinates
		Value = Math.abs(zc.col(zc.minX() + d) - zc.col(zc.minX()));
		return Value < zc.selectionSize() * 2;
	}

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

	public static Coordinates intersect(final PrimitiveLineObject l1,
			final PrimitiveLineObject l2) // compute the intersection
	// coordinates of two lines
	{
		final double det = -l1.DX * l2.DY + l1.DY * l2.DX;
		if (Math.abs(det) < 1e-10) {
			return null;
		}
		final double a = (-(l2.X1 - l1.X1) * l2.DY + (l2.Y1 - l1.Y1) * l2.DX)
		/ det;
		return new Coordinates(l1.X1 + a * l1.DX, l1.Y1 + a * l1.DY);
	}

	public static Coordinates intersect(final PrimitiveLineObject l,
			final PrimitiveCircleObject c) // compute the intersection
	// coordinates of a line with a
	// circle
	{
		double x = c.getX(), y = c.getY();
		final double r = c.getR();
		final double d = (x - l.X1) * l.DY - (y - l.Y1) * l.DX;
		if (Math.abs(d) > r + 1e-10) {
			return null;
		}
		x -= d * l.DY;
		y += d * l.DX;
		double h = r * r - d * d;
		if (h > 0) {
			h = Math.sqrt(h);
		} else {
			h = 0;
		}
		return new Coordinates(x + h * l.DX, y + h * l.DY, x - h * l.DX, y - h
				* l.DY);
	}

	public static Coordinates intersect(final PrimitiveLineObject l,
			final QuadricObject q) {
		// compute the intersection coordinates of a line with a quadric
		// done with XCAS :
		// System.out.println("l.DX="+l.DX+" l.DY="+l.DY);
		final double M = -l.DY, N2 = l.DX, P = -(M * l.X1 + N2 * l.Y1);
		// System.out.println("M="+M+" N2="+N2+" P="+P);
		final double A = q.X[0], B = q.X[1], C = q.X[2], D = q.X[3], E = q.X[4], F = q.X[5];
		double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
		if (N2 != 0) {
			final double part1 = -2 * B * M * P - C * N2 * N2 + D * M * N2 + E
			* N2 * P;
			final double part2 = Math.abs(N2)
			* Math.sqrt(-2 * M * D * N2 * C + 4 * P * D * A * N2 + 4
					* P * M * B * C + 4 * E * M * N2 * F - 2 * E * P
					* N2 * C - 2 * E * P * M * D - 4 * M * M * B * F
					- 4 * P * P * A * B - 4 * A * N2 * N2 * F + N2 * N2
					* C * C + M * M * D * D + E * E * P * P);

			final double part3 = 2 * A * N2 * N2 + 2 * B * M * M + (-2 * E) * M
			* N2;
			x1 = (part1 + part2) / part3;
			if (Double.isNaN(x1)) {
				return null;
			}
			y1 = (-M * x1 - P) / N2;
			x2 = (part1 - part2) / part3;
			y2 = (-M * x2 - P) / N2;
			if (((x2 - x1) / l.DX) < 0) {
				return new Coordinates(x2, y2, x1, y1);
			}
		} else {
			x1 = -P / M;
			x2 = x1;
			final double part1 = -D * M * M + E * M * P;
			final double part2 = Math.abs(M)
			* Math.sqrt(4 * P * M * B * C - 2 * E * P * M * D - 4 * M
					* M * B * F - 4 * P * P * A * B + M * M * D * D + E
					* E * P * P);
			final double part3 = 2 * M * M * B;
			y1 = (part1 + part2) / part3;
			if (Double.isNaN(y1)) {
				return null;
			}
			y2 = (part1 - part2) / part3;
			if (((y2 - y1) / l.DY) < 0) {
				return new Coordinates(x2, y2, x1, y1);
			}
		}

		return new Coordinates(x1, y1, x2, y2);
	}

	public double getDX() {
		return DX;
	}

	public double getDY() {
		return DY;
	}

	@Override
	public double getX() {
		return X1;
	}

	@Override
	public double getY() {
		return Y1;
	}



	@Override
	public String getEquation() {
		double y = DX, x = -DY;
		double c = y * Y1 + x * X1;
		if (c < 0) {
			c = -c;
			x = -x;
			y = -y;
		}
		if (Math.abs(x) < 1e-10 && y < 0) {
			c = -c;
			x = -x;
			y = -y;
		} else if (Math.abs(y) < 1e-10 && x < 0) {
			c = -c;
			x = -x;
			y = -y;
		}
		final String s = helpDisplayValue(true, x, "x");
		return s + helpDisplayValue(s.equals(""), y, "y") + "="
		+ ((Math.abs(c) < 1e-10) ? "0" : helpDisplayNumber(true, c));
	}

	/**
	 * Test, if the projection of (x,y) to the line contains that point.
	 */
	public boolean contains(final double x, final double y) {
		return true;
	}

	@Override
	public void printArgs(final XmlWriter xml) {
		if (Partial) {
			xml.printArg("partial", "true");
		}
	}

	public double project(final double x, final double y) {
		return (x - X1) * DX + (y - Y1) * DY;
	}

	@Override
	public boolean equals(final ConstructionObject o) {
		if (!(o instanceof PrimitiveLineObject) || !o.valid()) {
			return false;
		}
		final PrimitiveLineObject l = (PrimitiveLineObject) o;
		return equals(DX * l.DY - DY * l.DX, 0)
		&& equals((X1 - l.X1) * DY - (Y1 - l.Y1) * DX, 0);
	}

	public PointObject getP1() {
		return P1;
	}

	public Enumeration points() {
		return depending();
	}

	@Override
	public boolean locallyLike(final ConstructionObject o) {
		if (!(o instanceof PrimitiveLineObject)) {
			return false;
		}
		return (equals(DX, ((PrimitiveLineObject) o).DX) && equals(DY,
				((PrimitiveLineObject) o).DY))
				|| (equals(-DX, ((PrimitiveLineObject) o).DX) && equals(-DY,
						((PrimitiveLineObject) o).DY));
	}

	@Override
	public boolean isPartial() {
		return Partial;
	}

	@Override
	public void setPartial(final boolean partial) {
		if (Partial == partial) {
			return;
		}
		Partial = partial;
		if (partial) {
			Dep = new PointObject[16];
			NDep = 0;
		} else {
			Dep = null;
		}
	}

	public void addDep(final PointObject p) {
		if (!Partial || Dep == null || NDep >= Dep.length) {
			return;
		}
		Dep[NDep++] = p;
	}

	@Override
	public void clearCircleDep() {
		NDep = 0;
	}

	@Override
	public int getDistance(final PointObject P) {
		final double h = project(P.getX(), P.getY());
		final double dx = P.getX() - (getX() + h * getDX());
		final double dy = P.getY() - (getY() + h * getDY());
		final double d = Math.sqrt(dx * dx + dy * dy);
		return (int) Math.round(d * Cn.getPixel());
	}

	public void project(final PointObject P) {
		final double h = project(P.getX(), P.getY());
		P.setXY(getX() + h * getDX(), getY() + h * getDY());
		P.setA(h);
	}

	public void project(final PointObject P, final double alpha) {
		P.setXY(getX() + alpha * getDX(), getY() + alpha * getDY());
	}

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

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