package kr.util.ft;

import kr.util.NotificationManager;
import kr.util.audio.AudioClip;
import kr.util.sample.Utils;
import kr.gui.midigraph.ColoredScaleUI;
import kr.gui.midigraph.ColoredScaleUI.DataSet;

public class FFTScaleData implements ColoredScaleUI.ScaleData
{

	private static final double MAX_VAL = 127;
	private double[] freqs;
	private int fftSize;
	private byte[][] results;
	
	private double dataTimeSize; //the amount of time in micros of the entire array
	private int clipSize;

	/**
	 * @param fftSize size of each range considered, must be a power of 2
	 */
	public FFTScaleData(double [] freqs, int clipSize, int fftSize) {
		this.freqs = freqs;
		this.clipSize = clipSize;
		this.fftSize = fftSize;
	}

	public DataSet getDataSet(double microsPos) {
		int index = (int) Math.floor(microsPos * results.length / dataTimeSize);
		DataSet s = new DataSet();
		s.microsStart = (long) (dataTimeSize / results.length * index);
		s.microsEnd = (long) (dataTimeSize / results.length  * (index+1));
		if(index >=0 && index < results.length)
			s.noteValues = results[index];
		else s.noteValues = new byte[freqs.length];
		return s;
	}
	
	public void analyzeStereo(AudioClip ac)
	{
		int size = (int) (Math.ceil((double)(ac.length()) / clipSize));
		
		dataTimeSize = size * (double)clipSize / ac.getRate() * 1000000.;// in micros
		results = new byte [size][freqs.length];
		
		double [][] reArray = new double[2][fftSize];
		double [][] imArray = new double[2][fftSize];
		byte [] data = new byte[clipSize * 2];
		
		for(int i = 0; i < size; i++)
		{
			int copySize = (i+1)*clipSize > ac.length() ? ac.length() - i*clipSize : clipSize;

			ac.copyTo(i*clipSize, copySize, data, 0);

			//zero out data at the end
			while(copySize < clipSize) data[copySize++] = 0;
			
			for(int channel = 0; channel < 2; channel++)
			{
				double [] re = reArray[channel];
				double [] im = imArray[channel];
				
				for(int j = 0; j < fftSize; j++)
				{
					if(j < clipSize)
					{
						re[j] = data[j * 2 +channel];
						im[j] = 0;
					}
					else
					{
						re[j] = im[j] = 0;
					}
				}
				
				
				Utils.performFFT(re, im);
			}
			
			for(int j = 0; j < fftSize; j++)
			{
				double re1 = reArray[0][j];
				double im1 = imArray[0][j];
				double re2 = reArray[1][j];
				double im2 = imArray[1][j];
				
				reArray[0][j] = re1 * re2 - im1 * im2;
				imArray[0][j] = re1 * im2 + re2 * im1;
			}			
				
			double [] re = reArray[0];
			double [] im = imArray[0];

			int resultsFreqIndex = 0;
			int count = 0;
			double threshold = (freqs[0] + freqs[1]) / 2.;
			
			double [] currResults = new double[freqs.length];
						
			//go through the frequences that are in the scale
			for(int j = (int) (freqs[0] / (double)ac.getRate() * fftSize); j < fftSize; j++)
			{
				//get the frequency from the fft and the amplitude
				double freq = j * (double)ac.getRate() / fftSize;
				double v = Math.sqrt(re[j] * re[j] + im[j] * im[j]);
			
				//while the frequency is greater then the threshold
				//increase freqIndex for the results
				while(freq > threshold)
				{
					if(count > 0)
						currResults[resultsFreqIndex] /= count; //normalize by the number of frequencies we used
					count = 0;
					resultsFreqIndex ++;
					if(resultsFreqIndex < freqs.length-1)
						threshold = (freqs[resultsFreqIndex] + freqs[resultsFreqIndex+1]) / 2.;
					else if(resultsFreqIndex == freqs.length -1)
						threshold = freqs[resultsFreqIndex];
					else break;
				}
				
				if(resultsFreqIndex >= freqs.length) break;
				currResults[resultsFreqIndex] += v;
				count++;
			}//for each calculated frequency in a block
			
			if(count > 0)
				currResults[resultsFreqIndex-1] /= count;
			
			
			double maxVal = 0;
			
			for(int j=0; j < currResults.length; j++)
			{
				maxVal = Math.max(currResults[j], maxVal);
			}

			for(int j=0; j < currResults.length; j++)
			{
				results[i][j] = (byte) Math.round(currResults[j] * MAX_VAL / maxVal);
			}
		}//for each data block in audio file
	}

