#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <math.h>
//#include <unistd.h>


// needed to concatenate constants into strings
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)


// word size exponent, taper length exponent
#define  SIZEEXP   5
#define  WORDSIZE  (1 << SIZEEXP)
#define  SIZEMASK  (WORDSIZE - 1)

// register emulation masking
#define  ACC1SIZE  30
#define  ACC1MASK  (~((1 << (WORDSIZE - ACC1SIZE)) - 1))
#define  ACC2MASK  (~((1 << (WORDSIZE + 2 - ACC1SIZE)) - 1))
#define  ACC3MASK  (~((1 << (WORDSIZE + 6 - ACC1SIZE)) - 1))
#define  DIF1MASK  (~((1 << (WORDSIZE + 7 - ACC1SIZE)) - 1))
#define  DIF2MASK  (~((1 << (WORDSIZE + 8 - ACC1SIZE)) - 1))
#define  DIF3MASK  (~((1 << (WORDSIZE + 9 - ACC1SIZE)) - 1))
// register emulation masking for "case 43:"
#define  ACCSIZE  24
#define  ACCMASK  (~((1 << (WORDSIZE - ACCSIZE)) - 1))


static uint32_t  bitstream[0x100000];  // 32 Mbit



static uint32_t  chunk( int chunktype, uint32_t chunklen, int reset )
{
	static uint32_t  bitindex, acc1, acc2, acc3, diff1, diff2, diff3;
	uint32_t  bits, ones, reading, indexSV, i;


	bitindex &= 0x7fffff;  // 8 Mbit
	reading = 0;
	if( reset & 2 )  bitindex = 0;


	switch( chunktype )
	{
	case 1: // Flat box chunk, using flat middle section from Chip's algorithm
		indexSV = bitindex + chunklen;
		do {
			bits = bitstream[bitindex >> SIZEEXP];
			bitindex += (1 << SIZEEXP);
			// count the set bits
			i = ones = 0;
			do {
				ones += (bits >> i) & 1;
				i++;
			} while( i < (1 << SIZEEXP) );

			reading += ones << SIZEEXP;

		} while( bitindex < indexSV );

		reading >>= SIZEEXP;
		break;


	case 2: // Chip's trapedzoidal algorithm
		// taper up section
		bits = bitstream[bitindex >> SIZEEXP];
		bitindex += (1 << SIZEEXP);
		i = ones = 0;
		do {
			ones++;
			if( (bits >> i) & 1 )  reading += ones;
			i++;

		} while( i < (1 << SIZEEXP) );


		// flat middle section
		indexSV = bitindex + chunklen - (2 << SIZEEXP);
		do {
			bits = bitstream[bitindex >> SIZEEXP];
			bitindex += (1 << SIZEEXP);
			// count the set bits
			i = ones = 0;
			do {
				ones += (bits >> i) & 1;
				i++;
			} while( i < (1 << SIZEEXP) );

			reading += ones << SIZEEXP;

		} while( bitindex < indexSV );


		// taper down section
		bits = bitstream[bitindex >> SIZEEXP];
		bitindex += (1 << SIZEEXP);
		ones = (1 << SIZEEXP);
		i = 0;
		do {
			if( (bits >> i) & 1 )  reading += ones;
			ones--;
			i++;
		} while( ones );

		reading >>= SIZEEXP;
		break;


	case 11:// Sinc1 algorithm
		if( reset & 1 )  acc1 = diff1 = 0;

		indexSV = bitindex + chunklen;
		do {
			bits = bitstream[bitindex >> SIZEEXP];
			if( (bits >> (bitindex & SIZEMASK)) & 1 )  // C bitfield extraction
			{
				acc1 += 1;
			}
			bitindex++;

		} while( bitindex < indexSV );

		reading = acc1 - diff1;
		diff1 = acc1;
		break;


	case 12:// Sinc2 algorithm
		if( reset & 1 )  acc1 = acc2 = diff1 = diff2 = 0;

		indexSV = bitindex + chunklen;
		do {
			acc2 += acc1;

			bits = bitstream[bitindex >> SIZEEXP];
			if( (bits >> (bitindex & SIZEMASK)) & 1 )  // C bitfield extraction
			{
				acc1 += 1;
			}
			bitindex++;

		} while( bitindex < indexSV );

		reading = acc2 - diff1 - diff2;
		diff2 = acc2 - diff1;
		diff1 = acc2;
		break;


	case 13:// Sinc3 algorithm
		if( reset & 1 )  acc1 = acc2 = acc3 = diff1 = diff2 = diff3 = 0;

		indexSV = bitindex + chunklen;
		do {
			acc3 += acc2;
			acc2 += acc1;

			bits = bitstream[bitindex >> SIZEEXP];
			if( (bits >> (bitindex & SIZEMASK)) & 1 )  // C bitfield extraction
			{
				acc1 += 1;
			}
			bitindex++;

		} while( bitindex < indexSV );

		reading = acc3 - diff1 - diff2 - diff3;
		diff3 = acc3 - diff1 - diff2;
		diff2 = acc3 - diff1;
		diff1 = acc3;
		break;


	case 23:// Sinc3 algorithm, with pruning
		if( reset & 1 )  acc1 = acc2 = acc3 = diff1 = diff2 = diff3 = 0;

		indexSV = bitindex + chunklen;
		do {
			acc3 = (acc3 + acc2) & ACC3MASK;
			acc2 = (acc2 + acc1) & ACC2MASK;

			bits = bitstream[bitindex >> SIZEEXP];
			if( (bits >> (bitindex & SIZEMASK)) & 1 )  // C bitfield extraction
			{
				acc1 = (acc1 + (1 << (WORDSIZE - ACC1SIZE))) & ACC1MASK;
			}
			bitindex++;

		} while( bitindex < indexSV );

		reading = (acc3 - diff1 - diff2 - diff3) >> (WORDSIZE - ACC1SIZE);

		diff3 = (acc3 - diff1 - diff2) & DIF3MASK;
		diff2 = (acc3 - diff1) & DIF2MASK;
		diff1 = acc3 & DIF1MASK;
		break;


	case 33:// Sinc3 algorithm, with pruning, with 32-bit software diff'ing
		if( reset & 1 )  acc1 = acc2 = acc3 = diff1 = diff2 = diff3 = 0;

		indexSV = bitindex + chunklen;
		do {
			acc3 = (acc3 + acc2) & ACC3MASK;
			acc2 = (acc2 + acc1) & ACC2MASK;

			bits = bitstream[bitindex >> SIZEEXP];
			if( (bits >> (bitindex & SIZEMASK)) & 1 )  // C bitfield extraction
			{
				acc1 = (acc1 + (1 << (WORDSIZE - ACC1SIZE))) & ACC1MASK;
			}
			bitindex++;

		} while( bitindex < indexSV );

		reading = (acc3 - diff1 - diff2 - diff3) >> (WORDSIZE - ACC1SIZE);

		diff3 = acc3 - diff1 - diff2;
		diff2 = acc3 - diff1;
		diff1 = acc3;
		break;


	case 43:// Sinc3 algorithm, matched accumulators, 32-bit software diff'ing
		if( reset & 1 )  acc1 = acc2 = acc3 = diff1 = diff2 = diff3 = 0;

		indexSV = bitindex + chunklen;
		do {
			acc3 = (acc3 + acc2) & ACCMASK;
			acc2 = (acc2 + acc1) & ACCMASK;

			bits = bitstream[bitindex >> SIZEEXP];
			if( (bits >> (bitindex & SIZEMASK)) & 1 )  // C bitfield extraction
			{
				acc1 = (acc1 + (1 << (WORDSIZE - ACCSIZE))) & ACCMASK;
			}
			bitindex++;

		} while( bitindex < indexSV );

		reading = (acc3 - diff1 - diff2 - diff3) >> (WORDSIZE - ACCSIZE);

		diff3 = acc3 - diff1 - diff2;
		diff2 = acc3 - diff1;
		diff1 = acc3;
		break;


	default:
		break;
	}

	return( reading );
}



