#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "SDL/SDL.h"

#define INT_MAX 32767 
#define PI 3.14159265f
#define INSTRUMENT_NUM  6

int const SAMPLE_RATE = 44100;
float const TEMPO = 190;
int const BEATS_PER_MEASURE = 4;
int DELAY_SAMPLES = 44100/190/4;
int LOOP_POINT = 44100.0 / (190.0 / 60.0 ) * 8;  


#include "common.h"
#include "filter.h"

typedef struct
{
	unsigned long sStart, sStop;
	unsigned char key, instrument;
} noteP; //notePlayback

typedef enum 
{ SIN, SAW, SQU, NOI, COS } osc_type;

int release[INSTRUMENT_NUM] = {20, 2000, 12000, 300, 0, 0};

double * frequencyTable;
long clock = 0;
noteP * noteTable;
noteP ** currentlyPlayingTable;
noteP * songPos; //pointer of noteTable
filter_data * filts[INSTRUMENT_NUM];
int *buf[2]; //FX echo
int globalNoteIndex = 0; 
char currentlyPlayingCount = 0;
int TOTAL_NOTES;

//extern char _binary_notes_bin_start[];
//extern char _binary_notes_bin_end[];

extern char _binary_lala_bin_start[];
extern char _binary_lala_bin_end[];

static void initFreqTable()
{
        if( NULL == ( frequencyTable = calloc( 89, sizeof(double) ))) {
                printf("Fail init freqtable");
        }

        int a;
	// 49 = A4 / 440 Hz
        for( a=1; a<89 ; a++ )
        {
                frequencyTable[a] = pow( 2, (a-49)/ 12.0 ) * 440;
                //printf( "%i %e \n", a, frequencyTable[a] );
        }
}

static void initNoteTable()
{
	unsigned int iSize = (unsigned int)(_binary_lala_bin_end - _binary_lala_bin_start);
	
	if( NULL == ( noteTable = malloc( iSize / sizeof(note) * sizeof(noteP) ) ) ) {
		printf("notetable\n");
	}
	
	TOTAL_NOTES = 0;
	int e;
	for(e=0; e<iSize; e+=sizeof(note))
	{
		noteP n;
		char * _la = _binary_lala_bin_start;

		time s1 = (time){ _la[e], _la[e+1], _la[e+2] };
		time s2 = (time){ _la[e+3], _la[e+4], _la[e+5] };
		n.sStart = timeToClock(&s1);
		n.sStop = timeToClock(&s2);
		n.key = _binary_lala_bin_start[6+e];
		n.instrument = _binary_lala_bin_start[7+e];

		noteTable[TOTAL_NOTES] = n;
		TOTAL_NOTES++;
	}
	songPos = &noteTable[0];	

	if( NULL == ( currentlyPlayingTable = malloc( 256 * sizeof( noteP * ))) )
		printf("fail malloc curTable");
}

static void initFilterTables() {
	
	int j;
	for( j=0 ; j<2; j++ )
	{
		if( NULL == ( filts[j] = calloc( sizeof(filter_data), 1 ) ) )
			printf("faile calloc");

		//TODO put elsewhere
		//ECHO FX init
		if( NULL == ( buf[j] = calloc( sizeof(int), DELAY_SAMPLES ) ) )
		       printf("faile calloc");	
	}
}

/**
 * Outputs a ASDR value from 0.0f to 1.0f
 *   - sustain must be bounded by [0,1]
 **/
static float envelope( noteP * nt, int attack, int decay, float sustain)
{
	long clack = clock - nt->sStart;
	long note_end = nt->sStop - nt->sStart;
	int rel = release[nt->instrument];

	if( clack < attack )
		return (float)clack / (float)attack;
	else if( clack < attack + decay )
		return  1.0f - (float)(clack - attack) / decay  * (1.0f - sustain);
	else if( clack < note_end )
		return sustain;
	else if( clack < note_end + rel )
		return sustain  - sustain * (float)(clack - note_end) / (float)rel;
	else
		return 0;
}

static signed int filter( float freq, float res, int input, int const ID )
{
	calculateFilterCoeffs( freq, res );
	input = INT_MAX * updateFilter( (float) input / INT_MAX, filts[ID] );
	return input;
}

