#include "template.h"

#define LINES		1024
#define LINEFILE	"lines1024.dat"
#define ITERATIONS	16

float lx1[LINES], ly1[LINES], lx2[LINES], ly2[LINES];
float x1_, y1_, x2_, y2_;
int fitness = 0xfffffff, lidx = 0;
Surface* reference, *backup;
int* ref8;
Timer tm;

void MutateLine( int i )
{
	x1_ = lx1[i];
	y1_ = ly1[i];
	x2_ = lx2[i];
	y2_ = ly2[i];
	do
	{
		if (rand() & 1)
		{
			// small mutation
			lx1[i] += Rand( 6 ) - 3;
			ly1[i] += Rand( 6 ) - 3;
			lx2[i] += Rand( 6 ) - 3;
			ly2[i] += Rand( 6 ) - 3;
			if (lx1[i] < 0) lx1[i] = 0;
			if (lx2[i] < 0) lx2[i] = 0;
			if (ly1[i] < 0) ly1[i] = 0;
			if (ly2[i] < 0) ly2[i] = 0;
			if (lx1[i] > (SCRWIDTH - 1)) lx1[i] = SCRWIDTH - 1;
			if (lx2[i] > (SCRWIDTH - 1)) lx2[i] = SCRWIDTH - 1;
			if (ly1[i] > (SCRHEIGHT - 1)) ly1[i] = SCRHEIGHT - 1;
			if (ly2[i] > (SCRHEIGHT - 1)) ly2[i] = SCRHEIGHT - 1;
		}
		else
		{
			// new line
			lx1[i] = Rand( SCRWIDTH );
			lx2[i] = Rand( SCRWIDTH );
			ly1[i] = Rand( SCRHEIGHT );
			ly2[i] = Rand( SCRHEIGHT );
		}
	}
	while ((fabs( lx1[i] - lx2[i] ) < 3) || (fabs( ly1[i] - ly2[i] ) < 3));
}

void UndoMutation( int i )
{
	lx1[i] = x1_;
	ly1[i] = y1_;
	lx2[i] = x2_;
	ly2[i] = y2_;
}

int Game::Evaluate()
{
	Pixel* src = screen->GetBuffer();
	const int quads = (SCRWIDTH * SCRHEIGHT) / 4;
	__m128i* A4 = (__m128i*)src;
	__m128i* B4 = (__m128i*)ref8;
	union { __m128i diff4; int diff[4]; };
	diff4 = _mm_set1_epi32( 0 );
	union { __m128i mask4; int mask[4]; };
	mask[0] = mask[1] = mask[2] = mask[3] = 255;
	for( int i = 0; i < quads; i++ ) 
	{
		const __m128i d2 = _mm_abs_epi32( _mm_sub_epi32( _mm_and_si128( A4[i], mask4 ), B4[i] ) );
		diff4 = _mm_add_epi32( diff4, _mm_srai_epi32( _mm_mul_epi32( d2, d2 ), 12 ) );
	}
	return diff[0] + diff[1] + diff[2] + diff[3];
}

void Game::Init()
{
	for( int i = 0; i < LINES; i++ ) MutateLine( i );
	FILE* f = fopen( LINEFILE, "rb" );
	if (f)
	{
		fread( lx1, 4, LINES, f );
		fread( ly1, 4, LINES, f );
		fread( lx2, 4, LINES, f );
		fread( ly2, 4, LINES, f );
		fclose( f );
	}
	Surface* reference = new Surface( "assets/image3.png" );
	backup = new Surface( SCRWIDTH, SCRHEIGHT );
	ref8 = (int*)MALLOC64( SCRWIDTH * SCRHEIGHT * 4 );
	for( int i = 0; i < (SCRWIDTH * SCRHEIGHT); i++ ) ref8[i] = reference->GetBuffer()[i] & 255;
	fitness = 512 * 512 * 16;
}

void Game::Shutdown()
{
	FILE* f = fopen( LINEFILE, "wb" );
	fwrite( lx1, 4, LINES, f );
	fwrite( ly1, 4, LINES, f );
	fwrite( lx2, 4, LINES, f );
	fwrite( ly2, 4, LINES, f );
	fclose( f );
}

void Game::Tick( float _DT )
{
	tm.reset();
	int lineCount = 0;
	int iterCount = 0;
	// draw up to lidx
	memset( screen->GetBuffer(), 255, SCRWIDTH * SCRHEIGHT * 4 );
	for( int j = 0; j < lidx; j++, lineCount++ )
	{
		unsigned int c = (j * 128) / LINES;
		screen->WULine( (int)lx1[j], (int)ly1[j], (int)lx2[j], (int)ly2[j], c + (c << 8) + (c << 16) );
	}
	int base = lidx;
	screen->CopyTo( backup, 0, 0 );
	// iterate and draw from lidx to end
	for( int k = 0; k < ITERATIONS; k++ )
	{
		memcpy( screen->GetBuffer(), backup->GetBuffer(), SCRWIDTH * SCRHEIGHT * 4 );
		MutateLine( lidx );
		for( int j = base; j < LINES; j++, lineCount++ )
		{
			unsigned int c = (j * 128) / LINES;
			screen->WULine( (int)lx1[j], (int)ly1[j], (int)lx2[j], (int)ly2[j], c + (c << 8) + (c << 16) );
		}
		int diff = Evaluate();
		if (diff < fitness) fitness = diff; else UndoMutation( lidx );
		lidx = (lidx + 1) % LINES;
		iterCount++;
	}
	// stats
	char t[128];
	float elapsed = tm.elapsed();
	float lps = (float)lineCount / elapsed;
	sprintf( t, "fitness: %i", fitness );
	screen->Bar( 0, SCRHEIGHT - 25, 100, SCRHEIGHT - 1, 0 );
	screen->Print( t, 2, SCRHEIGHT - 24, 0xffffff );
	sprintf( t, "lps:     %5.2fK", lps );
	screen->Print( t, 2, SCRHEIGHT - 16, 0xffffff );
	sprintf( t, "ips:     %5.2f", (iterCount * 1000) / elapsed );
	screen->Print( t, 2, SCRHEIGHT - 8, 0xffffff );
}