/*
 * ----------------------------------------------------------------
 *
 *		ITA core libs
 *		(c) Copyright Institute of Technical Acoustics (ITA)
 *		RWTH Aachen University, Germany, 2015-2024
 *
 * ----------------------------------------------------------------
 *				    ____  __________  _______
 *				   //  / //__   ___/ //  _   |
 *				  //  /    //  /    //  /_|  |
 *				 //  /    //  /    //  ___   |
 *				//__/    //__/    //__/   |__|
 *
 * ----------------------------------------------------------------
 *
 * Benchmark uniform partitioned convolution
 *
 */

#include <ITAAudiofileWriter.h>
#include <ITAConfigUtils.h>
#include <ITAFileDataSource.h>
#include <ITASampleBuffer.h>
#include <ITASampleFrame.h>
#include <ITAStopWatch.h>
#include <ITAStreamFunctionGenerator.h>
#include <ITAStreamInfo.h>
#include <ITAStringUtils.h>
#include <ITAUPConvolution.h>
#include <ITAUPFilter.h>
#include <iostream>
#include <math.h>
#include <stdio.h>
#include <vector>

using namespace std;

ITASampleBuffer* pbfImpulse;

struct ParameterSet
{
	unsigned int iBlockLength;
	double dSampleRate;
	string sWAVOutFilePath;
	unsigned long iNumFrames;
	int iFilterLength;
	int iScalingApproximation;

	inline void SetSomeDefaults( )
	{
		dSampleRate = 44.1e3;
#ifdef DEBUG
		iNumFrames = 1000;
#else
		iNumFrames = 1e2;
#endif
		iScalingApproximation = 64;
	};

	inline void WriteToINIFile( ) const
	{
		INIFileWriteInt( "BlockLength", iBlockLength );
		INIFileWriteDouble( "SampleRate", dSampleRate );
		INIFileWriteInt( "FilterLength", iFilterLength );
		INIFileWriteInt( "NumFrames", iNumFrames );
		INIFileWriteString( "WAVOutFilePath", sWAVOutFilePath );
		INIFileWriteDouble( "BlockTimeSeconds", iBlockLength / dSampleRate );
		INIFileWriteString( "BlockTime", timeToString( iBlockLength / dSampleRate ) );
		INIFileWriteInt( "ScalingApproximation", iScalingApproximation );
	};
};

void run_benchmark( const ParameterSet&, const std::string& );

int main( int, char** )
{
	pbfImpulse = new ITASampleBuffer( int( 1000 * 44.1e3 ), true );

	srand( 100 );
	int iLength = pbfImpulse->GetLength( );
	for( unsigned int i = 0; i < iLength; i++ )
		pbfImpulse->GetData( )[i] = (float)rand( ) / RAND_MAX;


	cout << "Starting uniformly partitioned block convolution benchmark" << endl;

	INIFileUseFile( "ITAConvolution_BM_UPConv.ini" );

	ParameterSet pm;
	pm.SetSomeDefaults( );

	for( auto BL: { 32, 64, 128, 256 } )
	{
		for( auto BM: { 1, 2, 4, 8, 16 } )
		{
			stringstream ss;
			ss << "ITAConvolution_BM_NUPConv_L" << BL << "_M" << BL * BM;
			string sBMID       = ss.str( );
			pm.iBlockLength    = BL;
			pm.iFilterLength   = BL * BM;
			pm.sWAVOutFilePath = sBMID + ".wav";
			cout << "\tStarting " << sBMID << " ";
			// run_benchmark( pm, sBMID );
			cout << "\tdone." << endl;
		}
	}


	INIFileUseFile( "ITAConvolution_BM_UPConv_ScalingBehavior.ini" );

	for( auto BL: { 256 } )
	{
		for( auto BM: { 1, 2, 4, 8, 16, 32, 64, 128, 512, 1024, 2048, 4096, 8182 } )
		{
			stringstream ss;
			ss << "ITAConvolution_BM_NUPConv_ScalingBehavior_L" << BL << "_M" << BL * BM;
			string sBMID       = ss.str( );
			pm.iBlockLength    = BL;
			pm.iFilterLength   = BL * BM;
			pm.sWAVOutFilePath = sBMID + ".wav";
			cout << "\tStarting " << sBMID << " ";
			run_benchmark( pm, sBMID );
			cout << "\tdone." << endl;
		}
	}


	cout << "All done." << endl;

	return 255;
}

void run_benchmark( const ParameterSet& pm, const std::string& sName )
{
	INIFileUseSection( sName );
	pm.WriteToINIFile( );

	ITAStreamFunctionGenerator sinesignal( 1, pm.dSampleRate, pm.iBlockLength, ITAStreamFunctionGenerator::SINE, 500.0f, 0.9f, true );
	ITADatasource* pIntputStream = &sinesignal;

	ITAUPConvolution* pFIRFilterEnginge = new ITAUPConvolution( pm.iBlockLength, pm.iFilterLength );
	ITAUPFilter* pFIRFilter             = pFIRFilterEnginge->RequestFilter( );
	;
	pFIRFilter->Load( pbfImpulse->GetData( ), std::min( pbfImpulse->GetLength( ), pm.iFilterLength ) );
	pFIRFilterEnginge->ExchangeFilter( pFIRFilter );
	pFIRFilter->Release( );

	ITAAudiofileProperties props_out;
	props_out.iChannels            = 1;
	props_out.dSampleRate          = pm.dSampleRate;
	props_out.eQuantization        = ITAQuantization::ITA_FLOAT;
	props_out.eDomain              = ITADomain::ITA_TIME_DOMAIN;
	props_out.iLength              = pm.iNumFrames * (unsigned int)( pm.iBlockLength );
	props_out.iChannels            = 1;
	ITAAudiofileWriter* writer_out = ITAAudiofileWriter::create( pm.sWAVOutFilePath, props_out );

	ITAStreamInfo oState;

	ITASampleBuffer* psbInput = new ITASampleBuffer( pm.iBlockLength, true );
	ITASampleFrame sfTemp( 1, pm.iBlockLength, true );

	ITAStopWatch swBenchmark;

	long unsigned int n = 0;
	while( n < int( pm.iNumFrames ) )
	{
		// Add new samples
		psbInput->write( pIntputStream->GetBlockPointer( 0, &oState ), pm.iBlockLength );

		swBenchmark.start( );
		pFIRFilterEnginge->Process( psbInput->GetData( ), sfTemp[0].GetData( ) );
		swBenchmark.stop( );

		INIFileWriteDouble( "ComputationMean", swBenchmark.mean( ) );
		INIFileWriteDouble( "ComputationStdDev", swBenchmark.std_deviation( ) );
		INIFileWriteDouble( "ComputationMinimum", swBenchmark.minimum( ) );
		INIFileWriteDouble( "ComputationMaximum", swBenchmark.maximum( ) );
		INIFileWriteDouble( "ComputationScalingApproximation", swBenchmark.mean( ) * pm.iScalingApproximation );

		writer_out->write( &sfTemp, sfTemp.GetLength( ) );
		n++;

		pIntputStream->IncrementBlockPointer( );

		if( n % ( pm.iNumFrames / 10 ) == 0 )
			cout << ".";
	}

	delete writer_out;

	delete psbInput;
}