/**
 *    Copyright 2011 Peter Murray-Rust et. al.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.xmlcml.cml.element;

import nu.xom.Element;
import org.xmlcml.cml.base.CMLElement;
import org.xmlcml.euclid.Line3;
import org.xmlcml.euclid.Point3;
import org.xmlcml.euclid.Real;
import org.xmlcml.euclid.Util;
import org.xmlcml.euclid.Vector3;

/**
 * user-modifiable class supporting line3. * autogenerated from schema use as a
 * shell which can be edited
 *
 */
public class CMLLine3 extends AbstractLine3 {

	/** namespaced element name.*/
	public final static String NS = C_E+TAG;

   /**
     * start of point in array.
     *
     */
    public final static int POINT = 3;

    /**
     * start of vector in array.
     *
     */
    public final static int VECTOR = 0;

    /**
     * default. has NO default values (point and vector are null)
     */
    public CMLLine3() {
    }

    /**
     * constructor.
     *
     * @param old
     */
    public CMLLine3(CMLLine3 old) {
        super((AbstractLine3) old);

    }

    /**
     * copy node .
     *
     * @return Node
     */
    public Element copy() {
        return new CMLLine3(this);

    }

    /**
     * create new instance in context of parent, overridable by subclasses.
     *
     * @param parent
     *            parent of element to be constructed (ignored by default)
     * @return CMLLine3
     */
    public CMLElement makeElementInContext(Element parent) {
        return new CMLLine3();

    }

    /**
     * check line is OK. must have 3 double components
     *
     * @param parent
     *            element
     * @throws RuntimeException
     *             parsing error
     */
    public void finishMakingElement(Element parent) throws RuntimeException {
        if (this.getPoint3Attribute() == null) {
            throw new RuntimeException("point3 is now mandatory");
        }
        if (this.getVector3Attribute() == null) {
            throw new RuntimeException("vector3 is now mandatory");
        }
    }

    // =========================== additional constructors
    // ========================

    /**
     * formed from Euclid line.
     *
     * @param l
     */
    public CMLLine3(Line3 l) {
        this();
        this.setPoint3(l.getPoint().getArray());
        this.setVector3(l.getVector().getArray());
    }

    /**
     * construct from point and vector. takes doc from p the line will not
     * necessarily retain the exact point and the vector need not be normalized
     * p and vector are copied
     *
     * @param p
     *            a point on the line
     * @param v
     *            non-zero vector through the point
     * @exception RuntimeException
     *                zero length vector
     */
    public CMLLine3(CMLPoint3 p, CMLVector3 v) throws RuntimeException {
        if (v.isZero()) {
            throw new RuntimeException("zero vector");
        }
        v.normalize();
        setPoint3(p.getXYZ3());
        setVector3(v.getXYZ3());
    }

    /**
     * construct a line from two Point3s. takes doc from p1 the line will not
     * necessarily retain the exact points
     *
     * @param p1
     *            a point on the line
     * @param p2
     *            another point on the line
     * @exception RuntimeException
     *                points are coincident
     */
    public CMLLine3(CMLPoint3 p1, CMLPoint3 p2) throws RuntimeException {
        CMLVector3 v = p1.subtract(p2);
        if (v.isZero()) {
            throw new RuntimeException("coincident points");
        }
        this.setPoint3(p1.getXYZ3());
        this.setVector3(v.getXYZ3());
    }

    // ====================== housekeeping methods =====================

    /**
     * get euclid primitive.
     *
     * @return line
     * @exception RuntimeException
     */
    public Line3 getEuclidLine3() throws RuntimeException {
        return new Line3(new Point3(this.getPoint3()), new Vector3(this
                    .getVector3()));
    }

    // ====================== subsidiary accessors =====================

