package de.visone.nlpext.util;

import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static java.lang.Math.sqrt;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * This class represents a vector of an arbitrary dimention
 * and provides arithmetic functions upon it.
 * @author <a href="mailto:endele@inf.uni-konstanz.de">Simon Endele</a>
 */
public class Vect implements java.io.Serializable, Cloneable {
	
	//========== fields ==========
	
	public static double defVal = 0.0; //default value
	
	private double[] a; //Vektor, der durch diese Klasse dargestellt wird
	
	//========== construction ==========
	
	public Vect(double... a) {
		if (a==null || a.length==0) throw new IllegalArgumentException("Vect must at least have dimension 1!");
		this.a = a;
		//this.a = (double[])a.clone();
	}
	
	/** Creates a new Vect of dimension d with all entries 0.0. */
	public static Vect create(int d) {
		return create(d, 0.0);
	}
	/** Creates a new Vect of dimension d with all entries val. */
	public static Vect create(int d, double val) {
		if (d<1) throw new IllegalArgumentException("Vect must at least have dimension 1!");
		Vect b = new Vect(new double[d]);
		Arrays.fill(b.a, val);
		return b;
	}
	
	//========== misc ==========
	
	/** Returns a deep copy of this vector. */
	public Vect copy() {
		return new Vect((double[])a.clone());
	}
	
	/** Returns the size of this vector. */
	public int size() {
		return a.length;
	}
	/** Returns the dimension of this vector. */
	public int dim() {
		return a.length;
	}
	
	/** Returns the i-th entry in this vector. */
	public double get(int i) {
		return (i>=0 && i<dim()) ? a[i] : defVal;
	}
	/** Synonym for get(0). */
	public double x() { return get(0); }
	/** Synonym for get(1). */
	public double y() { return get(1); }
	/** Synonym for get(2). */
	public double z() { return get(2); }
	
	
	/** Sets the i-th entry in this vector to v. */
	public void set(int i, double v) {
		if (i>=0 && i<dim())  a[i] = v;
	}
	
	/** Returns true iff all entries are zero. */
	public boolean isNullVector() {
		for (double b : a) if (b!=0.0) return false;
		return true;
	}
	
	/** Returns true iff obj is a Vect of same length and same entries. */
	public boolean equals(Object obj) {
		if (obj instanceof Vect) {
			Vect b = (Vect)obj;
			if (dim()!=b.dim()) return false;
			for (int i=0; i<dim(); i++) if (a[i]!=b.a[i]) return false;
			return true;
		} else {
			return false;
		}
	}
	
	/** Returns a comma seperated String representation of this vector. */
	public String toString() {
		String s = ""+a[0];
		for (int i=1; i<a.length; i++) s += ","+a[i];
		return s;
	}
	
	//========== arithmetic  ==========
	
	//---------- unary operators ----------
	
	public double sum() {
		double s = 0.0;
		for (double b : a) s += b;
		return s;
	}
	
	public double quadSum() {
		double s = 0.0;
		for (double b : a) s += b*b;
		return s;
	}
	
	public double abs() {
		return length();
	}
	public double length() {
		return sqrt(quadSum());
	}
	
	public double max() {
		double m = -Double.MAX_VALUE;
		for (double b : a)  m = Math.max(b,m);
		return m;
	}
	
	public double min() {
		double m = Double.MAX_VALUE;
		for (double b : a)  m = Math.min(b,m);
		return m;
	}
	
	public Vect neg() {
		for (int i=0; i<dim(); i++)  a[i] = -a[i];
		return this;
	}
	
	public Vect add(double b) {
		for (int i=0; i<dim(); i++)  a[i] += b;
		return this;
	}
	
	public Vect sub(double b) {
		for (int i=0; i<dim(); i++)  a[i] -= b;
		return this;
	}
	
	public Vect mult(double b) {
		for (int i=0; i<dim(); i++)  a[i] *= b;
		return this;
	}
	
	public Vect div(double b) {
		for (int i=0; i<dim(); i++)  a[i] /= b;
		return this;
	}
	
	public Vect norm() {
		return div(length());
	}
	
	/** Returns a vector that is orthogonal to this 2D vector. */
	public Vect getOrtho() {
		if (dim()!=2) throw new IllegalArgumentException("Vector must have dimension 2!");
		return new Vect(a[1], -a[0]);
	}
	