static float osc( noteP *nt, osc_type type, double period )
{
	long clack = clock - nt->sStart;
	float out;

	switch( type )
	{
		case SAW:
			out = (float)(2.0 / period * fmod(clack, period) - 1.0f );
			break;
		case SQU:
			;	
			double modCurrSample = fmod(clack, period);
			if( modCurrSample < period / 2)
				out = 1.0f ;
			else if( modCurrSample < period )
				out = -1.0f;
			/*else
				printf("square wave error");
			*/
			break;
		case SIN:
			out = (float)sin( 2 * PI * clack / period ) ;
		default:
			out = (float)cos( 2 * PI * clack / period ) ;
	}

	return out;
}

static float osc_f( double freq, double phase )
{
	return (float)cos( 2 * PI * (double)clock / (double)SAMPLE_RATE * freq + phase );
}

// lr - channel output: left - 0  |  right - 1
static int synthesizer (noteP *nt, int lr)
{
	int out;
	double period = SAMPLE_RATE / frequencyTable[nt->key];
	double freq = frequencyTable[nt->key];
	switch ( nt->instrument )
	{
		float e = 0.0f;
		case 0: ;
			float t = 0;
			int a;
			period *= 1.0;
			for(a=0;a<5;a++)
			{
				t += osc( nt, SAW, period * 2.0* ( 1 + a * (lr ? 1.005 : -1.005) / 60.0) );
				t += osc( nt, SAW, period * (1+a * 0.012f)) * (0.5f - a/29.0f); 
				t += osc( nt, SAW, period / (1+a * 0.012f)) * (0.5f - a/29.0f);
			}
			e = envelope( nt, 0, 0, 1.0f);
			out = (int)(t / 4.0f * e * INT_MAX); 
			
	//		out = (int)( osc(SAW, period) * 0.5f * INT_MAX);
			break;
		case 1:
			out = (int)(osc( nt, SQU, period ) * INT_MAX);
			float abuse = envelope( nt, 100, 7000, 0.2f) * 0.7f;
			out *= abuse;
			// out = filter( 5000.0f * abuse , 2.5f, out, 0);
			break;
		case 2: ;
			float octave = 2.0f;
			//float modulator = osc( SIN, period );
			float ee  = envelope( nt, 0, 9000, 0.0f );
			e = envelope( nt , 90, 28913, 0.0f) * 0.7f;
			out = (int)( osc_f( freq / 2 , PI/4 + ee * osc_f(freq * 3 , 0)) * e * INT_MAX); // + osc_f( freq ) 
			break;
		case 3:
			out = (int)( osc(nt, SQU, period * (1 + 0.5 * osc(nt, SQU, SAMPLE_RATE * 60.0 / (float)TEMPO)/2 )) * envelope(nt, 10, 11000, 0.4f) * 0.3f * INT_MAX);
			break;
		case 5: ;
			float i;
			out = (int)( osc(nt, SIN, period / envelope(nt, 200, 26890, 0.0f))  * INT_MAX);
			break;
		case 6:
			;
			float tmp = osc(nt, SIN, period / osc(nt, SIN, period)  );
			abuse = envelope( nt, 100, 21000, 0.2f);
			out = (int)(tmp * INT_MAX);
			out = filter( 2000.0f * abuse , 7.5f, out, 0);
			out *= abuse;
			break;
		case 7:
			tmp = osc(nt, SIN, period * 3 * osc(nt, SIN, period * 3 )); 
			abuse = envelope( nt, 40, 2100, 0.0f);
			out = (int)(tmp * abuse * INT_MAX);
			//out = filter( 18200.0f , 1.5f, out, 1);
			break;
		default:
			out = (int)( osc(nt, SIN, period ) * INT_MAX);
	}
//	out = filter( 1500.0f, 3.0f, out );
	out *= 0.2; //TODO; replace volume
	return out;
}

