#include "FModAudioProcessor.h"
#include "DFunc/DController.h"
#include "DFunc/debug.h"
#include "System/FMod/FModSystem.h"
#include "../Framework/Libraries/fftw3.h"	//for FFT computations
#include "Managers/Managers.h"
#include "Messages/Messages.h"

FModAudioProcessor::FModAudioProcessor()
{
	bb_up_counter 						= 0;
	bb_mid_up_counter 					= 0;
	bb_high_up_counter 					= 0;
	
	bass_cutoff 						= 6;
	mid_cutoff 							= 16;
	high_cutoff 						= 32;
	
	history_size 						= 20;
	decibel_cutoff 						= static_cast<float>(Config::Instance()->getdbCutoff());
	
	bass_threshold 						= 157.0f,
	mid_threshold 						= 130.0f,
	high_threshold 						= 80.0f;
	
	//setup fftw (C library, use custom malloc for bus aligned data)
	e_fft_buffer.resize(FModSystem::Instance()->spectrum_size, 0.0f);
	fftw_in 							= (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * FModSystem::Instance()->spectrum_size);
	fftw_out							= (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * FModSystem::Instance()->spectrum_size);
	fftw_p 								= fftw_plan_dft_1d(FModSystem::Instance()->spectrum_size, fftw_in, fftw_out, FFTW_FORWARD, FFTW_ESTIMATE);
	
	//only initialize if the system was setup properly
	if (	FModSystem::Instance()->getEnergySubBand().size() 	== FModSystem::Instance()->getBandSubDivisions() &&
			FModSystem::Instance()->getEnergyMean().size() 		== FModSystem::Instance()->getBandSubDivisions() &&
			FModSystem::Instance()->getEnergyVariance().size()	== FModSystem::Instance()->getBandSubDivisions() )
	{
		e_history.resize(FModSystem::Instance()->getBandSubDivisions());
		for (unsigned short i=0;i<FModSystem::Instance()->getBandSubDivisions();i++)
			e_history[i].resize(history_size);
	}
	else
	{
#		ifdef DEBUG
		LOG("FMod System incorrectly setup for FFT/Beat detect audio processor", CL_ERROR);
#		endif
		std::cerr << "FMod System incorrectly setup for FFT/Beat detect audio processor" << std::endl;
	}
#	ifdef DEBUG
//	DController::Instance()->addFloatSlideBar(&bass_threshold, 50, 200, "Bass Threshold");
//	DController::Instance()->addFloatSlideBar(&mid_threshold, 50, 200, "Midrange Threshold");
//	DController::Instance()->addFloatSlideBar(&high_threshold, 50, 200, "High Threshold");
#	endif
}

void FModAudioProcessor::processEvent(Event *event)
{
	
}