int  main( int argc, char* argv[] )
{
//	uint32_t  bitstream[0x100000];  // 8 Mbit
	uint32_t  bitindex, dither, rollover, reading, i, j, portion, dclevel, sublevel;
	struct timespec  start, stop;
	FILE  *fh;
	double  duration;
	uint64_t  level;


	memset( bitstream, 0, sizeof(bitstream) );

	clock_gettime( CLOCK_MONOTONIC, &start );
	printf( "Started at %.3f ...\n", start.tv_sec + start.tv_nsec/1.0e9 );

#define  NUMREADINGS  400
// number of bits of bitstream per reading
#define  CHUNKLEN  0x100
#define  PRINTFMT  " %d"

//	fh = stdout;

	// write readings to csv file
	fh = fopen( "sinc.csv", "w" );
	if( fh == NULL )
	{
		printf( "file opening error\n" );
		exit( 1 );
	}


	// Reference DC level
	fprintf( fh, "\nDC-level  " );

	// fill the bitstream array with valid synthesised data
	dclevel = 0;
	sublevel = 1;
	dither = 25000;
	bitindex = 0;
	do {
		if( (bitindex % CHUNKLEN) == 0 )  fprintf( fh, PRINTFMT, dclevel * 671/2 );  // 2^24 / 50000

//		portion = (portion + 1) % 2000;
//		dclevel = (double)(sin( M_PI * portion / 1000.0 ) * 31.5 + 32.0);
//		dclevel = (double)(sin( M_PI * portion / 1000.0 ) * 32767.5 + 32768.0);
/*
		if( sublevel >= 10 )
		{
			sublevel = 0;
			dclevel = (dclevel + 1) % 5000;
		}
		sublevel++;
*/
		dclevel = (dclevel + 1) % 50000;
//printf( " %d", dclevel );

		dither += dclevel;    // DC level, arbitrary
//		rollover = dither & 0x3f;    // 6-bit based
		rollover = dither % 50000;
		if( dither != rollover )
		{
			dither = rollover;
			bitstream[bitindex >> SIZEEXP] |= 1 << (bitindex & SIZEMASK);  // C bitfield insertion
		}
		bitindex++;
	} while( bitindex < (CHUNKLEN * NUMREADINGS) );
//	} while( bitindex < 102800 );

/*
	// diag dump
	fh = fopen( "bitstream.bin", "w" );
	if( fh == NULL )
	{
		printf( "file opening error\n" );
		exit( 1 );
	}
	fwrite( bitstream, 102800/8, 1, fh );
	fclose( fh );
	exit(0);
*/
/*
	dither = sublevel = level = 0;
	do {
		level += sublevel;
		sublevel += dither;
		dither += 1;
	} while( level < (1L<<30) );
	printf( " %10d %10d %10ld\n", dither, sublevel, level );
	printf( " %f %f %f", log(dither)/log(2), log(sublevel)/log(2), log(level)/log(2) );
*/



/*
	reading = chunk( 1, CHUNKLEN, 3 );   // reset on first
	fprintf( fh, "\nBox  "PRINTFMT, reading );
	for( j = 1; j < NUMREADINGS; j++ )
	{
		reading = chunk( 1, CHUNKLEN, 1 );  // Square box chunked, non-rolling

		fprintf( fh, PRINTFMT, reading );
	}


	reading = chunk( 2, CHUNKLEN, 3 );   // reset on first
	fprintf( fh, "\nTrapezoid  "PRINTFMT, reading );
	for( j = 1; j < NUMREADINGS; j++ )
	{
		reading = chunk( 2, CHUNKLEN, 1 );  // Chip's trapezoid, non-rolling

		fprintf( fh, PRINTFMT, reading );
	}


	reading = chunk( 11, CHUNKLEN, 3 );   // reset on first
	fprintf( fh, "\nSinc1_rolling  "PRINTFMT, reading );
	for( j = 1; j < NUMREADINGS; j++ )
	{
		reading = chunk( 11, CHUNKLEN, 0 );   // Sinc1  (Smartpin mode %01100), rolling

		fprintf( fh, PRINTFMT, reading );
	}


	// Sinc2
	reading = chunk( 12, CHUNKLEN, 3 );   // reset on first
	fprintf( fh, "\nSinc2-1  "PRINTFMT, reading );
	for( j = 1; j < NUMREADINGS; j++ )
	{
		reading = chunk( 12, CHUNKLEN, 1 );   // Sinc2, non-rolling

		fprintf( fh, PRINTFMT, reading );
	}


	// Sinc2
	reading = chunk( 12, CHUNKLEN, 3 );   // reset on first
	fprintf( fh, "\nSinc2_rolling  "PRINTFMT, reading );
	for( j = 1; j < NUMREADINGS; j++ )
	{
		reading = chunk( 12, CHUNKLEN, 0 );   // Sinc2, rolling

		fprintf( fh, PRINTFMT, reading );
	}


	// Sinc2
	fprintf( fh, "\nSinc2-4  " );
	for( j = 0; j < NUMREADINGS; j++ )
	{
		chunk( 12, CHUNKLEN/4, j==0?3:1 );   // reset for gap
		chunk( 12, CHUNKLEN/4, 0 );   // rolling, stablising
		reading = 0;
		for( i = 0; i < 2; i++ )
		{
			reading += chunk( 12, CHUNKLEN/4, 0 );   // rolling
		}
		fprintf( fh, PRINTFMT, reading );
	}
*/

	// Sinc3
	reading = chunk( 13, CHUNKLEN, 3 );   // reset on first
	fprintf( fh, "\nSinc3_rolling  "PRINTFMT, reading );
	for( j = 1; j < NUMREADINGS; j++ )
	{
		reading = chunk( 13, CHUNKLEN, 0 );   // Sinc3, rolling

		fprintf( fh, PRINTFMT, reading );
	}


	// Sinc3
	fprintf( fh, "\nSinc3-4  " );
	for( j = 0; j < NUMREADINGS; j++ )
	{
		chunk( 13, CHUNKLEN/4, j==0?3:1 );   // reset for gap
		chunk( 13, CHUNKLEN/4, 0 );   // rolling, stablising
		reading = 0;
		for( i = 0; i < 2; i++ )
		{
			reading += chunk( 13, CHUNKLEN/4, 0 );   // rolling
		}
		fprintf( fh, PRINTFMT, reading );
	}


	// Sinc3, pruned
	reading = chunk( 23, CHUNKLEN, 3 );   // reset on first
	fprintf( fh, "\nSinc3-rolling_Pruned  "PRINTFMT, reading );
	for( j = 1; j < NUMREADINGS; j++ )
	{
		reading = chunk( 23, CHUNKLEN, 0 );   // Sinc3, rolling

		fprintf( fh, PRINTFMT, reading );
	}


	// Sinc3, pruned
	fprintf( fh, "\nSinc3-4_Pruned  " );
	for( j = 0; j < NUMREADINGS; j++ )
	{
		chunk( 23, CHUNKLEN/4, j==0?3:1 );   // reset for gap
		chunk( 23, CHUNKLEN/4, 0 );   // rolling, stablising
		reading = 0;
		for( i = 0; i < 2; i++ )
		{
			reading += chunk( 23, CHUNKLEN/4, 0 );   // rolling
		}
		fprintf( fh, PRINTFMT, reading );
	}


	// Sinc3, pruned, alt
	reading = chunk( 33, CHUNKLEN, 3 );   // reset on first
	fprintf( fh, "\nSinc3-rolling_Pruned-dif32  "PRINTFMT, reading );
	for( j = 1; j < NUMREADINGS; j++ )
	{
		reading = chunk( 33, CHUNKLEN, 0 );   // Sinc3, rolling

		fprintf( fh, PRINTFMT, reading );
	}


	// Sinc3 algorithm, 30-bit accumulators, 32-bit software diff'ing
	reading = chunk( 43, CHUNKLEN, 3 );   // reset on first
	fprintf( fh, "\nSinc3-rolling_acc24-dif32  "PRINTFMT, reading );
	for( j = 1; j < NUMREADINGS; j++ )
	{
		reading = chunk( 43, CHUNKLEN, 0 );   // Sinc3, rolling

		fprintf( fh, PRINTFMT, reading );
	}


	fprintf( fh, "\n" );
	fclose( fh );


	clock_gettime( CLOCK_MONOTONIC, &stop );
	duration = (time_t)(stop.tv_sec - start.tv_sec) + (long)(stop.tv_nsec - start.tv_nsec)/1.0e9;
	printf( "\nFinished at %.3f  -  duration = %.3f\n", stop.tv_sec + stop.tv_nsec/1.0e9, duration );

	return 0;
}

