package math.matrix;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import math.Maths;
import math.geometry.Point;
import thisandthat.WObject;
import exception.IllegalDimensionException;

/**
 * Vector of real numbers, v<sub>i</sub>&isin;&#x211D;.
 * @author Daniel Weschke
 */
public class Vector extends WObject implements Cloneable, Serializable{
	
	/**
	 * UID
	 */
	private static final long serialVersionUID = 7909642082289701909L;

	protected List<Double> vector;
//	protected double[] vector;
	
	public Vector(){
	}
	
	/**
	 * Create zero vector
	 * @param n
	 */
	public Vector(int n){
		int i;
//		vector = new double[n];
		vector = new ArrayList<Double>();
		for(i=0; i<n; i++)
			add(0);
	}
	
	/**
	 * Create vector with given x value
	 * @param x
	 */
	public Vector(double x){
//		vector = new double[]{x};
		this(1);
		set(0, x);
	}
	
	/**
	 * Create vector with given x and y value
	 * @param x
	 * @param y
	 */
	public Vector(double x, double y){
//		vector = new double[]{x, y};
		this(2);
		set(0, x);
		set(1, y);
	}
	
	/**
	 * Create vector with given x, y and z value
	 * @param x
	 * @param y
	 * @param z
	 */
	public Vector(double x, double y, double z){
//		vector = new double[]{x, y, z};
		this(3);
		set(0, x);
		set(1, y);
		set(2, z);
	}
	
	private void add(double s){
		vector.add(s);
	}

	/**
	 * Create vector with given array
	 * @param data array
	 */
	public Vector(double... data){
		this(data.length);
		int i;
		int n = data.length;
//		vector = new double[n];
		for(i=0; i<n; i++)
			set(i, data[i]);
	}
	
	/**
	 * Create vector with given array (integers)
	 * @param v array
	 */
	public Vector(int[] v){
		this(v.length);
		int i;
		int n = v.length;
//		vector = new double[n];
		for(i=0; i<n; i++)
			set(i, (v[i]));
	}

	/**
	 * Create vector with given array
	 * @param data array
	 */
	public Vector(List<Double> data){
		this(data.size());
		int i;
		int n = data.size();
		for(i=0; i<n; i++)
			set(i, data.get(i));
	}
	
	public Vector(Point p){
		this(p.get());
	}
	
	/**
	 * Copy vector
	 * @param v vector
	 */
	public Vector(Vector v){
		this(v.vector);
	}
	
	private Vector create(int n){
		if(this instanceof Vector2D) return new Vector2D();
		if(this instanceof Vector3D) return new Vector3D();
		else return new Vector(n);
	}
	
	private Vector create(double[] v){
		if(this instanceof Vector2D) return new Vector2D(v);
		if(this instanceof Vector3D) return new Vector3D(v);
		else return new Vector(v);
	}
	
	private Matrix create(int m, int n){
    	if(this instanceof Vector2D) return new Matrix2D(n);
    	if(this instanceof Vector3D) return new Matrix3D(n);
    	else return new Matrix(m,n);
	}
	
	/**
	 * @return vector array
	 */
	public double[] getArray(){
		int i, n = n();
		double[] v = new double[n];
		for(i=0; i<n; i++)
			v[i] = get(i);
		return v;
	}
	
	/**
	 * @return vector array
	 */
	public List<Double> get(){
		return vector;
	}
	
	/**
	 * @param i index
	 * @return corresponding coordinate
	 */
	public double get(int i){
		return vector.get(i);
	}
	
	/**
	 * @return vector array (integers)
	 */
	public int[] getInts(){
		int i;
		int[] result = new int[n()];
		for(i=0; i<n(); i++)
			result[i] = (int) get(i);
		return result;
	}
	
	/**
	 * set value at index
	 * @param i index
	 * @param a value
	 */
	public Vector set(int i, double a){
		vector.set(i, a);
		return this;
	}
	
	/**
	 * get x (first element)
	 * @return x value
	 */
	public double x(){
		return n()>0?get(0):Double.NaN;
	}
	
	/**
	 * get y (second element)
	 * @return y value
	 */
	public double y(){
		return n()>1?get(1):Double.NaN;
	}
	
	/**
	 * get z (third element)
	 * @return z value
	 */
	public double z(){
		return n()>2?get(2):Double.NaN;
	}
	
