package dotnet54.measures.multivariate;

import dotnet54.tscore.data.Dataset;

/**
 * Some classes in this package may contain borrowed code from the timeseriesweka project (Bagnall, 2017), 
 * we might have modified (bug fixes, and improvements for efficiency) the original classes.
 * 
 */

public class DistanceTools {

	public static int[] getInclusive10(int min, int max){
	        int[] output = new int[10];

	        double diff = (double)(max-min)/9;
	        double[] doubleOut = new double[10];
	        doubleOut[0] = min;
	        output[0] = min;
	        for(int i = 1; i < 9; i++){
	            doubleOut[i] = doubleOut[i-1]+diff;
	            output[i] = (int)Math.round(doubleOut[i]);
	        }
	        output[9] = max; // to make sure max isn't omitted due to double imprecision
	        return output;
	    }

	    public static double[] getInclusive10(double min, double max){
	        double[] output = new double[10];
	        double diff = (max-min)/9;
	        output[0] = min;
	        for(int i = 1; i < 9; i++){
	            output[i] = output[i-1]+diff;
	        }
	        output[9] = max;

	        return output;
	    }

	    public static int sim(double a, double b, double epsilon) {
			return (Math.abs(a - b) <= epsilon) ? 1 : 0;
		}

		//this algorithm is numerically unstable use Welford's algorithm
		public static double stdv_p(Dataset train) {
			double sumx = 0;
			double sumx2 = 0;
			double[] ins2array;
			for (int i = 0; i < train.size(); i++) {
				for (int d = 0; d < train.dimensions(); d++){
					ins2array = train.getSeries(i).data()[d];
					for (int j = 0; j < ins2array.length; j++) {
						// avoid
						// classVal
						sumx += ins2array[j];
						sumx2 += ins2array[j] * ins2array[j];
					}
				}
			}
			int n = train.size() * train.dimensions() * (train.length());
			double mean = sumx / n;
			return Math.sqrt(sumx2 / (n) - mean * mean);
		}

//	//this algorithm is numerically unstable use Welford's algorithm
//	// only works on univariate data
//	public static double stdv_p(List<double[]> train) {
//
//			double sumx = 0;
//			double sumx2 = 0;
//			double[] ins2array;
//			for (int i = 0; i < train.size(); i++) {
//				ins2array = train.get(i);
//				for (int j = 0; j < ins2array.length; j++) {
//										// avoid
//										// classVal
//					sumx += ins2array[j];
//					sumx2 += ins2array[j] * ins2array[j];
//				}
//			}
//			int n = train.size() * (train.get(0).length);
//			double mean = sumx / n;
//			return Math.sqrt(sumx2 / (n) - mean * mean); // this?? possible issue here
//
//		}
		
//		public static <C,D> double stdv_p(ArrayDataset<C,D> train) {
//
//			double sumx = 0;
//			double sumx2 = 0;
//			double[] ins2array;
//			for (int i = 0; i < train.size(); i++) {
//				ins2array = train.getPrimitiveDoubleArray(i);
//				for (int j = 0; j < ins2array.length - 1; j++) {// -1 to
//										// avoid
//										// classVal
//					sumx += ins2array[j];
//					sumx2 += ins2array[j] * ins2array[j];
//				}
//			}
//			int n = train.size() * (train.get(0).size());
//			double mean = sumx / n;
//			return Math.sqrt(sumx2 / (n) - mean * mean);
//
//		}		
		
		public final static double Min3(final double a, final double b, final double c) {
			return (a <= b) ? ((a <= c) ? a : c) : (b <= c) ? b : c;
		}

		public static int ArgMin3(final double a, final double b, final double c) {
			return (a <= b) ? ((a <= c) ? 0 : 2) : (b <= c) ? 1 : 2;
		}
		
		public static double sum(final double... tab) {
			double res = 0.0;
			for (double d : tab)
				res += d;
			return res;
		}
		
		public static double max(final double... tab) {
			double max = Double.NEGATIVE_INFINITY;
			for (double d : tab){
				if(max<d){
					max = d;
				}
			}
			return max;
		}
		
		public static double min(final double... tab) {
			double min = Double.POSITIVE_INFINITY;
			for (double d : tab){
				if(d<min){
					min = d;
				}
			}
			return min;
		}

	public static double[] getTimePoint(double[][] series, int index){
		double[] timePoint = new double[series.length];
		for (int dimension = 0; dimension < series.length; dimension++) {
			timePoint[dimension] = series[dimension][index];
		}
		return timePoint;
	}

	public static double[] getTimePoint(double[][] series, int index, double[] allocatedTimePoint){
		for (int dimension = 0; dimension < series.length; dimension++) {
			allocatedTimePoint[dimension] = series[dimension][index];
		}
		return allocatedTimePoint;
	}

	public static final double squaredDistanceScalar(double A, double B) {
		return (A - B) * (A - B);
	}

	public static final double squaredDistanceVector(double[] A, double[] B, int[] dimensionsToUse) {
		double total = 0;
		for (int dimension : dimensionsToUse) {
			total += ((A[dimension] - B[dimension]) * (A[dimension] - B[dimension]));
		}
		return total;
	}

	public static final double squaredDistanceVector(double[] A, double[][] B, int j, int[] dimensionsToUse) {
		double total = 0;
		for (int dimension : dimensionsToUse) {
			total += ((A[dimension] - B[dimension][j]) * (A[dimension] - B[dimension][j]));
		}
		return total;
	}

