package Geometry;
import java.io.Serializable;

/**
 * Class to represent vectors in a variable number of dimensions.
 * Written by Chris Studholme
 * Licenced for redistribution under the GNU General Public Licence
 */
public final class NVector implements Serializable {

    /**
     * Individual components of the vector.
     */
    public double[] component;
    
    /**
     * Create a vector of dimension n.
     * @param n number of dimensions
     */
    public NVector(int n) {
	component = new double[n];
    }
    /**
     * Create a vector of dimension 3.
     */
    public NVector() {
	this(3);
    }
    /**
     * Create a vector of dimension 2.
     * @param x first component
     * @param y second component
     */
    public NVector(double x, double y) {
	this(2);
	component[0]=x;
	component[1]=y;
    }
    /**
     * Create a vector of dimension 3.
     * @param x first component
     * @param y second component
     * @param z thrid component
     */
    public NVector(double x, double y, double z) {
	this(3);
	component[0]=x;
	component[1]=y;
	component[2]=z;
    }
    /**
     * Create a vector that is an exact copy of some other vector.
     * @param other vector to copy
     */
    public NVector(NVector other) {
	this(other.component.length);
	System.arraycopy(other.component,0,component,0,
			 other.component.length);
    }

    /**
     * Compare this vector to another.  Returns true if the two vectors
     * are of the same dimension and the compoenents agree.
     * @param other other vector
     * @return true if equal
     */
    public boolean equals(Object other) {
	if (!(other instanceof NVector))
	    return false;
	if (component.length!=((NVector)other).component.length)
	    return false;
	for (int i=0; i<component.length; ++i)
	    if (component[i]!=((NVector)other).component[i])
		return false;
	return true;
    }

    /**
     * Return string representation of vector in form "(?,?,...,?)".
     * @return String object
     */
    public String toString() {
	StringBuffer buf = new StringBuffer("("+component[0]);
	for (int i=1; i<component.length; ++i)
	    buf.append(',').append(component[i]);
	return buf.append(')').toString();
    }

    /**
     * Compute sum of the squares of the components.
     * @return square of length
     */
    public double squareLength() {
	return innerProduct(this,this);
    }

    /**
     * Compute sum of the squares of the components.
     * @return square of length
     */
    public double lengthSquare() {
	return innerProduct(this,this);
    }

    /**
     * Compute l2 length of vector.
     * @return length of vector
     */
    public double length() {
	return Math.sqrt(innerProduct(this,this));
    }

    /**
     * Add other to this and return new NVector as result.
     * @param other summand
     * @return new NVector representing result
     */
    public NVector add(NVector other) {
	return muladd(this,other,1,1);
    }

    /**
     * Add other to this directly (without creating a new object).
     * @param other summand
     * @return this
     */
    public NVector addDirect(NVector other) {
	for (int i=0; i<component.length; ++i)
	    component[i]+=other.component[i];
	return this;
    }

    /**
     * Subtract other from this and return new NVector as result.
     * @param other vector to subtract
     * @return new NVector representing result
     */
    public NVector subtract(NVector other) {
	return muladd(this,other,1,-1);
    }

    /**
     * Subtract other from this directly (without creating a new object).
     * @param other vector to subtract
     * @return this
     */
    public NVector subtractDirect(NVector other) {
	for (int i=0; i<component.length; ++i)
	    component[i]-=other.component[i];
	return this;
    }

    /**
     * Scale other, add it to this, and return a new NVector with the result.
     * Note that neither other nor this is not modified.
     * @param other vector to add
     * @param scale factor to scale other by before adding
     * @return new NVector representing result
     */
    public NVector scaleAndAdd(NVector other, double scale) {
	return muladd(this,other,1,scale);
    }

    /**
     * Scale this by factor and return a new NVector with the result.
     * @param factor factor to scale by
     * @return new NVector representing result
     */
    public NVector scale(double factor) {
	return scale(this,factor);
    }

    /**
     * Scale this by factor by modifying this directly.
     * @param factor factor to scale by
     * @return this
     */
    public NVector scaleDirect(double s) {
	for (int i=0; i<component.length; ++i)
	    component[i]*=s;
	return this;
    }

    /**
     * Compute cross product of this and other and return new NVector with
     * the result.  Note that this call only works with 3 dimensional vectors.
     * @param other other vector
     * @return new NVector representing result
     */
    public NVector crossProduct(NVector other) {
	return crossProduct(this,other);
    }

    /**
     * Compute inner product of this and other.
     * @param other other vector
     * @return inner product
     */
    public double innerProduct(NVector other) {
	return innerProduct(this,other);
    }

    /**
     * Return a new NVector object that is this vector normalized to unit
     * length (by the l2 measure).  If this is a vector of length 0, then
     * null is returned.
     * @return new NVector with result (possibly null)
     */
    public NVector normalize() {
	return normalize(this);
    }

