package edu.berkeley.nlp.math;

import java.util.Arrays;

/**
 */
public class DoubleArrays {
	public static double[] clone(double[] x) {
		double[] y = new double[x.length];
		assign(y, x);
		return y;
	}

	public static void assign(double[] y, double[] x) {
		if (x.length != y.length)
			throw new RuntimeException("diff lengths: " + x.length + " "
					+ y.length);
		System.arraycopy(x, 0, y, 0, x.length);
	}

	public static double innerProduct(double[] x, double[] y) {
		if (x.length != y.length)
			throw new RuntimeException("diff lengths: " + x.length + " "
					+ y.length);
		double result = 0.0;
		for (int i = 0; i < x.length; i++) {
			result += x[i] * y[i];
		}
		return result;
	}

	public static double innerProduct(Double[] x, double[] y) {
		if (x.length != y.length)
			throw new RuntimeException("diff lengths: " + x.length + " "
					+ y.length);
		double result = 0.0;
		for (int i = 0; i < x.length; i++) {
			result += x[i] * y[i];
		}
		return result;
	}

	public static double[] addMultiples(double[] x, double xMultiplier,
			double[] y, double yMuliplier) {
		if (x.length != y.length)
			throw new RuntimeException("diff lengths: " + x.length + " "
					+ y.length);
		double[] z = new double[x.length];
		for (int i = 0; i < z.length; i++) {
			z[i] = x[i] * xMultiplier + y[i] * yMuliplier;
		}
		return z;
	}

	public static double[] constantArray(double c, int length) {
		double[] x = new double[length];
		Arrays.fill(x, c);
		return x;
	}

	public static double[] pointwiseMultiply(double[] x, double[] y) {
		if (x.length != y.length)
			throw new RuntimeException("diff lengths: " + x.length + " "
					+ y.length);
		double[] z = new double[x.length];
		for (int i = 0; i < z.length; i++) {
			z[i] = x[i] * y[i];
		}
		return z;
	}

	public static boolean probabilisticNormalize(double[] x) {
		double sum = add(x);
		if (sum <= 0.0)
			return false;
		scale(x, 1.0 / sum);
		return true;
	}

	public static String toString(double[] x) {
		return toString(x, x.length);
	}

	public static String toString(double[][] x) {
		StringBuilder sb = new StringBuilder();
		for (double[] row : x) {
			sb.append(DoubleArrays.toString(row));
			sb.append("\n");
		}
		return sb.toString();
	}

	public static String toString(double[] x, int length) {
		StringBuffer sb = new StringBuffer();
		sb.append("[");
		for (int i = 0; i < SloppyMath.min(x.length, length); i++) {
			sb.append(String.format("%.5f", x[i]));
			if (i + 1 < SloppyMath.min(x.length, length))
				sb.append(", ");
		}
		sb.append("]");
		return sb.toString();
	}

	public static void scale(double[] x, double s) {
		if (s == 1.0)
			return;
		for (int i = 0; i < x.length; i++) {
			x[i] *= s;
		}
	}

	public static double[] multiply(double[] x, double s) {
		double[] result = new double[x.length];
		if (s == 1.0) {
			System.arraycopy(x, 0, result, 0, x.length);
			return result;
		}
		for (int i = 0; i < x.length; i++) {
			result[i] = x[i] * s;
		}
		return result;
	}

	public static int argMax(double[] v) {
		int maxI = -1;
		double maxV = Double.NEGATIVE_INFINITY;
		for (int i = 0; i < v.length; i++) {
			if (v[i] > maxV) {
				maxV = v[i];
				maxI = i;
			}
		}
		return maxI;
	}

	public static double max(double[] v) {
		double maxV = Double.NEGATIVE_INFINITY;
		for (int i = 0; i < v.length; i++) {
			if (v[i] > maxV) {
				maxV = v[i];
			}
		}
		return maxV;
	}

	public static double max(double[][] m) {
		double max = Double.NEGATIVE_INFINITY;
		for (double[] row : m) {
			max = Math.max(max(row), max);
		}
		return max;
	}

	public static int argMin(double[] v) {
		int minI = -1;
		double minV = Double.POSITIVE_INFINITY;
		for (int i = 0; i < v.length; i++) {
			if (v[i] < minV) {
				minV = v[i];
				minI = i;
			}
		}
		return minI;
	}

	public static double min(double[] v) {
		double minV = Double.POSITIVE_INFINITY;
		for (int i = 0; i < v.length; i++) {
			if (v[i] < minV) {
				minV = v[i];
			}
		}
		return minV;
	}

	public static double min(double[][] m) {
		double min = Double.POSITIVE_INFINITY;
		for (double[] row : m) {
			min = Math.min(min(row), min);
		}
		return min;
	}

