package dotnet54.measures.univariate;

import java.util.Random;

import dotnet54.measures.Measure;
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 TWE extends UnivariateMeasure {
	public static final String stiffnessParam = "nu";
	public static final String costParam = "lambda";
	protected double nu;
	protected double lambda;

	protected static final double[] twe_nuParams = { 0.00001, 0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1 };

	protected static final double[] twe_lamdaParams = { 0, 0.011111111, 0.022222222, 0.033333333, 0.044444444, 0.055555556, 0.066666667,
			0.077777778, 0.088888889, 0.1 };

	public TWE() {
		super(Measure.twe);
	}

	@Override
	public double distance(double[] series1, double[] series2, double bsf) {
		return distance(series1, series2, bsf, nu, lambda);
	}

	public synchronized double distance(double[] ta, double[] tb, double bsf, double nu, double lambda) {

		int m = ta.length;
		int n = tb.length;
		int maxLength = Math.max(m, n);
		
		double dist, disti1, distj1;

		int r = ta.length; // this is just m?!
		int c = tb.length; // so is this, but surely it should actually
				   // be n anyway
		
		int i, j;
		/*
		 * allocations in c double **D = (double **)calloc(r+1,
		 * sizeof(double*)); double *Di1 = (double *)calloc(r+1,
		 * sizeof(double)); double *Dj1 = (double *)calloc(c+1,
		 * sizeof(double)); for(i=0; i<=r; i++) { D[i]=(double
		 * *)calloc(c+1, sizeof(double)); }
		 */

		double[][]D = MemorySpaceProvider.getInstance(maxLength+1).getDoubleMatrix();
		double[]Di1 = MemorySpaceProvider.getInstance(maxLength+1).getDoubleArray();
		double[]Dj1 = MemorySpaceProvider.getInstance(maxLength+1).getDoubleArray();
//		double[][] D = MemoryManager.getInstance().getDoubleMatrix(0);
//		double[] Di1 = MemoryManager.getInstance().getDoubleArray(0);
//		double[] Dj1 = MemoryManager.getInstance().getDoubleArray(1);

		// FPH adding initialisation given that using matrices as fields
		Di1[0] = 0.0;
		Dj1[0] = 0.0;
		// local costs initializations
		for (j = 1; j <= c; j++) {
			distj1 = 0;
			if (j > 1) {
				// CHANGE AJB 8/1/16: Only use power of
				// 2 for speed
				distj1 += (tb[j - 2] - tb[j - 1]) * (tb[j - 2] - tb[j - 1]);
				// OLD VERSION
				// distj1+=Math.pow(Math.abs(tb[j-2][k]-tb[j-1][k]),degree);
				// in c:
				// distj1+=pow(fabs(tb[j-2][k]-tb[j-1][k]),degree);
			} else {
				distj1 += tb[j - 1] * tb[j - 1];
			}
			// OLD distj1+=Math.pow(Math.abs(tb[j-1][k]),degree);
			Dj1[j] = (distj1);
		}

		for (i = 1; i <= r; i++) {
			disti1 = 0;
			if (i > 1) {
				disti1 += (ta[i - 2] - ta[i - 1]) * (ta[i - 2] - ta[i - 1]);
			} // OLD
			  // disti1+=Math.pow(Math.abs(ta[i-2][k]-ta[i-1][k]),degree);
			else {
				disti1 += (ta[i - 1]) * (ta[i - 1]);
			}
			// OLD disti1+=Math.pow(Math.abs(ta[i-1][k]),degree);

			Di1[i] = (disti1);

			for (j = 1; j <= c; j++) {
				dist = 0;
				dist += (ta[i - 1] - tb[j - 1]) * (ta[i - 1] - tb[j - 1]);
				// dist+=Math.pow(Math.abs(ta[i-1][k]-tb[j-1][k]),degree);
				if (i > 1 && j > 1) {
					dist += (ta[i - 2] - tb[j - 2]) * (ta[i - 2] - tb[j - 2]);
				}
				// dist+=Math.pow(Math.abs(ta[i-2][k]-tb[j-2][k]),degree);
				D[i][j] = (dist);
			}
		} // for i

		// border of the cost matrix initialization
		D[0][0] = 0;
		for (i = 1; i <= r; i++) {
			D[i][0] = D[i - 1][0] + Di1[i];
		}
		for (j = 1; j <= c; j++) {
			D[0][j] = D[0][j - 1] + Dj1[j];
		}

		double dmin, htrans, dist0;

		for (i = 1; i <= r; i++) {
			for (j = 1; j <= c; j++) {
				htrans = Math.abs(i- j);
				if (j > 1 && i > 1) {
					htrans += Math.abs((i-1) - (j-1));
				}
				dist0 = D[i - 1][j - 1] + nu * htrans + D[i][j];
				dmin = dist0;
				if (i > 1) {
					htrans = 1;
				} else {
					htrans = i;
				}
				dist = Di1[i] + D[i - 1][j] + lambda + nu * htrans;
				if (dmin > dist) {
					dmin = dist;
				}
				if (j > 1) {
					htrans = 1;
				} else {
					htrans = j;
				}
				dist = Dj1[j] + D[i][j - 1] + lambda + nu * htrans;
				if (dmin > dist) {
					dmin = dist;
				}
				D[i][j] = dmin;
			}
		}

		dist = D[r][c];
		MemorySpaceProvider.getInstance().returnDoubleMatrix(D);
		MemorySpaceProvider.getInstance().returnDoubleArray(Di1);
		MemorySpaceProvider.getInstance().returnDoubleArray(Dj1);
		return dist;
	}
	
	public double get_random_nu(Dataset d, Random r) {
		double nu = twe_nuParams[r.nextInt(twe_nuParams.length)];
		return nu;
	} 	
	
	public double get_random_lambda(Dataset d, Random r) {
		double lambda = twe_lamdaParams[r.nextInt(twe_lamdaParams.length)];
		return lambda;
	}

	@Override
	public void initParamsByID(Dataset trainData, Random rand) {

	}

	@Override
	public void setParamsByID(int paramID, int seriesLength, Random rand) {
		setNu(twe_nuParams[paramID / 10]);
		setLambda(twe_lamdaParams[paramID % 10]);
	}

	@Override
	public void setRandomParamByID(Dataset trainData, Random rand) {
		int paramID = rand.nextInt(100 );
		setParamsByID(paramID, trainData.length(), rand);
	}

	@Override
	public void setRandomParams(Dataset trainData, Random rand) {
		setNu(getRandomNu(trainData, rand));
		setLambda(getRandomLambda(trainData, rand));
	}

	public static double getRandomNu(Dataset dataset, Random rand) {
		return twe_nuParams[rand.nextInt(twe_nuParams.length)];
	}

	public static double getRandomLambda(Dataset dataset, Random rand) {
		return twe_lamdaParams[rand.nextInt(twe_lamdaParams.length)];
	}

	public double getNu() {
		return nu;
	}

	public void setNu(double nu) {
		this.nu = nu;
	}

	public double getLambda() {
		return lambda;
	}

	public void setLambda(double lambda) {
		this.lambda = lambda;
	}

	@Override
	public String toString(){
		return "TWE[,n="+nu+",l="+lambda+","
				+super.toString()+",]";
	}
}
