package dotnet54.classifiers.tschief.splitters.rise;

import dotnet54.tscore.exceptions.MultivariateDataNotSupportedException;
import dotnet54.tscore.data.MTimeSeries;
import dotnet54.tscore.data.TimeSeries;
import dotnet54.tscore.data.MTSDataset;

public class ACF {

	private boolean normalized = false; // Assumes zero mean and unit variance
	int endTerms = 4;
	int maxLag = 300;
	int seriesLength;

	public void setMaxLag(int n) {
		maxLag = n;
	}

	public void setNormalized(boolean flag) {
		normalized = flag;
	}

	public MTSDataset transform(MTSDataset input) throws Exception {
		int size = input.size();
		int length = input.length();

		if (input.isMultivariate()){
			throw new MultivariateDataNotSupportedException();
		}

		// TODO can do this better?
		maxLag = length / 4;
		if (maxLag > 100) {
			maxLag = 100;
		} else if (maxLag < 10) {
			maxLag = length;
		}
		
		// TODO check this again
		if (maxLag > input.length() - endTerms) // weka implementation use len + 1 - endTerms
			maxLag = input.length() - endTerms;
		if (maxLag < 0)
			maxLag = input.length();		

		MTSDataset output = new MTSDataset(size);

		for (int i = 0; i < size; i++) {
			//TODO using only dimension 1
			double[] acf = fitAutoCorrelations(input.getSeries(i).data()[0]);

			for (int j = 0; j < acf.length; j++) {
				if (acf[j] < -1.0 || acf[j] > 1 || Double.isNaN(acf[j]) || Double.isInfinite(acf[j])) {
					acf[j] = 0;
				}
			}
			TimeSeries ts = new MTimeSeries(acf, input.getClass(i));
			output.add(ts);
		}

		return output;
	}
	
	
	public double[] transformArray(double[] data) {
		double[] acf = fitAutoCorrelations(data);

		for (int j = 0; j < acf.length; j++) {
			if (acf[j] < -1.0 || acf[j] > 1 || Double.isNaN(acf[j]) || Double.isInfinite(acf[j])) {
				acf[j] = 0;
			}
		}

		return acf;
	}

	// reference: timeseriesweka source code
	public double[] fitAutoCorrelations(double[] data) {
		double[] a = new double[maxLag];

		if (!normalized) {
			for (int i = 1; i <= maxLag; i++) {
				double s1, s2, ss1, ss2, v1, v2;
				a[i - 1] = 0;
				s1 = s2 = ss1 = ss2 = 0;
				for (int j = 0; j < data.length - i; j++) {
					s1 += data[j];
					ss1 += data[j] * data[j];
					s2 += data[j + i];
					ss2 += data[j + i] * data[j + i];
				}
				s1 /= data.length - i;
				s2 /= data.length - i;
				for (int j = 0; j < data.length - i; j++)
					a[i - 1] += (data[j] - s1) * (data[j + i] - s2);
				a[i - 1] /= (data.length - i);

				v1 = ss1 / (data.length - i) - s1 * s1;
				v2 = ss2 / (data.length - i) - s2 * s2;

				a[i - 1] /= Math.sqrt(v1) * Math.sqrt(v2);
			}
		} else {
			for (int i = 1; i <= maxLag; i++) {
				a[i - 1] = 0;
				for (int j = 0; j < data.length - i; j++)
					a[i - 1] += data[j] * data[j + i];
				a[i - 1] /= data.length;
			}
		}
		return a;
	}

	public static double[] transformArray(double[] data, int mLag) {
		double[] a = new double[mLag];

		double s1, s2, ss1, ss2, v1, v2;
		for (int i = 1; i <= mLag; i++) {
			a[i - 1] = 0;
			s1 = s2 = ss1 = ss2 = 0;
			for (int j = 0; j < data.length - i; j++) {
				s1 += data[j];
				ss1 += data[j] * data[j];
				s2 += data[j + i];
				ss2 += data[j + i] * data[j + i];
			}
			s1 /= data.length - i;
			s2 /= data.length - i;

			for (int j = 0; j < data.length - i; j++)
				a[i - 1] += (data[j] - s1) * (data[j + i] - s2);
			a[i - 1] /= (data.length - i);
			v1 = ss1 / (data.length - i) - s1 * s1;
			v2 = ss2 / (data.length - i) - s2 * s2;
			a[i - 1] /= Math.sqrt(v1) * Math.sqrt(v2);
		}
		
		for (int j = 0; j < a.length; j++) {
			if (a[j] < -1.0 || a[j] > 1 || Double.isNaN(a[j]) || Double.isInfinite(a[j])) {
				a[j] = 0;
			}
		}
		
		return a;
	}

	
	public String toString() {
		return "ACF[max_lag=" + maxLag + "]";
	}
}
