package com.quui.tm2.agents.classifier.bayestree;

import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.List;

/**
 * Helper class with static methods for array and matrix operations
 * 
 * @author fsteeg
 * 
 */
public class ArrayTools {

	/**
	 * @param vectors
	 *            The vectors to multiply
	 * @return Returns a vector of the size of each vector in vectors containing
	 *         the products of the corresponding elements, or, if one of them is
	 *         zero, the other element
	 */
	static Float[] product(List<Float[]> vectors) {
		Float[] result = new Float[vectors.get(0).length];
		for (int i = 0; i < result.length; i++) {
			Float prod = 0.0f;
			for (int j = 0; j < vectors.size(); j++) {
				Float current = vectors.get(j)[i];
				prod = prod == 0.0f ? current : current == 0.0f ? prod : prod
						* current;
			}
			result[i] = prod;
		}
		result = normalizeForSum(result);
		return result;
	}

	/**
	 * @param m
	 *            The matrix to normalize
	 * @return Returns a douoble[][] of the same dmension containing the
	 *         normalized values (division of each element by the row sum)
	 */
	static Float[][] normalizeForSum(Float[][] m) {
		Float[][] res = new Float[m.length][m[0].length];
		for (int i = 0; i < m.length; i++) {
			Float[] bigIntegers = m[i];
			Float[] row = normalizeForSum(bigIntegers);
			res[i] = row;
		}
		return res;

	}

	static Float[][] normalizeForSum(int[][] m) {
		Float[][] res = new Float[m.length][m[0].length];
		for (int i = 0; i < m.length; i++) {
			int[] ints = m[i];
			Float[] row = normalizeForSum(ints);
			res[i] = row;
		}
		return res;

	}

	public static Float[] normalizeForSum(Float[] bigIntegers) {
		Float sum = 0f;
		Float[] row = new Float[bigIntegers.length];
		for (int j = 0; j < bigIntegers.length; j++) {
			sum += bigIntegers[j].floatValue();
		}
		for (int j = 0; j < bigIntegers.length; j++) {
			if (sum == 0)
				row[j] = 0.0f;
			else {
				row[j] = bigIntegers[j].floatValue() / sum;
			}
		}
		return row;
	}

	public static Float[] normalizeForSum(int[] bigIntegers) {
		Float sum = 0f;
		Float[] row = new Float[bigIntegers.length];
		for (int j = 0; j < bigIntegers.length; j++) {
			sum += bigIntegers[j];
		}
		for (int j = 0; j < bigIntegers.length; j++) {
			if (sum == 0)
				row[j] = 0.0f;
			else {
				row[j] = (float) bigIntegers[j] / sum;
			}
		}
		return row;
	}

	/**
	 * @param m1
	 *            Matrix 1
	 * @param m2
	 *            Matrix 2
	 * @return A matrix of the same dimension as m1 and m2 containing their
	 *         products
	 */
	static int[][] multiply(int[][] m1, int[][] m2) {
		int[][] res = new int[m1.length][m1[0].length];
		for (int i = 0; i < m1.length; i++) {
			for (int j = 0; j < m1[i].length; j++) {
				res[i][j] = m1[i][j] * m2[i][j];
			}
		}
		return res;
	}

	/**
	 * @param m
	 *            The matrix
	 * @return Returns an array containing the max values for each column in m
	 */
	static Float[] maxForCols(Float[][] m) {
		Float[] r = new Float[m[0].length];
		for (int i = 0; i < m[0].length; i++) {
			Float max = Float.MIN_VALUE;
			for (int j = 0; j < m.length; j++) {
				if (m[j][i] > max) {
					max = m[j][i];
				}
			}
			r[i] = max;
		}
		return r;
	}

	/**
	 * @param m
	 *            The matrix
	 * @return Returns an array containing the max values for each row in m
	 */
	static Float[] maxForRows(Float[][] m) {
		Float[] max = new Float[m.length];
		for (int i = 0; i < max.length; i++) {
			max[i] = maximum(m[i]);
		}
		return max;
	}

	/**
	 * @param a
	 *            The array
	 * @return Returns the sum of all the values in the array
	 */
	double sum(double[] a) {
		double sum = 0.0;
		for (double d : a) {
			sum += d;
		}
		return sum;
	}

