#include <nsp.h>
#include <nspfft.h>
#include <nspwin.h>
#include <nspcvrt.h>
#include <string>
#include "main.h"

const int FFTSize=4096*2;
const int FFTSize2=FFTSize>>1;

#define StepSize				16

#define PEAKWINDOWSIZE	10
#define PEAKDIFF				20

#define MAXPEAKS 512

int nPeaks;
SPeak Peak[MAXPEAKS];

int nInputDataLen=0;
float *pInputData=NULL;
float *pFFTInput=NULL;
float *FFTData;

// analyzis-data
float fHiPeak;
float *LoFFTData;
float *HiFFTData;
float *AvgFFTData;

int Length2Order(int length)
{
  int order = 0;

  while(0 != length) {
    length >>= 1;
    order ++;
  }
  order--;
  return order;
}

signed short GetSample(SWaveHdr *pHdr, char *pData, unsigned long nSample, unsigned long nChannel)
{
	unsigned long nBytesPerSample=pHdr->BitsPerSample/8;
	unsigned long nSamples=pHdr->dwDSize/(nBytesPerSample*pHdr->wChnls);
	if (nSample>=nSamples)
		return 0;
	signed short w;
	memcpy(&w, &(pData[nSample*(nBytesPerSample*pHdr->wChnls)+(nChannel*nBytesPerSample)]), 2);
	if (pHdr->BitsPerSample==8)
		w<<=8;
	return w;
}

int CompareFFTIdx(const void *arg1, const void *arg2)
{
	SPeak f1=*((SPeak*)arg1);
	SPeak f2=*((SPeak*)arg2);
	if (f1.fAmp==f2.fAmp)
		return 0;
	if (f1.fAmp<f2.fAmp)
		return -1;
	return 1;
}

void CalcPeaks(std::string sFilename, std::string sOutput)
{
	SPeak PeakWnd[PEAKWINDOWSIZE];
	nPeaks=0;
	fHiPeak=0.0f;
	int b=0;
	int nWndSize;
	for (int i=0;i<FFTSize2;i++)
	{
		if (AvgFFTData[i]>fHiPeak)
			fHiPeak=AvgFFTData[i];
	}
	if (fHiPeak<=0.0f)
		return;
	while (b<FFTSize2)
	{
		nWndSize=PEAKWINDOWSIZE;
		if ((nWndSize+b)>=FFTSize2)
			nWndSize=FFTSize2-b;
		for (int j=0;j<nWndSize;j++)
		{
			PeakWnd[j].nIdx=b+j;
			PeakWnd[j].fAmp=AvgFFTData[b+j];
		}
		qsort((void*)PeakWnd, nWndSize, sizeof(SPeak), CompareFFTIdx);
		if ((PeakWnd[nWndSize-1].fAmp-PeakWnd[nWndSize>>1].fAmp)>PEAKDIFF)
		{
			if (((b+nWndSize)<FFTSize2) && (b>0))
			{
				if ((AvgFFTData[b+nWndSize]<PeakWnd[nWndSize-1].fAmp) && (AvgFFTData[b-1]<PeakWnd[nWndSize-1].fAmp))
				{
					PeakWnd[nWndSize-1].fAmp=PeakWnd[nWndSize-1].fAmp/fHiPeak;
					Peak[nPeaks++]=PeakWnd[nWndSize-1];
				}
			}
		}
		b+=nWndSize;
	}
	if (nPeaks)
	{
		printf("%i peaks or relevant pattern found...\n", nPeaks);
		// print out resulting-peaks
		FILE *pFile=fopen(sOutput.c_str(), "r+b");
		if (pFile)
		{
			fseek(pFile, 0, SEEK_END);
			unsigned char c=sFilename.length();
			fwrite(&c, 1, 1, pFile);
			for (i=0;i<c;i++)
			{
				unsigned char s=sFilename[i];
				fwrite(&s, 1, 1, pFile);
			}
			fwrite(&nPeaks, 4, 1, pFile);
			for (i=0;i<nPeaks;i++)
			{
				fwrite(&(Peak[i].nIdx), 4, 1, pFile);
				fwrite(&(Peak[i].fAmp), 4, 1, pFile);
	//			fprintf(pFile, "%03d: Position: %05d, Ratio: %5.5f%c%c", i+1, Peak[i].nIdx, Peak[i].fAmp, 13, 10);
			}
			fclose(pFile);
		}else
			printf("WARNING: Unable to open output file with write-permission !!!\n");
	}else
		printf("WARNING: No peaks or relevant pattern found !!!\n");
}