	public void analyze(AudioClip ac) {
		int size = (int) (Math.ceil((double)(ac.length()) / clipSize));
		
		dataTimeSize = size * (double)clipSize / ac.getRate() * 1000000.;// in micros
		results = new byte [size][freqs.length];
		
		double [] re = new double[fftSize];
		double [] im = new double[fftSize];
		byte [] data = new byte[clipSize];
		
		for(int i = 0; i < size; i++)
		{
//			if(i % 5 == 0)
				NotificationManager.inst().progress((double)i/size, "Analyzing frequencies...");
			int copySize = (i+1)*clipSize > ac.length() ? ac.length() - i*clipSize : clipSize;

			ac.copyTo(i*clipSize, copySize, data, 0);
			
//			//HACK
//			int runningBlanks = 0;
//			int startBlankIndex=0, endBlankIndex;
//			
//			for(int hack = 0; hack < fftSize; hack++)
//			{
//				if(Math.abs(data[hack]) < 3)
//				{
//					if(runningBlanks == 0)
//						startBlankIndex = i*fftSize + hack;
//					runningBlanks ++;
//				}
//				else {
//					if(runningBlanks > 10)
//					{
//						System.out.println(String.format("fft blank indexes %d - %d, micros %f - %f",
//								startBlankIndex,
//								startBlankIndex + runningBlanks,
//								(dataTimeSize / size  * (i)) + ((double)startBlankIndex - i*fftSize) / ac.getRate() * 1000000,
//								(dataTimeSize / size  * (i)) + ((double)startBlankIndex - i*fftSize + runningBlanks) / ac.getRate() * 1000000));
//					}
//					runningBlanks = 0;
//				}			
//			}
//			//END HACK
			
			//zero out data at the end
			while(copySize < clipSize) data[copySize++] = 0;
			
			for(int j = 0; j < fftSize; j++)
			{
				if(j < clipSize)
				{
					re[j] = data[j];
					im[j] = 0;
				}
				else
				{
					re[j] = im[j] = 0;
				}
			}
			
			
			Utils.performFFT(re, im);
			
			int resultsFreqIndex = 0;
			int count = 0;
			double threshold = (freqs[0] + freqs[1]) / 2.;
			
			double [] currResults = new double[freqs.length];
			
			//go through the frequences that are in the scale
			for(int j = (int) (freqs[0] / (double)ac.getRate() * fftSize); j < fftSize; j++)
			{
				//get the frequency from the fft and the amplitude
				double freq = j * (double)ac.getRate() / fftSize;
				double v = Math.sqrt(re[j] * re[j] + im[j] * im[j]);
			
				//while the frequency is greater then the threshold
				//increase freqIndex for the results
				while(freq > threshold)
				{
					if(count > 0)
						currResults[resultsFreqIndex] /= count; //normalize by the number of frequencies we used
					count = 0;
					resultsFreqIndex ++;
					if(resultsFreqIndex < freqs.length-1)
						threshold = (freqs[resultsFreqIndex] + freqs[resultsFreqIndex+1]) / 2.;
					else if(resultsFreqIndex == freqs.length -1)
						threshold = freqs[resultsFreqIndex];
					else break;
				}
				
				if(resultsFreqIndex >= freqs.length) break;
				currResults[resultsFreqIndex] += v;
				count++;
			}//for each calculated frequency in a block
			
			if(count > 0)
				currResults[resultsFreqIndex-1] /= count;
			
			double maxVal = 0;
			
			for(int j=0; j < currResults.length; j++)
			{
				maxVal = Math.max(currResults[j], maxVal);
			}

			for(int j=0; j < currResults.length; j++)
			{
				results[i][j] = (byte) Math.round(currResults[j] * MAX_VAL / maxVal);
			}
		}//for each data block in audio file
		
		NotificationManager.inst().finish();
	}

}
