//Copyright 2015 <>< Charles Lohr under the ColorChord License.

#include <ctype.h>
#include "color.h"
#include <math.h>
#include <stdio.h>
#include "sound.h"
#include "os_generic.h"
#include "DrawFunctions.h"
#include "dft.h"
#include "filter.h"
#include "decompose.h"
#include <stdlib.h>
#include <string.h>
#include "notefinder.h"
#include "outdrivers.h"
#include "parameters.h"
#include "hook.h"
#include "configs.h"


struct SoundDriver * sd;

#ifdef WIN32
#include <windows.h>
void WindowsTerm()
{
	CloseSound( sd );
}

#endif

float DeltaFrameTime = 0;
double Now = 0;

int lastfps;
short screenx, screeny;
struct DriverInstances * outdriver[MAX_OUT_DRIVERS];

int   SoundRawLength;
int   SoundRawChannels;
float * SoundRawData; //SoundRawChannels*SoundRawLength

float SoundPictureHz;
int     SoundPictureStacks;
int		SoundPictureBins;
float * SoundPicture;       // [SoundPictureStacks * SoundPictureBins]

int headless = 0;		REGISTER_PARAM( headless, PAINT );
int set_screenx = 640;	REGISTER_PARAM( set_screenx, PAINT );
int set_screeny = 480;	REGISTER_PARAM( set_screeny, PAINT );
char sound_source[16]; 	REGISTER_PARAM( sound_source, PABUFFER );
int cpu_autolimit = 1; 	REGISTER_PARAM( cpu_autolimit, PAINT );
int sample_channel = -1;REGISTER_PARAM( sample_channel, PAINT );

struct NoteFinder * nf;

//Sound circular buffer
#define SOUNDCBSIZE 8096
#define MAX_CHANNELS 2

double VisTimeEnd, VisTimeStart;
int soundhead = 0;
float sound[SOUNDCBSIZE];
int show_debug = 0;
int show_debug_basic = 1;

int gKey = 0;
extern int force_white;

void HandleKey( int keycode, int bDown )
{
	char c = toupper( keycode );
	if( c == 'D' && bDown ) show_debug = !show_debug;
	if( c == 'W' ) force_white = bDown;
	if( c == '9' && bDown ) { gKey--; 		nf->base_hz = 55 * pow( 2, gKey / 12.0 ); ChangeNFParameters( nf ); }
	if( c == '-' && bDown ) { gKey++; 		nf->base_hz = 55 * pow( 2, gKey / 12.0 ); ChangeNFParameters( nf ); }
	if( c == '0' && bDown ) { gKey = 0;		nf->base_hz = 55 * pow( 2, gKey / 12.0 ); ChangeNFParameters( nf ); }
	if( c == 'E' && bDown ) show_debug_basic = !show_debug_basic;
	if( c == 'K' && bDown ) DumpParameters();
	if( keycode == 65307 ) exit( 0 );
	printf( "Key: %d -> %d\n", keycode, bDown );
	KeyHappened( keycode, bDown );
}

void HandleButton( int x, int y, int button, int bDown )
{
	printf( "Button: %d,%d (%d) -> %d\n", x, y, button, bDown );
}

void HandleMotion( int x, int y, int mask )
{
}