void FModAudioProcessor::process()
{
	//reset the up counters
	if (bb_up_counter > 0)
		bb_up_counter--;
		
	if (bb_mid_up_counter > 0)
		bb_mid_up_counter--;
		
	if (bb_high_up_counter > 0)
		bb_high_up_counter--;
	
	//get values for the fft
	for (unsigned int i=0;i<FModSystem::Instance()->spectrum_size;i++)
	{
		fftw_in[i][0] = FModSystem::Instance()->spectrum_buffer[i];
		fftw_in[i][1] = 0.0f;
	}
	fftw_execute(fftw_p);//do the fft

	//get the amplitude of the fft
	for (unsigned int i=0;i<FModSystem::Instance()->spectrum_size;i++)
		e_fft_buffer[i] = sqrt(pow(fftw_out[i][0], 2) + pow(fftw_out[i][1], 2));
	//collect the energy
	int j = 0;
	for (unsigned short i=0;i<FModSystem::Instance()->spectrum_size;i++)
	{
		j = i/(FModSystem::Instance()->spectrum_size/FModSystem::Instance()->band_sub_divisions);
		FModSystem::Instance()->e_subband[j] += e_fft_buffer[i] * 10.0f;//normalized
	}
	//add enerty to history
	for (unsigned short i=0;i<FModSystem::Instance()->band_sub_divisions;i++)
	{
		FModSystem::Instance()->e_subband[i] /= (FModSystem::Instance()->spectrum_size/FModSystem::Instance()->band_sub_divisions);
		e_history[i].push_back(FModSystem::Instance()->e_subband[i]);
	}
	//clear out the history, and re-compute the mean values
	for (unsigned short i=0;i<FModSystem::Instance()->band_sub_divisions;i++)
	{
		FModSystem::Instance()->e_mean[i] = 0.0f;
		while (e_history[i].size() > static_cast<unsigned int>(history_size))
			e_history[i].erase(e_history[i].begin());
		for (unsigned short j=0;j<static_cast<int>(history_size);j++)
			FModSystem::Instance()->e_mean[i] += e_history[i][j];
		FModSystem::Instance()->e_mean[i] /= history_size;
	}
	//calculate variance (or standard deviation)
	for (unsigned short i=0;i<FModSystem::Instance()->band_sub_divisions;i++)
	{
		FModSystem::Instance()->e_variance[i] = 0.0f;
		for (unsigned short j=0;j<e_history[i].size();j++)
			//threshold of 145 works best with variance, and 157 with standard deviation
			FModSystem::Instance()->e_variance[i] += fabs(e_history[i][j] - FModSystem::Instance()->e_mean[i]);			//standard deviation
		FModSystem::Instance()->e_variance[i] /= history_size;
	}

	unsigned short 	bass_count 	= 0,
					mid_count	= 0,
					high_count 	= 0;
	
	
	float 			low_mean 	= 0.0f,
					low_peak 	= 0.0f,
					mid_mean 	= 0.0f,
					mid_peak 	= 0.0f,
					high_mean 	= 0.0f,
					high_peak	= 0.0f;
	
	//check each energy peak against the threshold, and mean values.
	//if it's over the mean * threshold * constant then a beat is detected at
	//that sub-spectrum.  Count the beats in the super-sub-spectrum.
	for (unsigned short i=0;i<FModSystem::Instance()->band_sub_divisions;i++)
	{
		if (i < bass_cutoff)
		{
			low_mean += FModSystem::Instance()->e_mean[i];
			low_peak += FModSystem::Instance()->e_subband[i];
			if (FModSystem::Instance()->e_subband[i] > 
				(FModSystem::Instance()->e_variance[i]/FModSystem::Instance()->e_mean[i])
					+ (FModSystem::Instance()->e_mean[i]*(bass_threshold/100.0)))
			{
				bass_count++;
			}
		}
		else if (i < mid_cutoff)
		{
			mid_mean += FModSystem::Instance()->e_mean[i];
			mid_peak += FModSystem::Instance()->e_subband[i];
			if (FModSystem::Instance()->e_subband[i] > 
				(FModSystem::Instance()->e_variance[i]/FModSystem::Instance()->e_mean[i])
					+ (FModSystem::Instance()->e_mean[i]*(mid_threshold/100.0)))
			{
				mid_count++;
			}
		}
		else if (i < high_cutoff)
		{
			high_mean += FModSystem::Instance()->e_mean[i];
			high_peak += FModSystem::Instance()->e_subband[i];
			if (FModSystem::Instance()->e_subband[i] > 
				(FModSystem::Instance()->e_variance[i]/FModSystem::Instance()->e_mean[i])
					+ (FModSystem::Instance()->e_mean[i]*(high_threshold/100.0)))
			{
				high_count++;
			}
		}
	}
	
	//check counts of peaks in the super-sub spectrum. if enough were found, then
	//send out beat messages
	if (bb_up_counter == 0)
	{
		if (bass_count > bass_cutoff/2)
		{
			bb_up_counter = 1;
			bass_beat_energy = low_peak;// - low_mean;
			EntityManager::Instance()->newMessage(new MessageBeat(BASS_BEAT, bass_beat_energy));
		}
	}
	if (bb_mid_up_counter == 0)
	{
		if (mid_count > (mid_cutoff-bass_cutoff)/2)
		{
			bb_mid_up_counter = 1;
			mid_beat_energy = mid_peak;// - mid_mean;
			EntityManager::Instance()->newMessage(new MessageBeat(MID_BEAT, mid_beat_energy));
		}
	}
	if (bb_high_up_counter == 0)
	{
		if (high_count > (high_cutoff-mid_cutoff)/2)
		{
			bb_high_up_counter = 1;
			high_beat_energy = high_peak;// - high_mean;
			EntityManager::Instance()->newMessage(new MessageBeat(HIGH_BEAT, high_beat_energy));
		}
	}
	
}

void FModAudioProcessor::update()
{
	//update the spectrum in the system
	FMOD_ChannelGroup_GetSpectrum(FModSystem::Instance()->getInputChannelGroup(),
		FModSystem::Instance()->spectrum_buffer, FModSystem::Instance()->spectrum_size, 0, FMOD_DSP_FFT_WINDOW_TRIANGLE);
		
//	FMOD_ChannelGroup_GetSpectrum(FModSystem::Instance()->getInputChannelGroup(),
//		FModSystem::Instance()->getSpectrumRight(), FModSystem::Instance()->getSpectrumSize(), 1, FMOD_DSP_FFT_WINDOW_RECT);

	float max = 0.0f;
	for (unsigned short i=0;i<FModSystem::Instance()->getSpectrumSize();i++)
	{
		if (FModSystem::Instance()->getSpectrum()[i+1] > max)
			max = FModSystem::Instance()->getSpectrum()[i+1];
	}
//calculate decibels
	if (max > 0.001f)
	{
		for (int i=0;i<FModSystem::Instance()->getSpectrumSize()/2;i++)
		{
			float val, db;
			val = FModSystem::Instance()->spectrum_buffer[i+1];
			db = 10.0f  * static_cast<float>(log10(val)) * 2.0f;
			
			val = db;
			if (val < -decibel_cutoff)
			    val = -decibel_cutoff;
			
			val /= -static_cast<float>(decibel_cutoff);
			val = 1.0f - val;
			FModSystem::Instance()->db_spectrum_buffer[i] = val;
		}
	}
}

FModAudioProcessor::~FModAudioProcessor()
{
	fftw_destroy_plan(fftw_p);
	fftw_free(fftw_in); 
	fftw_free(fftw_out);
}