	/**
	 * length of vector
	 * @return dimension
	 */
	public int n(){
		return length();
	}
	
	/**
	 * length of vector
	 * @return dimension
	 */
	public int length(){
//		return vector.length;
		return vector.size();
	}
	
	/**
	 * Create vector with zeros
	 * @param n size
	 * @return zero vector
	 */
	public static Vector zeros(int n){
		return new Vector(n);
	}
	
	/**
	 * Create vector with ones
	 * @param n size
	 * @return one vector
	 */
	public static Vector ones(int n){
		return fill(n, 1);
	}
	
	/**
	 * Create vector with given scalar
	 * @param n size
	 * @param s scalar
	 * @return filled vector
	 */
	public static Vector fill(int n, double s){
		Vector a = new Vector(n);
		int i;
		for(i=0; i<n; i++)
			a.set(i, s);
		return a;
	}
	
	/**
	 * Create vector and fill it from a start value to an end value
	 * @param start
	 * @param end
	 * @return filled vector [start,start+1,...,end]
	 */
	public static Vector fill(double start, double end){
		double delta = end-start;
		int increment = delta>0 ? 1 : -1;
		return fill(start, increment, end);
	}
	
	/**
	 * Create vector and fill it from a start value to an end value with n equidistant steps
	 * @param start
	 * @param end
	 * @param n steps
	 * @return filled vector [start,(end-start)*1/n,(end-start)*2/n,...,end]
	 */
	public static Vector fillN(double start, double end, int n){
		double increment = (end-start)/n;
		return fill(start, increment, end);
	}
	
	/**
	 * Create vector and fill it from a start value and increments it to an end value
	 * @param start
	 * @param increment
	 * @param end
	 * @return filled vector [start,start+increment,start+2*increment,...,end]
	 */
	public static Vector fill(double start, double increment, double end){
		double delta = end-start;
		int n = (int)(delta/increment)+1;
		if((delta>=0&&increment<0)||(delta<0&&increment>=0)||increment==0) return new Vector();
		Vector a = new Vector(n);
		int i;
		for(i=0; i<n; i++)
			a.set(i, start + i*increment);
		return a;
	}
	
	/**
	 * Create zero vector and fill it with given values at given positions (first position is 1)
	 * @param n
	 * @param position
	 * @param a value array
	 * @return filled vector
	 */
	public static Vector fill(int n, int[] position, double[] a){
		Vector c = new Vector(n);
		for(int i=0; i<position.length; i++)
			c.set(position[i]-1, a[i]);
		return c;
	}
	
	/**
	 * Create zero vector and fill it with given values at given positions (first position is 1)
	 * @param n
	 * @param position
	 * @param a value vector
	 * @return filled vector
	 */
	public static Vector fill(int n, int[] position, Vector a){
		Vector c = new Vector(n);
		for(int i=0; i<position.length; i++)
			c.set(position[i]-1, a.get(i));
		return c;
	}
	
	/**
	 * Create zero vector and fill it with given values at given positions (first position is 1)
	 * @param n
	 * @param position
	 * @param a value vector
	 * @return filled vector
	 */
	public static Vector fill(int n, Vector position, Vector a){
		Vector c = new Vector(n);
		for(int i=0; i<position.n(); i++)
			c.set((int) position.get(i)-1, a.get(i));
		return c;
	}
	
	/**
	 * Fill vector with given values at given indices (first index is 0)
	 * @param indices
	 * @param a value vector
	 */
	public void fill(int[] indices, Vector a){
		for(int i=0; i<indices.length; i++)
			set(indices[i], a.get(i));
	}
	
	/**
	 * Fill vector with given values at given positions (indices +1)
	 * @param positions
	 * @param a vector
	 */
	public void fillPos(int[] positions, Vector a){
		fill(new Vector(positions).minus(1).getInts(),a);
	}
	
	/**
	 * Create vector and fill it with random values [0,1)
	 * @param n rows
	 * @return random number filled vector
	 */
	public static Vector rand(int n){
		Vector a = new Vector(n);
		int i;
		for(i=0; i<n; i++)
			a.set(i, Math.random());
		return a;
	}
	