void SoundCB( float * out, float * in, int samplesr, int * samplesp, struct SoundDriver * sd )
{
	int channelin = sd->channelsRec;
//	int channelout = sd->channelsPlay;
	//*samplesp = 0;
//	int process_channels = (MAX_CHANNELS < channelin)?MAX_CHANNELS:channelin;

	//Load the samples into a ring buffer.  Split the channels from interleved to one per buffer.

	int i;
	int j;

	for( i = 0; i < samplesr; i++ )
	{
		if( out )
		{
			for( j = 0; j < channelin; j++ )
			{
				out[i*channelin+j] = 0;
			}
		}

		if( sample_channel < 0 )
		{
			float fo = 0;
			for( j = 0; j < channelin; j++ )
			{
				float f = in[i*channelin+j];
				if( f >= -1 && f <= 1 )
				{
					fo += f;
				}
				else
				{
					fo += (f>0)?1:-1;
//					printf( "Sound fault A %d/%d %d/%d %f\n", j, channelin, i, samplesr, f );
				}
			}

			fo /= channelin;
			sound[soundhead] = fo;
			soundhead = (soundhead+1)%SOUNDCBSIZE;
		}
		else
		{
			float f = in[i*channelin+sample_channel];

			if( f > 1 || f < -1 )
			{ 	
				f = (f>0)?1:-1;
			}

			//printf( "Sound fault B %d/%d\n", i, samplesr );
			sound[soundhead] = f;
			soundhead = (soundhead+1)%SOUNDCBSIZE;

		}
	}

	SoundEventHappened( samplesr, in, 0, channelin );
	if( out )
	{
		SoundEventHappened( samplesr, out, 1, sd->channelsPlay );
	}
	*samplesp = samplesr;
}

void LoadRaw()
{
	const char * rawfilename = GetParameterS( "rawfilename", 0 );
	if( !rawfilename )
	{
		fprintf( stderr, "Error: This application requires the 'rawfilename' parameter.\n" );
		exit( -19 );
	}
	FILE * f = fopen( rawfilename, "rb" );
	if( !f )
	{
		fprintf( stderr, "Error: Can't open the specified rawfile. %s\n", rawfilename );
		exit( -20 );
	}

	int SoundRawChannels = MAX_CHANNELS;
	fseek( f, 0, SEEK_END );
	SoundRawLength = ftell( f ) / SoundRawChannels / 4;
	fseek( f, 0, SEEK_SET );

	SoundRawData = malloc( SoundRawChannels * 4 * SoundRawLength );
	int r = fread( SoundRawData, SoundRawChannels * 4, SoundRawLength, f );
	fclose( f );

	int samplerate = GetParameterI( "samplerate", 44100 );
	SoundPictureHz = GetParameterF( "initmapper_width_hz", 120 );
	float fSoundMapMux =  GetParameterF( "sound_map_mux", 20 );
	int iBins = GetParameterI( "freqbins", 24 );
	float SoundPictureTime = samplerate / SoundPictureHz;

	int SoundPictureStacks = (SoundRawLength / SoundPictureTime) + 1;
	SoundPictureBins = nf->octaves * nf->freqbins;
	SoundPicture = malloc( ( SoundPictureStacks + 1 ) * SoundPictureBins * 4);

	int i;
	int place = 0;

	int sbs = (int)SoundPictureTime + 4;
	int sbp = 0;
	float IntSoundBuffer[sbs];


	FILE * fr = fopen( "folded_data.txt", "w" );

	for( i = 0; i < SoundPictureStacks; i++ )
	{
		int nplace = ((int)(SoundPictureTime * (i+1)))&0xffffffe;
		int j = 0;
		//float SoundLump[nplace-place];
		for( ; place < nplace; place++, j++ )
		{
			float fo = 0;
			int k;
			if( place >= SoundRawLength ) break;
			for( k = 0; k < SoundRawChannels; k++ )
			{
				float f = SoundRawData[place*SoundRawChannels+k];
				fo += f;
			}
			place++;
			fo /= SoundRawChannels;
			IntSoundBuffer[sbp++] = fo;
			if( sbp == sbs ) sbp = 0;
		}

		//XXX WARNING: apparently we actually have to use a circular buffer or something

		float backup = nf->dft_iir;
		nf->dft_iir = 0;
		//printf( "%d %d  -- %d %d\n", sbp, sbs, nplace, place );
		RunNoteFinder( nf, IntSoundBuffer, sbp, sbs );
		nf->dft_iir = backup;
//		printf( "NF On %d\n", j );
		for( j = 0; j < SoundPictureBins; j++ )
		{
			SoundPicture[i*SoundPictureBins+j] = nf->outbins[j];
		}

		for( j = 0; j < nf->freqbins; j++ )
		{
			fprintf( fr, "%f ", nf->folded_bins[j] );
		}
		fprintf( fr, "\n" );
	}

	fclose( fr );

	FILE * pic = fopen( "pic.pgm", "wb" );
	fprintf( pic, "P6\n%d %d\n255\n", SoundPictureStacks, SoundPictureBins );
	int x, y;
	for( y = SoundPictureBins-1; y >= 0; y-- )
	{
		for( x = 0; x < SoundPictureStacks; x++ )
		{
			float pf = SoundPicture[x*SoundPictureBins+y]*fSoundMapMux;
			if( pf > 1 ) pf = 1;
			if( pf < 0 ) pf = 0;

			//int c = (int)(pf * 255. + 0.5);
			//putc( c, pic );

			int r = CCtoHEX( y/(float)iBins, 1.0, pf );

			putc( r, pic );
			putc( r>>8, pic );
			putc( r>>16, pic );
		}
	}
	fclose( pic );



}

