package zero1hd.rhythmbullet.audio.analyzer;

import java.util.Observable;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.TimeUtils;

import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D;
import zero1hd.rhythmbullet.audio.processor.AudioProcessor;

public class DynamicAudioAnalyzer extends Observable implements Runnable {
	private volatile boolean work = true;
	private Thread thread;
	private String threadName = "analyzer";
	private final int WINDOWLENGTH = 1024;
	private AudioProcessor processor;
	private AudioAnalyzerSection[] sections;
	private FloatArray[] flux;
	private FloatArray[] threshold;
	private volatile int pUID = 0;
	private long timer;
	
	public DynamicAudioAnalyzer(AudioProcessor processor, AudioAnalyzerSection... sections) {
		this.sections = sections;
		this.processor = processor;
		flux = new FloatArray[sections.length];
		threshold = new FloatArray[sections.length];
		
		for (int section = 0; section < sections.length; section++) {
			flux[section] = new FloatArray();
			threshold[section] = new FloatArray();
		}
	}
	
	@Override
	public void run() {
		calculateSpectralFlux();
		calculateThreshold();
		calculatePeaks();
	}
	
	public void start() {
		if (thread == null) {
			thread = new Thread(this, threadName);
			thread.setDaemon(true);
			thread.start();
		} else {
			throw new IllegalStateException("Cannot have two analyzer threads.");
		}
	}
	
	public void stop() {
		work = false;
	}
	
	private void calculateSpectralFlux() {
		Gdx.app.debug("Spectral Flux Calculation", "Beginning...");
		timer = TimeUtils.millis();
		float[] audioPCM = new float[WINDOWLENGTH];
		float[] spectrum = new float[(WINDOWLENGTH/2)+1];
		float[] lastSpectrum = new float[spectrum.length];
		FloatFFT_1D fft = new FloatFFT_1D(WINDOWLENGTH);
		int windowsComplete = 0;
		int seedCurrentDigit = 0;

		int totalWindows = (int) processor.getSampleFrames()/WINDOWLENGTH;
		while (processor.readFrames(audioPCM) > 0 && work) {
			fft.realForward(audioPCM);
			
			//Building a PUID (Pseudo unique ID)
			if (windowsComplete == (seedCurrentDigit*totalWindows/9)) {
				float avg = 0;
				for (int frame = 0; frame < spectrum.length; frame++) {
					avg += spectrum[frame];
				}
				avg /= spectrum.length;
				if (avg < 0) {
					avg *= -1f;
				}
				pUID +=(int) Math.pow(10, 9-seedCurrentDigit) * ((int)(avg*1000f)-(int)(avg*100f)*10);
				seedCurrentDigit ++;
			}
			
			System.arraycopy(spectrum, 0, lastSpectrum, 0, spectrum.length);
			System.arraycopy(audioPCM, 0, spectrum, 0, spectrum.length);
			
			for (int section = 0; section < sections.length; section++) {
				float currentFlux = 0;
				for (int bin = sections[section].getLower(); bin < sections[section].getUpper(); bin++) {
					currentFlux += Math.max(0f, spectrum[bin] - lastSpectrum[bin]);
				}
				flux[section].add(currentFlux);
			}
			
			windowsComplete++;
		}
		
		for (int section = 0; section < sections.length; section++) {
			sections[section].setPUID(pUID);
		}
		
		Gdx.app.debug("Spectral Flux Calculation", "Finished. Took " + (TimeUtils.timeSinceMillis(timer)) + "ms");
	}
	
	private void calculateThreshold() {
		long subTimer = TimeUtils.millis();
		Gdx.app.debug("Threshold Calculation", "Beginning...");
		for (int section = 0; section < sections.length && work; section++) {
			FloatArray fluxArray = flux[section];
			for (int bin = 0; bin < fluxArray.size; bin++) {
				int range = sections[section].getThresholdRange();
				int start = Math.max(0, bin - range);
				int end = Math.min(fluxArray.size, bin + range);
				
				float average = 0;
				for (int pos = start; pos < end; pos++) {
					average += fluxArray.get(pos);
				}
				average /= (end - start);
				threshold[section].add(average);
			}
		}
		
		Gdx.app.debug("Spectral Flux Calculation", "Finished. Took " + (TimeUtils.timeSinceMillis(subTimer)) + "ms");
	}
	
	private void calculatePeaks() {
		long subTimer = TimeUtils.millis();
		Gdx.app.debug("Peak Calculation", "Beginning...");
		for (int section = 0; section < sections.length && work; section++) {
			FloatArray peaks = new FloatArray();
			
			for (int bin = 0; bin < threshold[section].size -1; bin++) {
				float prunedFlux = flux[section].get(bin) - threshold[section].get(bin);
				float prunedNextFlux = flux[section].get(bin + 1) - threshold[section].get(bin + 1);
				
				peaks.add((prunedFlux > prunedNextFlux) ? prunedFlux : 0);
			}
			
			sections[section].setPeaks(peaks);
		}
		Gdx.app.debug("Spectral Flux Calculation", "Finished. Took " + (TimeUtils.timeSinceMillis(subTimer)) + "ms. Total time was " + (TimeUtils.timeSinceMillis(timer)));

		setChanged();
		notifyObservers();
	}
}