	/**
	 * Create vector and fill it with random values [0,s]
	 * @param n rows
	 * @param s scalar
	 * @return random number filled vector
	 */
	public static Vector rand(int n, double s){
		Vector a = new Vector(n);
		int i;
		for(i=0; i<n; i++)
			a.set(i, Math.random()*s);
		return a;
	}
	
	/**
	 * add a Vector.
	 * @param a vector to add
	 * @return added Vector
	 */
	public Vector plus(Vector a){
		int i;
		Vector c = create(n());
		for(i=0; i<n(); i++)
			c.set(i, get(i) + a.get(i));
		return c;
	}
	
	/**
	 * add a Vector.
	 * @param s as vector to add
	 * @return added Vector
	 */
	public Vector plus(double s){
		return plus(Vector.fill(n(), s));
	}
	
	public void plus(int i, double s){
		set(i, get(i)+s);
	}
	
	/**
	 * subtract a Vector.
	 * @param a vector to subtract
	 * @return subtracted Vector
	 */
	public Vector minus(Vector a){
		return plus(a.times(-1));
	}
	
	/**
	 * subtract a Vector.
	 * @param s as vector to subtract
	 * @return subtracted Vector
	 */
	public Vector minus(double s){
		return plus(Vector.fill(n(), -s));
	}
    
	/**
	 * lengthen the vector due scalar multiplication
	 * @param s scalar
	 * @return C = s A
	 */
    public Vector times(double s){
    	int i;
    	Vector c = create(n());
        for(i=0; i<n(); i++)
        	c.set(i, get(i) * s);
        return c;
    }
	
    /**
     * Multiply a matrix right. Linear algebraic matrix multiplication.
     * @param B matrix
     * @return <b>C</b> = <b>A</b> • <b>B</b> = <b>A</b><sup>T</sup><b>B</b> 
     * @throws IllegalDimensionException Inner matrix dimensions must agree.
     */
	public Vector times(Matrix B) throws IllegalDimensionException{
		int i,j;
        if(n() != B.getM()) throw new IllegalDimensionException("Inner matrix dimensions must agree.");
		double[] c = new double[n()];
		for(i=0; i<n(); i++)
			for(j=0; j<B.getM(); j++)
				c[i] += (get(j) * B.get(j,i));
		return create(c);
	}
    
    /**
     * Dot / inner / scalar product x<sup>T</sup>x.
     * A • B = a<sub>1</sub>b<sub>1</sub> + a<sub>2</sub>b<sub>2</sub> +
     * &hellip; + a<sub>n</sub>b<sub>n</sub>;
     * a scalar quantity
     * @return scalar
     * @throws IllegalDimensionException
     */
    public double dot() throws IllegalDimensionException{
    	return dot(this);
    }
    
    /**
     * Dot / inner / scalar product x<sup>T</sup>y.
     * A • B = a<sub>1</sub>b<sub>1</sub> + a<sub>2</sub>b<sub>2</sub> +
     * &hellip; + a<sub>n</sub>b<sub>n</sub>;
     * a scalar quantity
     * @param a vector
     * @return scalar 
     * @throws IllegalDimensionException 
     */
    public double dot(Vector a) throws IllegalDimensionException{
        if(n() != a.n()) throw new IllegalDimensionException("Illegal vector dimension.");
    	int i;
    	double c = 0;
    	for(i=0; i<n(); i++)
    		c += get(i) * a.get(i);
    	return c;
    }
    
    /**
     * Outer / tensor product vv'
     * @param b vector
     * @return matrix
     */
    public Matrix tensorProduct(Vector b){
    	int i, j;
    	Matrix c = create(n(),b.n());
    	for(i=0; i<n(); i++)
    		for(j=0; j<b.n(); j++)
    			c.set(i, j, get(i) * b.get(j));
    	return c;
    }
    
    /**
     * Multiplicate the vector a element wise with vector b.
     * c<sub>i</sub> = a<sub>i</sub>b<sub>i</sub>
     * @param b vector
     * @return vector
     */
    public Vector timesE(Vector b){
    	int i;
    	Vector c = create(n());
    	for(i=0; i<n(); i++)
    		c.set(i, get(i) * b.get(i));
    	return c;
    }
    
	/**
	 * shorten the vector due scalar division
	 * @param s scalar
	 * @return C = A/s
	 */
    public Vector over(double s){
    	int i;
    	Vector c = create(n());
        for(i=0; i<n(); i++)
        	c.set(i, get(i) / s);
        return c;
    }
    