    /**
     * Scale this to unit length (by the l2 measure).  If this is a zero
     * length vector it is unchanged and null is returned.
     * @return this (or null if this is zero length)
     */
    public NVector normalizeDirect() {
	double l = lengthSquare();
	if (l==0) return null;
	if (l==1) return this;
	return scaleDirect(1/Math.sqrt(l));
    }

    /**
     * Compute angle between this vector and positive y axis when projected
     * to xy plane (for vectors of dimension greater than 2).  The angle
     * increases clockwise and will be in the range -PI/2 to 3*PI/2.
     * @return angle in radians
     */
    public double planarAngle() {
	if (component[1]>0)
	    return Math.atan(component[0]/component[1]);
	else if (component[1]<0)
	    return Math.PI+Math.atan(component[0]/component[1]);
	else if (component[0]>0)
	    return Math.PI/2;
	else if (component[0]<0)
	    return -Math.PI/2;
	return Double.NaN;
    }

    /**
     * Make this orthogonal to other and possibly normalized to unit length.
     * If the vectors are parallel to each other, this will become zero
     * length and null is returned.  If either vector (or both) is zero
     * length initially, this is not changed and this may be returned.  
     * If this is initially zero length or becomes zero length and
     * normalization is requested, null is returned.
     * @param other vector we want this to be orthogonal to
     * @param normalize should this be normalized to unit length
     * @return usually this but possibly null
     */
    public NVector makeOrthogonalTo(NVector other, boolean normalize) {
	double extra = innerProduct(other);
	if (extra!=0) {
	    subtractDirect(other.scale(extra));
	    if (lengthSquare()==0)
		return null;
	}
	return normalize ? normalizeDirect() : this;
    }
    /**
     * Make this orthogonal to other.  Returns null if neither this nor
     * other is zero length and they are parallel to each other.
     * @param other vector we want this to be orthogonal to
     * @return usually this but possibly null
     */
    public NVector makeOrthogonalTo(NVector other) {
	return makeOrthogonalTo(other,false);
    }

    /**********************************************************************/
    
    /**
     * Compute factor*a.  Does not change a.
     * @param a a vector
     * @param factor scalar multiple
     * @return new NVector representing result
     */
    public static NVector scale(NVector a, double factor) {
	NVector result = new NVector(a.component.length);
	for (int i=0; i<a.component.length; ++i)
	    result.component[i]=factor*a.component[i];
	return result;
    }

    /**
     * Compute sa*a + sb*b.  Does not change either a or b.
     * @param a a vector
     * @param b another vector
     * @param sa factor to multiply a by
     * @param sb factor to multiply b by
     * @return new NVector representing result
     */
    public static NVector muladd(NVector a, NVector b,
				 double sa, double sb) {
	NVector result = new NVector(a.component.length);
	for (int i=0; i<a.component.length; ++i)
	    result.component[i]=sa*a.component[i]+sb*b.component[i];
	return result;
    }

    /**
     * Compute a + b.  Does not change either a or b.
     * @param a a vector
     * @param b another vector
     * @return new NVector representing result
     */
    public static NVector add(NVector a, NVector b) {
	return muladd(a,b,1,1);
    }

    /**
     * Compute a - b.  Does not change either a or b.
     * @param a a vector
     * @param b another vector
     * @return new NVector representing result
     */
    public static NVector subtract(NVector a, NVector b) {
	return muladd(a,b,1,-1);
    }

    /**
     * Compute cross product a x b.  Does not change either a or b.  This
     * method only works for 3 dimensional vectors.
     * @param a a vector
     * @param b another vector
     * @return new NVector representing result
     */
    public static NVector crossProduct(NVector a, NVector b) {
	if ((a.component.length!=3)||(b.component.length!=3))
	    throw new IllegalArgumentException("Invalid dimension");
	NVector result = new NVector(a.component.length);
	result.component[0] = 
	    a.component[1]*b.component[2]-
	    a.component[2]*b.component[1];
	result.component[1]=
	    a.component[2]*b.component[0]-
	    a.component[0]*b.component[2];
	result.component[2]=
	    a.component[0]*b.component[1]-
	    a.component[1]*b.component[0];
	return result;
    }    

    /**
     * Compute inner product of a and b.  
     * @param a a vector
     * @param b another vector
     * @return inner product
     */
    public static double innerProduct(NVector a, NVector b) {
	double result=0;
	for (int i=0; i<a.component.length; ++i)
	    result+=a.component[i]*b.component[i];
	return result;
    }

    /**
     * Normalize vector to unit length (by l2 metric).  The vector is not
     * changed.  If the vector is zero length, null is returned.
     * @param a a vector
     * @return new NVector representing result (or null if a is zero length)
     */
    public static NVector normalize(NVector a) {
	double l = a.lengthSquare();
	if (l==0) return null;
	if (l==1) return a;
	return a.scale(1/Math.sqrt(l));
    }


}