void Analyze(std::string sFilename, std::string sOutput)
{
	int ofs=0;
	int frame=0;
	while (ofs<nInputDataLen)
	{
		int p=ofs;
		for (int i=0;i<FFTSize;i++)
		{
			pFFTInput[i]=pInputData[p++];
			if (p>=nInputDataLen)
				p=0;
		}
		nspsWinHamming(pFFTInput, FFTSize);
		nspsRealFft(pFFTInput, Length2Order(FFTSize), NSP_Forw);
		nspcbMag((SCplx*)pFFTInput, FFTData, FFTSize2);
		// calc lo- and hi-fft's
		if (!frame)
		{
			memcpy(LoFFTData, FFTData, FFTSize2*sizeof(float));
			memcpy(HiFFTData, FFTData, FFTSize2*sizeof(float));
			memcpy(AvgFFTData, FFTData, FFTSize2*sizeof(float));
		}else
		{
			for (int i=0;i<FFTSize2;i++)
			{
				if (FFTData[i]<LoFFTData[i])
					LoFFTData[i]=FFTData[i];
				if (FFTData[i]>HiFFTData[i])
					HiFFTData[i]=FFTData[i];
				AvgFFTData[i]+=FFTData[i];
			}
		}
		frame++;
		ofs+=StepSize;
	}
	for (int i=0;i<FFTSize2;i++)
	{
		AvgFFTData[i]/=(float)frame;
	}
	CalcPeaks(sFilename, sOutput);
}

void Process(std::string sFilename, std::string sOutput)
{
	SWaveHdr WaveHeader;
	char *pData;
	std::string sName=sFilename+".wav";
	printf("Loading %s...\n", sName.c_str());
	FILE *pFile=fopen(sName.c_str(), "rb");
	if (!pFile)
		return;
	if (!fread(&WaveHeader, sizeof(WaveHeader), 1, pFile))
	{
		fclose(pFile);
		return;
	}
	pData=(char*)malloc(WaveHeader.dwDSize);
	if (!fread(pData, WaveHeader.dwDSize, 1, pFile))
	{
		free(pData);
		fclose(pFile);
		return;
	}
	fclose(pFile);

	nInputDataLen=(int)(WaveHeader.dwDSize/((WaveHeader.BitsPerSample/8)*WaveHeader.wChnls));
	pInputData=(float*)malloc(nInputDataLen*sizeof(float));
	for (int i=0;i<nInputDataLen;i++)
	{
		int w=0;
		for (int j=0;j<WaveHeader.wChnls;j++)
		{
			w+=GetSample(&WaveHeader, pData, i, j);
		}
		pInputData[i]=(float)w/(float)WaveHeader.wChnls/32768.0f;
	}
	free(pData);
	printf("Analyzing %s...\n", sName.c_str());
	while ((i=sFilename.find("\\"))!=-1)
	{
		char s[256];
		sFilename.copy(s, sFilename.length()-i-1, i+1);
		s[sFilename.length()-i-1]='\0';
		sFilename=s;
	}
	Analyze(sFilename, sOutput);
	free(pInputData);
}

void main(int argn, char *arg[])
{
	if (argn<2)
	{
		printf("ERROR: wrong argument-count !\n\n");
		printf("Syntax: CPAT [directory]\n");
		printf("Remarks:\n");
		printf("[directory] refers to the directory with all needed patterns (wav); '\\' at the end of [directory] needed.\n");
		return;
	}
	printf("Initializing...\n");
	nspsRealFft(NULL, Length2Order(FFTSize), NSP_Init);
	pFFTInput=(float*)calloc(FFTSize+2, sizeof(float));
	FFTData=(float*)calloc(FFTSize2, sizeof(float));
	LoFFTData=(float*)calloc(FFTSize2, sizeof(float));
	HiFFTData=(float*)calloc(FFTSize2, sizeof(float));
	AvgFFTData=(float*)calloc(FFTSize2, sizeof(float));

	printf("Parsing %s...\n\n", arg[1]);

	std::string sParsePath=arg[1];
	sParsePath+="*.wav";
	std::string sOutput=arg[1];
	sOutput+="phonemes.psf";
	FILE *pFile=fopen(sOutput.c_str(), "wb");
	if (pFile)
		fclose(pFile);
	WIN32_FIND_DATA FindData;
	HANDLE FindHandle=FindFirstFile(sParsePath.c_str(), &FindData);
	char pszFilename[256];
	if (FindHandle!=INVALID_HANDLE_VALUE)
	{
		do
		{
			std::string sFilename=arg[1];
			sFilename+=FindData.cFileName;
			int n=sFilename.find(".");
			sFilename.copy(pszFilename, n, 0);
			pszFilename[n]='\0';
			Process(pszFilename, sOutput);
			printf("\n");
		}while (FindNextFile(FindHandle, &FindData));
		FindClose(FindHandle);
	}
	pFile=fopen(sOutput.c_str(), "awb");
	if (pFile)
	{
		int n=0;
		fwrite(&n, 4, 1, pFile);
		fclose(pFile);
	}
	printf("Shutting down...\n");
  free(FFTData);
	free(pFFTInput);
	free(LoFFTData);
	free(HiFFTData);
	free(AvgFFTData);
	printf("Processing complete...\n");
}