    /**
     * Divide the vector a element wise with vector b.
     * c<sub>i</sub> = a<sub>i</sub>/b<sub>i</sub>
     * @param b vector
     * @return vector
     */
    public Vector overE(Vector b){
    	int i;
    	Vector c = create(n());
    	for(i=0; i<n(); i++)
    		c.set(i, get(i) / b.get(i));
    	return c;
    }
	
	/**
	 * @return length or magnitude or Euclidean norm 
	 */
	public double norm(){
		try {
			return Math.sqrt(dot(this));
		} catch (IllegalDimensionException e) {
			e.printStackTrace();
			return Double.NaN;
		}
	}

    /**
     * @param b
     * @return Euclidean distance between both vectors
     * @throws IllegalDimensionException
     */
    public double distanceTo(Vector b) throws IllegalDimensionException {
        if(n() != b.n()) throw new IllegalDimensionException("Illegal vector dimension.");
        return b.minus(this).norm();
    }
	
	/**
	 * normalizing a vector.
	 * A vector of arbitrary length divided by its length.
	 * <b>a</b>/||<b>a</b>||
	 * @return unit vector
	 */
	public Vector normalize(){
		Vector c = times(1.0/norm());
		this.vector = c.vector;
		return this;
	}
	
    /**
     * @return the corresponding unit vector
     */
    public Vector direction() {
        if(norm() == 0.0) throw new RuntimeException("Zero-vector has no direction");
        return times(1.0 / norm());
    }
	
    /**
     * Element-wise cosine of argument in radians.
     * @return the cosine for each element of the matrix
     */
    public Vector cos() {
		int i;
		int n = n();
        Vector c = new Vector(n);
        for(i=0; i<n; i++)
        	c.set(i, Math.cos(get(i)));
        return c;
    }
	
    /**
     * Element-wise cosine of argument in radians.
     * @return the cosine for each element of the matrix
     */
    public Vector sin() {
		int i;
		int n = n();
        Vector c = new Vector(n);
        for(i=0; i<n; i++)
        	c.set(i, Math.sin(get(i)));
        return c;
    }
    
    /**
     * cosine between this and given vector
     * @param v vector
     * @return cos&theta;
     * @throws IllegalDimensionException
     */
    public double cos(Vector v) throws IllegalDimensionException{
    	return dot(v)/(norm()*v.norm());
    }
    
    // sine see in Vector3D
    
    public double angleD(Vector v) throws IllegalDimensionException{
    	return Maths.acosd(cos(v));
    }

    /**
     * swap index i and j
     * @param i index
     * @param j index
     */
    public Vector swap(int i, int j) {
        double temp = get(i);
        set(i, get(j));
        set(j, temp);
        return this;
    }

    /**
     * swap min value and max value
     */
    public Vector swapMinMax() {
		int imin = indexOfMin();
		int imax = indexOfMax();
        double temp = get(imin);
        set(imin, get(imax));
        set(imax, temp);
        return this;
    }
	
    /**
     * Creates a sub vector only with chosen rows
     * @param indices rows
     * @return the sub vector
     */
    public Vector sub(int[] indices){
		return sub(indices,false);
    }
	
    /**
     * Creates a sub vector only with chosen rows
     * @param indices rows
     * @return the sub vector
     */
    public Vector sub(int[] indices, boolean positions){
    	int n = indices.length;
    	int d = positions?1:0;
		double[] red = new double[n];
		for(int i=0; i<n; i++)
			red[i] = get(indices[i]-d);
		return new Vector(red);
    }

    /**
     * element wise absolute value.
     * v = { |v<sub>1</sub>|, |v<sub>2</sub>|, &hellip;, |v<sub>n</sub>| }
     * @return vector with absolute values
     */
    public Vector abs(){
		int i;
		Vector c = create(n());
		for(i=0; i<n(); i++)
			c.set(i, get(i)>=0 ? get(i) : -get(i));
		return c;
    }
	
	/**
	 * Reflection against a plane due to a vector
	 * @return reflected vector
	 * @throws IllegalDimensionException
	 */
	public Vector flip(Vector v) throws IllegalDimensionException{
		// P = I - alpha v v'; alpha = 2 / (v'v)
		return Matrix.mirror(v).times(this);
	}
    