	public static final double squaredDistanceVector(double[][] A, double[][] B, int i, int j, int[] dimensionsToUse) {
		double total = 0;
		for (int dimension : dimensionsToUse) {
			total += ((A[dimension][i] - B[dimension][j]) * (A[dimension][i] - B[dimension][j]));
		}
		return total;
	}

	public static final double squaredDistanceVector(double[][] A, double scalar, int i, int[] dimensionsToUse) {
		double total = 0;
		for (int dimension : dimensionsToUse) {
			total += ((A[dimension][i] - scalar) * (A[dimension][i] - scalar));
		}
		return total;
	}

	public static double lpNorm(double[] vector, int norm, boolean getSumOnly){
		if (norm == 1){
			double total = 0;
			for (double element : vector) {
				// assumes all that elements are positive, strictly this is
				// total += Math.abs(element)
				total += element;
			}
			return total;
		}else if (norm == 2){
			double total = 0;
			for (double element : vector) {
				total += element * element;
			}
			if (getSumOnly){
				return total;
			}else{
				return Math.sqrt(total);
			}
		}else{
			double total = 0;
			for (double element : vector) {
				// assumes all that elements are positive, strictly this is
				// total += Math.abs(Math.pow(element, norm))
				total += Math.pow(element, norm);
			}
			if (getSumOnly) {
				return total;
			}else{
				return Math.pow(total, (double) 1/norm);
			}
		}
	}

	public static double lpNorm(double[] vector, int norm, boolean getSumOnly, int[] dimensionsToUse){
		if (norm == 1){
			double total = 0;
			for (int dimension : dimensionsToUse) {
				// assumes all that elements are positive, strictly this is
				// total += Math.abs(element)
				total += vector[dimension];
			}
			return total;
		}else if (norm == 2){
			double total = 0;
			for (int dimension : dimensionsToUse) {
				total += vector[dimension] * vector[dimension];
			}
			if (getSumOnly){
				return total;
			}else{
				return Math.sqrt(total);
			}
		}else{
			double total = 0;
			for (int dimension : dimensionsToUse) {
				// assumes all that elements are positive, strictly this is
				// total += Math.abs(Math.pow(element, norm))
				total += Math.pow(vector[dimension], norm);
			}
			if (getSumOnly) {
				return total;
			}else{
				return Math.pow(total, (double) 1/norm);
			}
		}
	}

	public static double vecDifference(double[] vectorA, double[] vectorB, int norm, boolean getSumOnly){
		int minLength = Math.min(vectorA.length, vectorB.length);
		if (norm == 1){
			double total = 0;
			for (int i = 0; i < minLength; i++) {
				total += Math.abs(vectorA[i] - vectorB[i]);
			}
			return total;
		}else if (norm == 2){
			double total = 0;
			double diff;
			for (int i = 0; i < minLength; i++) {
				diff = vectorA[i] - vectorB[i];
				total += diff * diff;
			}
			if (getSumOnly){
				return total;
			}else{
				return Math.sqrt(total);
			}
		}else{
			double total = 0;
			for (int i = 0; i < minLength; i++) {
				total += Math.pow(vectorA[i] - vectorB[i], norm);
			}
			if (getSumOnly) {
				return total;
			}else{
				return Math.pow(total, (double) 1/norm);
			}
		}
	}

	public static double vecDifference(double[][] series1, double[][] series2, int norm,
									   boolean getSumOnly, int[] dimensionsToUse){
		int minLength = Math.min(series1[0].length, series2[0].length);

		if (norm == 1){
			double total = 0;
			for (int i = 0; i < minLength; i++) {
				for (int dimension : dimensionsToUse) {
					total += Math.abs(series1[dimension][i] - series2[dimension][i]);
				}
			}
			return total;
		}else if (norm == 2){
			double total = 0;
			double diff;
			for (int i = 0; i < minLength; i++) {
				for (int dimension : dimensionsToUse) {
					diff = series1[dimension][i] - series2[dimension][i];
					total += diff * diff;
				}
			}
			if (getSumOnly){
				return total;
			}else{
				return Math.sqrt(total);
			}
		}else{
			double total = 0;
			for (int i = 0; i < minLength; i++) {
				for (int dimension : dimensionsToUse) {
					total += Math.pow(series1[dimension][i] - series2[dimension][i], norm);
				}
			}
			if (getSumOnly) {
				return total;
			}else{
				return Math.pow(total, (double) 1/norm);
			}
		}
	}


	public static double vecDifference(double[][] series1, double[][] series2,
									   int i, int j,
									   int norm, boolean getSumOnly, int[] dimensionsToUse){
		int minLength = Math.min(series1[0].length, series2[0].length);

		if (norm == 1){
			double total = 0;
			for (int dimension : dimensionsToUse) {
				total += Math.abs(series1[dimension][i] - series2[dimension][j]);
			}
			return total;
		}else if (norm == 2){
			double total = 0;
			double diff;
			for (int dimension : dimensionsToUse) {
				diff = series1[dimension][i] - series2[dimension][j];
				total += diff * diff;
			}
			if (getSumOnly){
				return total;
			}else{
				return Math.sqrt(total);
			}
		}else{
			double total = 0;
			for (int dimension : dimensionsToUse) {
				total += Math.pow(series1[dimension][i] - series2[dimension][j], norm);
			}
			if (getSumOnly) {
				return total;
			}else{
				return Math.pow(total, (double) 1/norm);
			}
		}
	}

}
