code: package dotnet54.classifiers.tschief.splitters.st;
  
  import java.util.Arrays;
  import java.util.Comparator;
  import java.util.Random;
  
  import dotnet54.applications.tschief.TSChiefOptions;
  import dotnet54.tscore.data.TimeSeries;
  import dotnet54.tscore.data.UTimeSeries;
  import dotnet54.util.Util;
  
  public class ShapeletEx {
      public static final double ROUNDING_ERROR_CORRECTION = 0.000000000000001;
  
  	protected TimeSeries series;
  	protected int start;
  	protected int end;
  	protected int length;
  	protected boolean normalized = false;
  	
  	
  	protected double[] data = null;
  	protected int[] sorted_indices;  //speed up technique: Refer to Hills2014 and Rakthanmanon2012 
  
  	public ShapeletEx(TimeSeries series) {
  		this.series = series;
  		this.start = 0;
  		this.length = series.length();
  		this.end = this.length + 1;
  	}	
  	
  	public ShapeletEx(TimeSeries series, int start, int end, boolean copy, boolean normalize) {
  		this.series = series;
  		this.start = start; //start > 0 && start < end ? start: 0;
  		this.end = end; //end > start && end <= series.getLength() + 1 ? end: series.getLength();
  		
  		this.length = end - start; //assert : min length == 1, max length = series.length
  		
  		if (copy) {
  			//then copy
  			this.data = getData();
  		}
  		
  		if (normalize) {	//THEN ALWAYS COPY DATA
  			//then normalize the shapelet
  			this.data = normalize();
  			normalize();
  		}
  		
  		this.normalized = normalize;
  	}
  	
  	public String toString() {
  		return this.start + "," + this.end + ","  + this.length;
  	}
  	
  	public double[] copyData() {
  		//TODO using only dimension 1
  		return Arrays.copyOfRange(this.series.data()[0], this.start, this.end);
  	}	
  	
  	public double[] getData() {
  		
  		if (this.data == null) {
  			return copyData();
  		}
  		
  		return this.data;
  	}
  
  	//@reference
  	//code based on  from timeseriesweka code -- bakeoff paper
  	public double[] normalize() {
  		
  		if (this.data == null) {
  			this.data = getData();
  		}
  
  		double mean = 0;
  		double stdv = 0;
  		double temp = 0;
  		
  		
  		for (int i = 0; i < data.length; i++) {
  			temp+= data[i];
  		}
  		mean = temp / (double) data.length;	//temp = total
  		
  		temp = 0;
  		for (int i = 0; i < data.length; i++) {
  			temp = data[i] - mean;
  			stdv += temp * temp;
  		}
  		
  		stdv /= data.length;
  	    // if the variance is less than the error correction, just set it to 0, else calc stdv.
  	    stdv = (stdv < ROUNDING_ERROR_CORRECTION) ? 0.0 : Math.sqrt(stdv);
  		
  	    for (int i = 0; i < data.length; i++)
  	    {
  	        //if the stdv is 0 then set to 0, else normalise.
  	    	data[i] = (stdv == 0.0) ? 0.0 : ((data[i] - mean) / stdv);
  	    }
  		
  		this.normalized = true;
  		return this.data;
  	}
  	
  //	public void initRandomly(Random rand) {
  //		
  //		this.start = rand.nextInt(this.series.getLength());
  //		this.end = rand.nextInt(this.series.getLength());
  //		
  //		int temp;
  //		
  //		if (end < start) {
  //			temp = start;
  //			start = end;
  //			end = temp;
  //		}
  //		
  //		this.length = end - start;
  //	}
  	
  	//supports min and max length
  	
  	protected final int MAX_ITERATIONS = 10000;
  	
  	public void initRandomly(Random rand, String method, int minLength, int maxLength, int maxIterations) throws Exception {
  		int[] interval;
  		
  		if (method.equals("lengthfirst")) {
  			
  			interval = Util.getRandomIntervalSamplingLengthFirst(rand,
  					minLength, maxLength, this.series.length());
  			
  		}else if (method.equals("swap")) {
  			
  			interval = Util.getRandomIntervalUsingUniformPoints(rand,
  					minLength, maxLength, this.series.length(), maxIterations);
  			
  		}else {
  			throw new Exception("Unsupported random interval selection method");
  		}
  		
  		this.start = interval[0];
  		this.end = interval[1];
  
  		this.length = (end - start)+1;
  	}
  	
  //	public void initUsingPreLoadedSet(ShapeletTransformDataLoader stLoader, Random rand) throws Exception {
  //		ShapeletTransformDataLoader.STInfoRecord rec = stLoader.getRandomShapelet();
  //		
  ////		this.series = 
  //		this.start = rec.startPos;
  //		this.end = rec.startPos + rec.length;
  //		
  //	}
  
  	
  	
  	//returns the shapelet distance of this shapelet to the given series
  	public double distance(TimeSeries series) {
  		double total = 0;
  		double min_distance = Double.POSITIVE_INFINITY;
  		double[] seriesData = series.data()[0]; //TODO using only dimension 1
  
  		if (data == null) { //assume normalized == false
  			for (int i = 0; i < seriesData.length - this.length-1; i++) {
  				total = 0; 
  				for (int j = start; j < end ; j++){
  					//TODO using only dimension 1
  					total += ((seriesData[i] - series.data(j)[0]) * (seriesData[i] - series.data(j)[0]));
  				}	
  				if (total < min_distance) {
  					min_distance = total;
  				}
  			}	
  		}else {
  			
  			if (normalized) {
  				//TODO optimize this point--> memory allocations - can we reuse the arrays?
  				double copy[] = new double [data.length];
  				
  				for (int i = 0; i < seriesData.length - this.length +1; i++) {
  					total = 0; 
  					
  					//copy array
  					//System.arraycopy(seriesData, i, copy, 0, data.length);
  					copy = normalizeArray(seriesData, i, i+data.length-1, true);		
  					
  					for (int j = 0; j < data.length; j++){
  						total += ((copy[j] - data[j]) * (copy[j] - data[j]));
  					}	
  //					total = Math.sqrt(total);
  					if (total < min_distance) {
  						min_distance = total;
  					}
  				}
  			}else {
  				
  				for (int i = 0; i < seriesData.length - this.length-1; i++) {
  					total = 0; 
  					for (int j = 0; j < data.length; j++){
  						total += ((seriesData[i+j] - data[j]) * (seriesData[i+j] - data[j]));
  					}	
  					if (total < min_distance) {
  						min_distance = total;
  					}
  				}				
  			}
  			
  
  		}
  		
  		//TODO DEBUG CHECK 
  		if (min_distance == 0) {
  //			System.out.println("check min_distance == 0");
  			
  			double copy[] = new double [data.length];
  			
  			for (int i = 0; i < seriesData.length - this.length +1; i++) {
  				total = 0; 
  				
  				//copy array
  				//System.arraycopy(seriesData, i, copy, 0, data.length);
  				copy = normalizeArray(seriesData, i, i+data.length-1, true);		
  				
  				for (int j = 0; j < data.length; j++){
  					total += ((copy[j] - data[j]) * (copy[j] - data[j]));
  				}	
  //				total = Math.sqrt(total);
  				if (total < min_distance) {
  					min_distance = total;
  				}
  			}
  			
  		}
  		
  		//TODO refer to ImprovedOnlineSubSeqDistance class in bakeoff implementation
  		min_distance = (min_distance == 0.0) ? 0.0 : (1.0 / data.length * min_distance);
  		//double check above statement
  
  		return min_distance;
  	}
  	
  	//start and end are indices of the array
  	public double[] normalizeArray(double[] input, int start, int end, boolean copy) {
  
  		double mean = 0;
  		double stdv = 0;
  		double temp = 0;
  		double[] output;
  		
  		//TODO change this
  		if (copy || start != 0 || end != input.length) {
  			output = new double[end - start + 1];  //Arrays.copyOfRange(input, start, end);
  		}else {
  			output = input;
  		}
  		
  		
  		for (int i = 0; i < output.length; i++) {
  			temp+= input[start+i];
  		}
  		mean = temp / (double) output.length;	//temp = total
  		
  		temp = 0;
  		for (int i = 0; i < output.length; i++) {
  			temp = input[start+i] - mean;
  			stdv += temp * temp;
  		}
  		
  		stdv /= output.length;
  	    // if the variance is less than the error correction, just set it to 0, else calc stdv.
  	    stdv = (stdv < ROUNDING_ERROR_CORRECTION) ? 0.0 : Math.sqrt(stdv);
  		
  	    for (int i = 0; i < output.length; i++)
  	    {
  	        //if the stdv is 0 then set to 0, else normalise.
  	    	output[i] = (stdv == 0.0) ? 0.0 : ((input[start+i] - mean) / stdv);
  	    }
  		
  		return output;
  	}
  	
  	
  	//returns the shapelet distance of this shapelet to the given series
  	public double online_distance(UTimeSeries series) {
  		double[] T = series.getData();
  		double[] S = this.data;
  		double b = 0;
  		double d = 0;
  		double x = 0;
  		double s = 0;
  		
  		if (data == null) { //assume normalized == false
  			//TODO find SD without normalization
  		}else {
  			//algorithm in Hills2014
  			
  			//assume S is normalized already
  			
  			//sort indices
  			int[] A = sort_indices(S, true);
  			double[] F = normalizeArray(T, 0, S.length, true);
  			double p = 0;	//running rum	
  			double q = T.length;	//running sum of squares
  			b = dist_n(S, F, Double.POSITIVE_INFINITY, 1);
  			
  			for (int i = 0; i < T.length - S.length; i++) {
  				p = p - T[i];
  				q = q - (T[i] * T[i]);
  				
  				p = p - T[i+S.length];
  				q = q - (T[i+S.length] * T[i+S.length]);
  				
  				x = p/S.length;
  				s = q/S.length - (x*x);
  				d = 0;
  				
  				int j = 0;
  				while( j < S.length & d < b) { //TODO note d < b
  					double temp = S[A[j]] - ( (T[i + A[j]] - x)/s );
  					d = d + temp * temp;
  					j++;
  				}
  				
  				if (j == S.length & d <  b) {
  					b = d;
  				}
  				
  			}
  
  		}
  	
  		return b;
  	}
  	
  	
  
  	//S = shapelet/subseqies. T = full series;  assumes normalized S and T
  	public double dist_n(double[] S, double[] T, double bsf, int step) {
  		double min_dist = 0;
  		double total = 0;
  		int num_windows_evaluated = 0; //stats
  
  		for (int i = 0; i < T.length; i = i + step) {
  			total = 0; 			
  			num_windows_evaluated++;
  			for (int j = 0; j < S.length; j++) {
  				total += ((T[j] - S[j]) * (T[j] - S[j]));
  				if (min_dist < total) {
  					min_dist = total;
  				}
  				if (total > bsf) {
  					min_dist = total;
  					break;
  				}
  			}
  		}
  		
  		
  		return min_dist;	// note; not returning sqr root
  	}
  	
  	
  //	//S = shapelet/subseqies. T = full series
  //	public double dist(double[] S, double[] T) {
  //		return dist(S, T, Double.POSITIVE_INFINITY, 1);
  //	}
  //	
  //	//S = shapelet/subseqies. T = full series
  //	public double dist(double[] S, double[] T, double bsf, int step_size) {
  //		double min_dist = 0;
  //		double total = 0;
  //		int num_windows_evaluated = 0; //stats
  //
  //		for (int i = 0; i < T.length - S.length; i = i + step_size) {
  //			total = 0; 
  //			num_windows_evaluated++;
  //			for (int j = 0; j < S.length; j++) {
  //				total += ((T[i+j] - S[j]) * (T[i+j] - S[j]));
  //				if (min_dist < total) {
  //					min_dist = total;
  //				}
  //				if (total > bsf) {
  //					min_dist = total;
  //					break;
  //				}
  //			}
  //		}
  //		
  //		
  //		return min_dist;
  //	}
  	
  	
  	//returns sorted indices of array items -- only ascending implemented -- inefficient due to int and Integer conversions
  	public int[] sort_indices(double[] array, boolean ascending) {
  		
  		final Integer[] idx = new Integer[array.length];
  
  		for (int i = 0; i < array.length; i++) {
  			idx[i] = i;
  		}
  		
  		Arrays.sort(idx, new Comparator<Integer>() {
  		    @Override public int compare(final Integer o1, final Integer o2) {
  		        return - Double.compare(array[o1], array[o2]); //reverse
  		    }
  		});
  		
  		return Arrays.stream(idx).mapToInt(Integer::intValue).toArray();
  	}
  	
  }
  