	public static double maxAbs(double[] v) {
		double maxV = 0;
		for (int i = 0; i < v.length; i++) {
			double abs = (v[i] <= 0.0d) ? 0.0d - v[i] : v[i];
			if (abs > maxV) {
				maxV = abs;
			}
		}
		return maxV;
	}

	public static double[] add(double[] a, double b) {
		double[] result = new double[a.length];
		for (int i = 0; i < a.length; i++) {
			double v = a[i];
			result[i] = v + b;
		}
		return result;
	}

	public static double add(double[] a) {
		double sum = 0.0;
		for (int i = 0; i < a.length; i++) {
			sum += a[i];
		}
		return sum;
	}

	public static int add(int[] a) {
		int sum = 0;
		for (int i = 0; i < a.length; i++) {
			sum += a[i];
		}
		return sum;
	}

	public static double add(double[] a, int first, int last) {
		if (last >= a.length)
			throw new RuntimeException("last beyond end of array");
		if (first < 0)
			throw new RuntimeException("first must be at least 0");
		double sum = 0.0;
		for (int i = first; i <= last; i++) {
			sum += a[i];
		}
		return sum;
	}

	public static double vectorLength(double[] x) {
		return Math.sqrt(innerProduct(x, x));
	}

	public static double[] add(double[] x, double[] y) {
		if (x.length != y.length)
			throw new RuntimeException("diff lengths: " + x.length + " "
					+ y.length);
		double[] result = new double[x.length];
		for (int i = 0; i < x.length; i++) {
			result[i] = x[i] + y[i];
		}
		return result;
	}

	public static void subtractInPlace(double[] x, double[] y) {
		// be in cvs
		for (int i = 0; i < x.length; ++i) {
			x[i] -= y[i];
		}
	}

	public static void subtractInPlace(double[] x, double y) {
		for (int i = 0; i < x.length; ++i) {
			x[i] -= y;
		}
	}

	/**
	 * If a subtraction results in NaN (i.e -inf - (-inf)) does not perform the
	 * computation.
	 * 
	 * @param x
	 * @param y
	 */
	public static void subtractInPlaceUnsafe(double[] x, double[] y) {
		// be in cvs
		for (int i = 0; i < x.length; ++i) {
			if (Double.isNaN(x[i] - y[i])) {
				continue;
			}
			x[i] -= y[i];
		}
	}

	public static double[] subtract(double[] x, double[] y) {
		if (x.length != y.length)
			throw new RuntimeException("diff lengths: " + x.length + " "
					+ y.length);
		double[] result = new double[x.length];
		for (int i = 0; i < x.length; i++) {
			result[i] = x[i] - y[i];
		}
		return result;
	}

	public static double[] exponentiate(double[] pUnexponentiated) {
		double[] exponentiated = new double[pUnexponentiated.length];
		for (int index = 0; index < pUnexponentiated.length; index++) {
			exponentiated[index] = SloppyMath.exp(pUnexponentiated[index]);
		}
		return exponentiated;
	}

	public static double[][] exponentiate(double[][] pUnexponentiated) {
		double[][] exponentiated = new double[pUnexponentiated.length][];
		for (int index = 0; index < pUnexponentiated.length; index++) {
			exponentiated[index] = exponentiate(pUnexponentiated[index]);
		}
		return exponentiated;
	}

	public static double[][][] exponentiate(double[][][] pUnexponentiated) {
		double[][][] exponentiated = new double[pUnexponentiated.length][][];
		for (int index = 0; index < pUnexponentiated.length; index++) {
			exponentiated[index] = exponentiate(pUnexponentiated[index]);
		}
		return exponentiated;
	}

	public static void truncate(double[] x, double maxVal) {
		for (int index = 0; index < x.length; index++) {
			if (x[index] > maxVal)
				x[index] = maxVal;
			else if (x[index] < -maxVal)
				x[index] = -maxVal;
		}
	}

	public static void initialize(double[] x, double d) {
		Arrays.fill(x, d);
	}

	public static void initialize(Object[] x, double d) {
		for (int i = 0; i < x.length; i++) {
			Object o = x[i];
			if (o instanceof double[])
				initialize((double[]) o, d);
			else
				initialize((Object[]) o, d);
		}
	}

	public static void addInPlace(double[] x, double c) {
		for (int i = 0; i < x.length; i++) {
			x[i] += c;
		}
	}

	public static void addInPlace(double[] x, double[] y) {
		assert y.length >= x.length;
		for (int i = 0; i < x.length; ++i) {
			x[i] += y[i];
		}
	}

	public static void addInPlace2D(double[][] x, double[][] y) {
		// TODO Auto-generated method stub
		assert y.length >= x.length;
		for (int i = 0; i < x.length; ++i) {
			DoubleArrays.addInPlace(x[i], y[i]);
		}
	}

	public static void multiplyInPlace(double[] x, double[] y) {
		for (int i = 0; i < x.length; i++) {
			x[i] *= y[i];
		}
	}