    /**
     * set vector. will normalize copy of vector
     *
     * @param v
     *            the vector
     */
    public void setVector3(CMLVector3 v) {
        if (v.isZero()) {
            throw new RuntimeException("Cannot make line with zero vector");
        }
        CMLVector3 vv = new CMLVector3(v);
        vv.normalize();
        super.setVector3(vv.getXMLContent());
    }

    // ====================== functionality =====================

    /**
     * Checks to see if the two lines are represented by the same vector and point
     * Line3.isEqualTo(Line3 line) will check to see if the lines are equivalent.
     *
     * @param l2
     *            CMLLine3 to compare
     * @return equals
     */
    public boolean isEqualTo(CMLLine3 l2) {
        return Util.isEqual(this.getPoint3(), l2.getPoint3(), EPS)
                && Util.isEqual(this.getVector3(), l2.getVector3(), EPS);
    }

    /**
     * form coincident antiparallel line.
     *
     * @return antiparallel line
     */
    public CMLLine3 subtract() {
        double[] vv = this.getVector3();
        for (int i = 0; i < vv.length; i++) {
            vv[i] = -vv[i];
        }
        this.setVector3(vv);
        return this;
    }

    /**
     * get transformed line. does not alter this
     *
     * @param t
     *            transform
     * @return transformed line
     */
    public CMLLine3 transform(CMLTransform3 t) {
        Line3 linee = getEuclidLine3();
        Line3 l = linee.transform(t.getEuclidTransform3());
        return new CMLLine3(l);
    }

    /**
     * are two lines parallel. (not antiparallel) does not test coincidence
     *
     * @param l2
     *            line to compare
     * @return true if parallel
     */
    public boolean isParallelTo(CMLLine3 l2) {
        double[] v = this.getVector3();
        double[] v2 = l2.getVector3();
        return Util.isEqual(v, v2, EPS);
    }

    /**
     * are two lines antiparallel. (not parallel) does not test coincidence
     *
     * @param l2
     *            line to compare
     * @return true if antiparallel
     */
    public boolean isAntiparallelTo(CMLLine3 l2) {
        double[] v = this.getVector3();
        Vector3 vv = new Vector3(v);
        vv = vv.multiplyBy(-1);
        return Util.isEqual(vv.getArray(), v, EPS);
    }

    /**
     * is a point on a line. tests for Real.isZero() distance from line
     *
     * @param p
     *            point
     * @return true if within Real.isZero()
     */
    public boolean containsPoint(CMLPoint3 p) {
        double d = p.distanceFromLine(this);
        return (Real.isZero(d, Real.getEpsilon()));
    }

    /**
     * point on line closest to another point.
     *
     * @param p2
     *            reference point
     * @return point on line closest to p2
     */
    public CMLPoint3 getClosestPointTo(CMLPoint3 p2) {
        Line3 leucl3 = this.getEuclidLine3();
        Point3 pp = leucl3.getClosestPointTo(p2.getEuclidPoint3());
        return (pp == null) ? null : new CMLPoint3(pp.getArray());
    }

    /**
     * distance of a point from a line
     *
     * @param p
     *            reference point
     * @return distance from line
     */
    public double getDistanceFromPoint(CMLPoint3 p) {
        Line3 leucl3 = this.getEuclidLine3();
        return leucl3.getDistanceFromPoint(p.getEuclidPoint3());
    }

    /**
     * point of intersection of line and plane calls
     * Plane3.getIntersectionWith(CMLPoint3)
     *
     * @param pl
     *            plane intersecting line
     * @return point (null if line parallel to plane)
     */
    public CMLPoint3 getIntersectionWith(CMLPlane3 pl) {
        Line3 leucl3 = this.getEuclidLine3();
        Point3 pp = leucl3.getIntersectionWith(pl.getEuclidPlane3());
        return (pp == null) ? null : new CMLPoint3(pp.getArray());
    }

    /**
     * get string.
     *
     * @return the string
     */
    public String getString() {
        String s = this.getEuclidLine3().toString();
        return s;
    }
}