int main(int argc, char ** argv)
{
	int i;

	printf( "Output Drivers:\n" );
	for( i = 0; i < MAX_OUT_DRIVERS; i++ )
	{
		if( ODList[i].Name ) printf( "\t%s\n", ODList[i].Name );
	}

#ifdef WIN32
    WSADATA wsaData;

    WSAStartup(0x202, &wsaData);

	strcpy( sound_source, "WIN" );
#else
	strcpy( sound_source, "PULSE" );
#endif

	gargc = argc;
	gargv = argv;

	SetupConfigs();

	//Initialize Rawdraw
	int frames = 0;
	double ThisTime;
	double LastFPSTime = OGGetAbsoluteTime();
	double LastFrameTime = OGGetAbsoluteTime();
	double SecToWait;
	CNFGBGColor = 0x800000;
	CNFGDialogColor = 0x444444;

	char title[1024];
	char * tp = title;

	memcpy( tp, "ColorChord ", strlen( "ColorChord " ) );
	tp += strlen( "ColorChord " );

	for( i = 1; i < argc; i++ )
	{
		memcpy( tp, argv[i], strlen( argv[i] ) );
		tp += strlen( argv[i] );
		*tp = ' ';
		tp++;
	}
	*tp = 0;
	if( !headless )
		CNFGSetup( title, set_screenx, set_screeny );


	char * OutDriverNames = strdup( GetParameterS( "outdrivers", "null" ) );
	char * ThisDriver = OutDriverNames;
	char * TDStart;
	for( i = 0; i < MAX_OUT_DRIVERS; i++ )
	{
		while( *ThisDriver == ' ' || *ThisDriver == '\t' ) ThisDriver++;
		if( !*ThisDriver ) break;

		TDStart = ThisDriver;

		while( *ThisDriver != 0 && *ThisDriver != ',' )
		{
			if( *ThisDriver == '\t' || *ThisDriver == ' ' ) *ThisDriver = 0;
			ThisDriver++;
		}

		if( *ThisDriver )
		{
			*ThisDriver = 0;
			ThisDriver++;
		}
	
		printf( "Loading: %s\n", TDStart );
		outdriver[i] = SetupOutDriver( TDStart );
	}
	free(OutDriverNames);


	//Initialize Sound
	sd = InitSound( sound_source, &SoundCB );

	if( !sd )
	{
		fprintf( stderr, "ERROR: Failed to initialize sound output device\n" );
		return -1;
	}

	nf = CreateNoteFinder( sd->spsRec );

	//Once everything was reinitialized, re-read the ini files.
	SetEnvValues( 1 );

	Now = OGGetAbsoluteTime();
	double Last = Now;

	LoadRaw();


	while(1)
	{
		char stt[1024];
		//Handle Rawdraw frame swappign

		Now = OGGetAbsoluteTime();
		DeltaFrameTime = Now - Last;

		if( !headless )
		{
			CNFGHandleInput();
			CNFGClearFrame();
			CNFGColor( 0xFFFFFF );
			CNFGGetDimensions( &screenx, &screeny );
		}

		RunNoteFinder( nf, sound, (soundhead-1+SOUNDCBSIZE)%SOUNDCBSIZE, SOUNDCBSIZE );
		//Done all ColorChord work.


		VisTimeStart = OGGetAbsoluteTime();

		for( i = 0; i < MAX_OUT_DRIVERS; i++ )
		{

			if( force_white )
			{
				memset( OutLEDs, 0x7f, MAX_LEDS*3 );
			}

			if( outdriver[i] )
				outdriver[i]->Func( outdriver[i]->id, nf );
		}

		VisTimeEnd = OGGetAbsoluteTime();


		if( !headless )
		{
			//Handle outputs.
			int freqbins = nf->freqbins;
			int note_peaks = freqbins/2;
			int freqs = freqbins * nf->octaves;
			//int maxdists = freqbins/2;

			//Do a bunch of debugging.
			if( show_debug_basic )
			{
				//char sttdebug[1024];
				//char * sttend = sttdebug;

				for( i = 0; i < nf->dists_count; i++ )
				{
					CNFGPenX = (nf->dists[i].mean + 0.5) / freqbins * screenx;  //Move over 0.5 for visual purposes.  The means is correct.
					CNFGPenY = 400-nf->dists[i].amp * 150.0 / nf->dists[i].sigma;
					//printf( "%f %f\n", dists[i].mean, dists[i].amp );
					sprintf( stt, "%f\n%f\n", nf->dists[i].mean, nf->dists[i].amp );
//					sttend += sprintf( sttend, "%f/%f ",nf->dists[i].mean, nf->dists[i].amp );
					CNFGDrawText( stt, 2 );
				}
				CNFGColor( 0xffffff );

				//Draw the folded bins
				for( i = 0; i < freqbins; i++ )
				{
					float x0 = i / (float)freqbins * (float)screenx;
					float x1 = (i+1) / (float)freqbins * (float)screenx;
					float amp = nf->folded_bins[i] * 250.0;
					CNFGDialogColor = CCtoHEX( ((float)(i+0.5) / freqbins), 1.0, 1.0 );
					CNFGDrawBox( x0, 400-amp, x1, 400 );
				}
				CNFGDialogColor = 0xf0f000;

				for( i = 0; i < note_peaks; i++ )
				{
					//printf( "%f %f /", note_positions[i], note_amplitudes[i] );
					if( nf->note_amplitudes_out[i] < 0 ) continue;
					CNFGDialogColor = CCtoHEX( (nf->note_positions[i] / freqbins), 1.0, 1.0 );
					CNFGDrawBox( ((float)i / note_peaks) * screenx, 480 - nf->note_amplitudes_out[i] * 100, ((float)(i+1) / note_peaks) * screenx, 480 );
					CNFGPenX = ((float)(i+.4) / note_peaks) * screenx;
					CNFGPenY = screeny - 30;
					sprintf( stt, "%d\n%0.0f", nf->enduring_note_id[i], nf->note_amplitudes2[i]*1000.0 );
					//sttend += sprintf( sttend, "%5d/%5.0f ", nf->enduring_note_id[i], nf->note_amplitudes2[i]*1000.0 );
					CNFGDrawText( stt, 2 );

				}

				//Let's draw the o-scope.
				int thissoundhead = soundhead;
				thissoundhead = (thissoundhead-1+SOUNDCBSIZE)%SOUNDCBSIZE;
				int lasty = sound[thissoundhead] * 128 + 128; thissoundhead = (thissoundhead-1+SOUNDCBSIZE)%SOUNDCBSIZE;
				int thisy = sound[thissoundhead] * 128 + 128; thissoundhead = (thissoundhead-1+SOUNDCBSIZE)%SOUNDCBSIZE;
				for( i = 0; i < screenx; i++ )
				{
					if( thisy < 0 || thisy > 256 ) printf( "%d/%d\n", thisy,thissoundhead );
					CNFGTackSegment( i, lasty, i+1, thisy );
					lasty = thisy;
					thisy = sound[thissoundhead] * 128 + 128; thissoundhead = (thissoundhead-1+SOUNDCBSIZE)%SOUNDCBSIZE;
				}
				//puts( sttdebug );
			}

			//Extra debugging?
			if( show_debug )
			{
				//Draw the histogram
				float lasthistval;
				CNFGColor( 0xffffff );

				for( i = -1; i < screenx; i++ )
				{
					float thishistval = CalcHistAt( (float)i/(float)screenx*freqbins-0.5, nf->freqbins, nf->dists, nf->dists_count );
					if( i >= 0 )
						CNFGTackSegment( i, 400-lasthistval * 250.0, i+1, 400-thishistval * 250.0 );
					lasthistval = thishistval;
				}

				CNFGColor( 0xffffff );

				//Draw the bins
				for( i = 0; i < freqs; i++ )
				{
					float x0 = i / (float)freqs * (float)screenx;
					float x1 = (i+1) / (float)freqs * (float)screenx;
					float amp = nf->outbins[i] * 250.0;
					CNFGDialogColor = CCtoHEX( ((float)i / freqbins), 1.0, 1.0 );
					CNFGDrawBox( x0, 0, x1, amp );
				}
				CNFGDialogColor = 0x0f0f0f;

				char stdebug[1024];
				sprintf( stdebug, "DFT:%8.2fms\nFLT:%8.2f\nDEC:%8.2f\nFNL:%8.2f\nDPY:%8.2f",
					(nf->DFTTime - nf->StartTime)*1000,
					(nf->FilterTime - nf->DFTTime)*1000,
					(nf->DecomposeTime - nf->FilterTime)*1000,
					(nf->FinalizeTime - nf->DecomposeTime)*1000,
					(VisTimeEnd - VisTimeStart)*1000 );
				CNFGPenX = 50;
				CNFGPenY = 50;
				CNFGDrawText( stdebug, 2 );
			}

			CNFGColor( show_debug?0xffffff:0x000000 );
			CNFGPenX = 0; CNFGPenY = screeny-10;
			CNFGDrawText( "Extra Debug (D)", 2 );

			CNFGColor( show_debug_basic?0xffffff:0x000000 );
			CNFGPenX = 120; CNFGPenY = screeny-10;
			CNFGDrawText( "Basic Debug (E)", 2 );

			CNFGColor( show_debug_basic?0xffffff:0x000000 );
			CNFGPenX = 240; CNFGPenY = screeny-10;
			sprintf( stt, "[9] Key: %d [0] (%3.1f) [-]", gKey, nf->base_hz );
			CNFGDrawText( stt, 2 );

			CNFGColor( 0xffffff );
			CNFGPenX = 440; CNFGPenY = screeny-10;
			sprintf( stt, "FPS: %d", lastfps );
			CNFGDrawText( stt, 2 );
			CNFGSwapBuffers();
		}

		//Finish Rawdraw with FPS counter, and a nice delay loop.
		frames++;

		ThisTime = OGGetAbsoluteTime();
		if( ThisTime > LastFPSTime + 1 )
		{
//			printf( "FPS: %d\n", frames );
			lastfps = frames;
			frames = 0;
			LastFPSTime+=1;
		}

		if( cpu_autolimit )
		{
			SecToWait = .016 - ( ThisTime - LastFrameTime );
			LastFrameTime += .016;
			if( SecToWait < -.1 ) LastFrameTime = ThisTime - .1;
			if( SecToWait > 0 )
				OGUSleep( (int)( SecToWait * 1000000 ) );
		}

		SetEnvValues( 0 );
		Last = Now;
	}

}