	public static double[] average(double[][] x) {
		if (x.length == 0) {
			return null;
		}
		double[] sum = x[0];
		for (int i = 1; i < x.length; i++) {
			sum = add(sum, x[i]);
		}
		double[] avg = multiply(sum, (1.0 / x.length));
		return avg;
	}

	/*
	 * project x onto a orthant defined by y
	 */
	public static void project(double[] x, double[] y) {
		for (int i = 0; i < x.length; i++) {
			if (x[i] * y[i] <= 0)
				x[i] = 0;
		}
	}

	/*
	 * project x onto a orthant defined by y
	 */
	public static void project2(double[] x, double[] y) {
		for (int i = 0; i < x.length; i++) {
			if (x[i] * y[i] < 0)
				x[i] = 0;
		}
	}

	public static void checkNonNegative(double[] x) {
		for (double v : x) {
			if (v < -1.0e-10) {
				throw new RuntimeException("Negative number " + v);
			}
		}
	}

	public static void checkNonNegative(double[][] m) {
		for (double[] row : m) {
			checkNonNegative(row);
		}
	}

	/**
	 * Loop and ensure all elements are non-infiite and non-nan, throws an
	 * exception if one is
	 * 
	 * @param x
	 */
	public static void checkValid(double[] x) {
		for (double v : x) {
			if (Double.isNaN(v)) {
				throw new RuntimeException("Invalid entry " + v);
			}
		}
	}

	public static void checkValid(double[][] m) {
		for (double[] row : m) {
			checkValid(row);
		}
	}

	public static double lInfinityDist(double[] x, double[] y) {
		double max = Double.NEGATIVE_INFINITY;
		for (int i = 0; i < x.length; i++) {
			max = Math.max(max, Math.abs(x[i] - y[i]));
		}
		return max;
	}

	public static void logInPlace(double[] vec) {
		for (int i = 0; i < vec.length; i++) {
			vec[i] = Math.log(vec[i]);
		}
	}

	public static void checkNonInfinite(double[] vec) {
		for (double v : vec) {
			if (Double.isInfinite(v)) {
				throw new RuntimeException("Invalid Entry: " + v);
			}
		}
	}

	public static void checkNonInfinite(double[][] m) {
		for (double[] row : m) {
			checkNonInfinite(row);
		}
	}

	public static void addInPlace(double[] a, double[] b, double c) {
		for (int i = 0; i < a.length; i++) {
			a[i] += b[i] * c;
		}
	}

	public static void addInPlace(double[][] a, double[][] b, double c) {
		for (int i = 0; i < a.length; i++) {
			addInPlace(a[i], b[i], c);
		}
	}

	public static double[] multiply(double[][] X, double[] y) {
		int m = X.length;
		int n = X[0].length;
		assert n == y.length;
		double[] result = new double[m];
		for (int i = 0; i < result.length; i++) {
			result[i] = DoubleArrays.innerProduct(X[i], y);
		}
		return result;
	}

	public static double[][] clone(double[][] M) {
		double[][] copy = new double[M.length][];
		for (int i = 0; i < M.length; i++) {
			copy[i] = clone(M[i]);
		}
		return copy;
	}

	public static double[][][] clone(double[][][] M) {
		double[][][] copy = new double[M.length][][];
		for (int i = 0; i < M.length; i++) {
			copy[i] = clone(M[i]);
		}
		return copy;
	}

	public static double[][][][] clone(double[][][][] M) {
		double[][][][] copy = new double[M.length][][][];
		for (int i = 0; i < M.length; i++) {
			copy[i] = clone(M[i]);
		}
		return copy;
	}

	public static float[] clone(float[] x) {
		float[] y = new float[x.length];
		assign(y, x);
		return y;
	}

	public static void assign(float[] y, float[] x) {
		if (x.length != y.length)
			throw new RuntimeException("diff lengths: " + x.length + " "
					+ y.length);
		System.arraycopy(x, 0, y, 0, x.length);
	}

	public static float[][] clone(float[][] M) {
		float[][] copy = new float[M.length][];
		for (int i = 0; i < M.length; i++) {
			copy[i] = clone(M[i]);
		}
		return copy;
	}

	public static float[][][] clone(float[][][] M) {
		float[][][] copy = new float[M.length][][];
		for (int i = 0; i < M.length; i++) {
			copy[i] = clone(M[i]);
		}
		return copy;
	}

	public static float[][][][] clone(float[][][][] M) {
		float[][][][] copy = new float[M.length][][][];
		for (int i = 0; i < M.length; i++) {
			copy[i] = clone(M[i]);
		}
		return copy;
	}

	public static double outerProduct(double[][] M, double[] x) {
		double sum = 0.0;
		for (int i = 0; i < M.length; i++) {
			for (int j = 0; j < M[i].length; j++) {
				sum += M[i][j] * x[i] * x[j];
			}
		}
		return sum;
	}
}