	/**
	 * @param a
	 *            The array
	 * @return Returns the max value in a
	 */
	static Float maximum(Float[] a) {
		Float max = Float.MIN_VALUE;
		for (Float d : a) {
			if (d > max)
				max = d;
		}
		return max;
	}

	/**
	 * @param a
	 *            The array
	 * @param m
	 *            The matrix
	 * @return Retruns a matrix of the same dimension as m containing modified
	 *         values from m: each element in each row is multiplied with the
	 *         corresponding element in a
	 */
	static Float[][] multiplyRows(Float[] a, Float[][] m) {
		if (a.length != m.length)
			throw new IllegalStateException(
					"Different array and matrix sizes: " + a.length + ", "
							+ m.length);
		Float[][] c = new Float[a.length][m[0].length];
		try {
			for (int i = 0; i < c.length; i++) {
				for (int j = 0; j < c[i].length; j++) {
					c[i][j] = a[i] * m[i][j];
				}
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			e.printStackTrace();
		}
		c = normalizeForSum(c);
		return c;
	}

	static Float[][] multiplyColums(Float[] a, Float[][] m) {
		if (a.length != m[0].length)
			throw new IllegalStateException(
					"Different array and matrix sizes: " + a.length + ", "
							+ m[0].length);
		Float[][] c = new Float[m.length][m[0].length];
		for (int i = 0; i < a.length; i++) {
			for (int j = 0; j < c.length; j++) {
				c[j][i] = a[i] * m[j][i];
			}
		}
		
		return c;
	}

	/**
	 * @param m
	 *            The matrix to fill with v
	 * @param v
	 *            The value to fill m with
	 */
	static void fill(Number[][] m, Number v) {
		for (Number[] ns : m) {
			Arrays.fill(ns, v);
		}
	}

	static void fill(int[][] m, int v) {
		for (int[] ns : m) {
			Arrays.fill(ns, v);
		}
	}

	static void fill(float[][] m, float v) {
		for (float[] ns : m) {
			Arrays.fill(ns, v);
		}
	}

	/**
	 * @param s
	 *            The size
	 * @return Returns a array filled with values 1 of size s
	 */
	static Float[] initialPi(int s) {
		Float[] piVector = new Float[s];
		Arrays.fill(piVector, 1.0f);
		return piVector;
	}

	/**
	 * @param in
	 *            The double to classify
	 * @param node
	 *            The node to create the input vector for
	 * @return An array, currently with only one element activated: the one
	 *         corresponding to in
	 */
	static Float[] initialLambda(int size, int index) {
		// currently, only one element is observed to classify...
		Float[] lambdaVector = new Float[size];
		Arrays.fill(lambdaVector, 0.004f);
		lambdaVector[index] = 1.0f;
		lambdaVector = normalizeForSum(lambdaVector);
		return lambdaVector;
	}

	static String format(boolean[] activation2) {
		StringBuilder buf = new StringBuilder();
		for (boolean b : activation2) {
			buf.append(b ? "1" : "0");
		}
		return buf.toString();
	}

	public static String numberFormat(Number t, int digits) {
		DecimalFormat instance = (DecimalFormat) NumberFormat.getInstance();
		instance.setMinimumFractionDigits(digits);
		instance.setMaximumFractionDigits(digits);
		String format = instance.format(t);
		return format;
	}

	public static void printArray(Double[] vector) {
		for (double d : vector) {
			System.out.print(numberFormat(d, 6) + " ");
		}
	}

	public static void printArray(int[] vector) {
		for (int d : vector) {
			System.out.print(d + " ");
		}
	}

	public static void printArray(BigInteger[] vector) {
		for (BigInteger d : vector) {
			System.out.print(d + " ");
		}
	}

	public static String format(Float[] features2, int digits) {
		String s = "";
		for (Float d : features2) {
			s += numberFormat(d, digits) + " ";
		}
		return s.trim();
	}

	public static String format(float[] features2, int digits) {
		String s = "";
		for (float d : features2) {
			s += numberFormat(d, digits) + " ";
		}
		return s.trim();
	}

	public static String format(Double[] features2, int digits) {
		String s = "";
		for (double d : features2) {
			s += numberFormat(d, digits) + " ";
		}
		return s.trim();
	}
}