    /**
     * Maximum value of the vector
     * @return maximum value in vector, -∞ if no such value.
     */
	public double max(){
		return Maths.max(getArray());
	}
    
    /**
     * Minimum value of the vector
     * @return minimum value in vector, +∞ if no such value.
     */
	public double min(){
		return Maths.min(getArray());
	}
	
	/**
	 * Find value in vector.
	 * @param a value
	 * @return index
	 */
	public int indexOf(double a){
		int i;
		for(i=0; i<n(); i++)
			if(a == get(i)) return i;
		return -1;
	}
	
	/**
	 * Find max value in vector.
	 * @return index
	 */
	public int indexOfMax(){
		return indexOf(max());
	}
	
	/**
	 * Find min value in vector.
	 * @return index
	 */
	public int indexOfMin(){
		return indexOf(min());
	}
	
	/**
	 * Sort vector in ascending order.
	 * @return sorted vector
	 */
	public Vector sort(){
//		Arrays.sort(vector);
		Collections.sort(vector);
		return this;
	}
	
	/**
	 * Sort vector in descending order.
	 * @return sorted vector
	 */
	public Vector sortR(){
		sort();
		int i,j;
		Vector tmp = create(n());
        for(i = length()-1, j=0; i >= 0; i--, j++)
            tmp.set(j, get(i));
        vector = tmp.vector;
        return this;
	}
	
	/**
	 * Zero vector?
	 * @return boolean
	 */
	public boolean isZero(){
		int i;
		boolean result = true;
		for(i=0; i<n(); i++){
			if(get(i) > 0){
				result = false;
				break;
			}
		}
		return result;
	}
	
	public boolean isNormalized(){
		return Math.abs(norm()-1)<Maths.ε;
	}
	
	/**
	 * Vector in euclidean plane &#x211D;<sup>2</sup>.
	 * n=2
	 * @return true or false
	 */
	public boolean isR2(){
		return (n()==2);
	}
	
	/**
	 * Vector in euclidean plane &#x211D;<sup>3</sup>.
	 * n=3
	 * @return true or false
	 */
	public boolean isR3(){
		return (n()==3);
	}
	
	/**
	 * 
	 * @param a int array
	 * @param b int array
	 * @return true if two integer arrays have same length and
	 * all corresponding pairs of integers are equal
	 */
    public static boolean equals(int[] a, int[] b){
        if(a.length != b.length) return false; // same length?
        int i;
        for(i=0; i<a.length; i++)              // check each corresponding pair
            if(a[i] != b[i]) return false;
        return true; // all elements must be equal
    }
	
    /**
     * A string representation of the vector.
     */
    @Override
	public String toString(){
    	return new Matrix(this).setName(getName()).toString();
    }
    
    /**
     * Transpose (horizontal)
     * @return string of vector in transposed form
     */
	public String toStringT(){
		return new Matrix(this).transpose().toString();
    }
    
    public void println(){
		System.out.println(toString());
    }
	
	/**
	 * Transpose (horizontal)
	 */
	public void toConsoleT(){
		System.out.println(toStringT());
	}

    /**
     * test client
     * @param args
     */
    public static void main(String[] args) { 
		Vector a = new Vector(5, 1, 1);
		a.println();
		Vector b = a.normalize();
		a.println();
		b.println();
		Vector.fill(2., 2.5).println();
		Vector.fill(2., 8).println();
		Vector.fill(8., 2).toConsoleT();
		Vector.fill(2., 2, 8).println();
		Vector.fill(8., -2, 2).toConsoleT();
		System.out.println("a isNull? : " + a.isZero());
		System.out.println("a-a isNull? : " + a.minus(a).isZero());
		a = new Vector(1, 1);
		a.println();
		try {
			System.out.println(a.dot(a));
		} catch (IllegalDimensionException e1) {
			e1.printStackTrace();
		}
		a.tensorProduct(a).println();
		b = new Vector(1, 2, 3);
		a.tensorProduct(b).println();
		Vector v = new Vector(1, 1);
		Vector w = new Vector(0, 1);
		try {
			w.flip(v).println();
		} catch (IllegalDimensionException e) {
			e.printStackTrace();
		}
		Vector c = new Vector(4, -21.0, 3);
		c.setName("c").println();
		c.sort().println();
		c.sortR().println();
   }
}