	/** Returns a vector that is rotated counter-clockwisely by angle. */
	public Vect rotate(double angle) {
		if (dim()!=2) throw new IllegalArgumentException("Vector must have dimension 2!");
		double cos = cos(angle);
		double sin = sin(angle);
		return new Vect(a[0]*cos-a[1]*sin, a[0]*sin+a[1]*cos);
	}
	
	/** Apply a method from java.lang.Math by components to this vector. */
	public Vect apply(String funct) {
		try {
			Method method = Math.class.getMethod(funct, double.class);
			double[] c = new double[dim()]; //Fehler kann auch mitten in der Schleife auftreten
			for (int i=0; i<dim(); i++) c[i] = ((Double)method.invoke(null, a[i])).doubleValue();
			return new Vect(c);
		} catch(Exception ex) {
			System.out.println("Did not find method \"java.lang.Math."+funct+"(double)\", returning this.");
			return this;
		}
	}
	
	//---------- binary operators ----------
	
	/** Apply a binary method from java.lang.Math by components to this vector and b. */
	public Vect apply(String funct, Vect b) {
		if (dim()!=b.dim()) throw new IllegalArgumentException("Vectors must have same lenghts!");
		try {
			Method method = Math.class.getMethod(funct, double.class, double.class);
			double[] c = new double[dim()]; //Fehler kann auch mitten in der Schleife auftreten
			for (int i=0; i<dim(); i++) c[i] = ((Double)method.invoke(null, a[i], b.get(i))).doubleValue();
			return new Vect(c);
		} catch(Exception ex) {
			System.out.println("Did not find method \"java.lang.Math."+funct+"(double,double)\", returning this. Occured Exception:");
			ex.printStackTrace(System.out);
			return this;
		}
	}
	
	public double dist(Vect b) {
		if (dim()!=b.dim()) throw new IllegalArgumentException("Vects must have same dimension!");
		return copy().sub(b).length();
	}
	
	public Vect add(Vect b) {
		if (dim()!=b.dim()) throw new IllegalArgumentException("Vects must have same dimension!");
		for (int i=0; i<dim(); i++)  a[i] += b.get(i);
		return this;
	}
	
	public Vect sub(Vect b) {
		if (dim()!=b.dim()) throw new IllegalArgumentException("Vects must have same dimension!");
		for (int i=0; i<dim(); i++)  a[i] -= b.get(i);
		return this;
	}
	
	public Vect mult(Vect b) {
		if (dim()!=b.dim()) throw new IllegalArgumentException("Vects must have same dimension!");
		for (int i=0; i<dim(); i++)  a[i] *= b.get(i);
		return this;
	}
	
	public Vect div(Vect b) {
		if (dim()!=b.dim()) throw new IllegalArgumentException("Vects must have same dimension!");
		for (int i=0; i<dim(); i++)  a[i] /= b.get(i);
		return this;
	}
	
	
	public double dotProduct(Vect b) {
		if (dim()!=b.dim()) throw new IllegalArgumentException("Vects must have same dimension!");
		double s = 0.0;
		for (int i=0; i<dim(); i++)  s += a[i]*b.get(i);
		return s;
	}
	
	public Vect crossProduct(Vect b) {
		if (dim()!=b.dim()) throw new IllegalArgumentException("Vects must have same dimension!");
		return new Vect(
			a[1]*b.a[2] - a[2]*b.a[1],
			a[2]*b.a[0] - a[0]*b.a[2],
			a[0]*b.a[1] - a[1]*b.a[0] );
	}
	
	
	//========== main ==========
	
	public static void main(String[] args) {
		Vect a = new Vect(1, 2);
		Vect b = a.copy();
		b.a[0] = 3;
		b.a[1] = 1;
		System.out.println("a: "+a);
		System.out.println("b: "+b);
		System.out.println("min(b): "+b.min());
		System.out.println("cos(a): "+a.apply("cos"));
		System.out.println("min(a,b): "+a.apply("min", b));
		System.out.println("a dot b: "+a.dotProduct(b));
		System.out.println("a+b: "+a.copy().add(b));
		System.out.println("a-b: "+a.copy().sub(b));
		System.out.println("(1,0,0)x(0,1,0): "+new Vect(1,1,1).crossProduct(new Vect(1,1,0)));
		
	}
}