static void callback(void *uData, Uint8 *stream, int len)
{
	len /= 2; //16bit
	Sint16* dump = (Sint16*) stream;

	int j;
	for(j=0 ; j<len ; j += 2 )
	{	
		int mixer[2] = {0, 0};
		int k;
	
		//load from noteTable
		while( clock > songPos->sStart && globalNoteIndex < TOTAL_NOTES)
		{
		//	printf("globalNoteIndex  %i currentNoteCount %i\n", globalNoteIndex, currentlyPlayingCount );
		
			//find index to insert
			if( currentlyPlayingCount != 0 )
			{
				int searchIndex = 0;
				while( searchIndex < currentlyPlayingCount 
				&& currentlyPlayingTable[searchIndex]->sStop > songPos->sStop )
					searchIndex++;

				int b;
				for( b=currentlyPlayingCount ; b > searchIndex ; b-- )
					currentlyPlayingTable[b] = currentlyPlayingTable[b-1];

				currentlyPlayingTable[searchIndex] = songPos;
			}
			else
				currentlyPlayingTable[0] = songPos;

			int a;
			/*for( a=0; a<currentlyPlayingCount; a++)
				if( currentlyPlayingTable[a] != NULL )
					printf("%i|ch%i\t", currentlyPlayingTable[a]->key, currentlyPlayingTable[a]->instrument);
			printf("\n");
			*/
			currentlyPlayingCount++;
			globalNoteIndex++;
			songPos = &noteTable[globalNoteIndex]; //move the song forward
		}
		
		//remove ended notes
		noteP * endingNote;
		endingNote = currentlyPlayingTable[ currentlyPlayingCount - 1 ];
		while( currentlyPlayingCount >= 1 && clock > endingNote->sStop 
		    + release[ endingNote->instrument]  )
		{
			currentlyPlayingTable[currentlyPlayingCount - 1] = NULL;
			currentlyPlayingCount--;
			endingNote = currentlyPlayingTable[ currentlyPlayingCount - 1 ];
		}
		
		for( k=0; k < currentlyPlayingCount ; k++)
		{
			noteP * nt = currentlyPlayingTable[k];
			if( nt->sStart < clock && nt->sStop + release[nt->instrument] 
			> clock ) 
			{ 
				mixer[0] += synthesizer( nt, 0);
			  	mixer[1] += synthesizer( nt, 1);
				//ADD LIMITER
			}
		}
		//delay...
		int pp;
		/*for(pp = 0 ; pp<2 ; pp++)
		{
			int z = abs( (clock - pp * 173) % DELAY_SAMPLES);
			mixer[0] += (int)(buf[0][z] / (4.3f + pp));
			mixer[1] += (int)(buf[1][z] / (4.3f + pp));
		}
		int z = clock % DELAY_SAMPLES;
		buf[ 0 ][z] = mixer[0];
		buf[ 1 ][z] = mixer[1]; 
*/
		dump[j] = mixer[0];
		dump[j+1] = mixer[1];
		clock++;

		if(clock >= 445800)
		{
			clock =0;
			globalNoteIndex = 0;
			currentlyPlayingCount = 0;
			currentlyPlayingTable[0] = songPos;
			songPos = &noteTable[globalNoteIndex]; //move the song forward
		}

		//if(clock % 20 == 0)
		//printf("%li i%d ", clock, dump[j]);	
		
	}
	
	//printf("notes playing: %i\n", currentlyPlayingCount);
}


int main (int argc, char ** argv)
{
	if( SDL_Init( SDL_INIT_AUDIO ) == -1 )
	{
		printf("fail init SDL: %s\n", SDL_GetError() );
		return(-1);
	}

	SDL_AudioSpec desirable;
	extern void callback( void *uData, Uint8 *stream, int len);

	desirable.freq = 44100;
	desirable.format = AUDIO_S16;
	desirable.channels = 2;
	desirable.samples = 1024; //latency
	desirable.callback = callback;
	desirable.userdata = 0;

	if( SDL_OpenAudio( &desirable, NULL) < 0) {
		fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError());
		return(-1);
	}

	//Pre rendering
	initFreqTable();
	initNoteTable();
	initFilterTables();

	//ACTUAL MUSIC GEN
	
	if(argc > 1)
	{
		int in = atoi(argv[1]);
		clock = in * SAMPLE_RATE / TEMPO * 60 * in;
	}

	SDL_PauseAudio(0); //start audio
	int r;
	r = 0;
	//while( finishSong != 0 )
		SDL_Delay( 43000 );
	
	//cleanup
	exit(0);
	SDL_PauseAudio(-1); //pause audio
	/*while(r<CH_COUNT)
	{
		free( channels[r].stream );
		r++;
	}*/

	SDL_CloseAudio();
	SDL_Quit();
	exit(0);
}


