#include "externals.hh"
#include "demo.hh"
#include "sync.hh"
#include "rendertarget.hh"
#include "globals.hh"
#include <ebcl/Files.hh>
#include <ebcl/SRDText.hh>


T_Demo::T_Demo( const uint32_t width ,
		const uint32_t height )
	: width( width ) , height( height )
{ }


bool T_Demo::initialise( )
{
	if ( !program ) {
		loadProgram( );
	}

	return true;
}

void T_Demo::render( )
{
	if ( program && !context ) {
		runInit( );
	}

	auto& sync( Globals::Sync( ) );
	if ( playing ) {
		const float time = SDL_GetTicks( ) * 1e-3;
		if ( playingPrevious ) {
			sync.timeDelta( time - lastFrame );
			playing = !sync.finished( );
		}
		lastFrame = time;
	}
	playingPrevious = playing;

	if ( context && !context->aborted ) {
		try {
			context->run(  ops::T_OpContext::R_RENDER , sync.time( ) , width , height );
		} catch ( ops::X_OpFailure const& fail ) {
			printf( "FAILED TO RUN FRAME!\n\t%s\n" , fail.what( ) );
			context->aborted = true;
		}
	}
}

void T_Demo::handleDND(
		ImVec2 const& move ,
		const bool hasCtrl ,
		const bool hasShift ,
		const bool lmb		// Left mouse button
	)
{
}

void T_Demo::handleWheel(
		const float wheel ,
		const bool hasCtrl ,
		const bool hasShift
	)
{
}

namespace {

/*============================================================================*/


void PrintStreamError(
		char const* const prefix ,
		T_String const& name ,
		ebcl::X_StreamError const& error )
{
	T_StringBuilder sb;
	sb << prefix << " '" << name << "': " << error.what( );
	if ( error.code( ) == ebcl::E_StreamError::SYSTEM_ERROR ) {
		sb << " (error code " << error.systemError( ) << ")";
	}
	sb << '\n' << '\0';
	fprintf( stderr , "%s" , sb.data( ) );
}

void WriteSRDError(
		T_StringBuilder& sb ,
		ebcl::T_SRDError const& error )
{
	sb << error.location( ) << " - " << error.error( ) << "\n";
}


/*============================================================================*/

} // namespace


bool T_Demo::loadProgram( )
{
	using namespace ebcl;
	const T_String inputName( T_String::Pooled( "demo.srd" ) );
	T_File input( inputName , E_FileMode::READ_ONLY );
	try {
		input.open( );
	} catch ( X_StreamError const& e ) {
		PrintStreamError( "Could not open" , inputName , e );
		return false;
	}

	// Load SRD data
	T_SRDMemoryTarget srdOut;
	srdOut.clearComments( true ).clearFlushToken( true );
	try {
		T_SRDTextReader srdReader{ srdOut };
		T_FileInputStream fis{ input };
		srdReader.read( inputName , fis );
	} catch ( X_StreamError const& e ) {
		PrintStreamError( "Could not open" , inputName , e );
		return false;

	} catch ( X_SRDErrors const& e ) {
		T_StringBuilder sb;
		const auto nErrors( e.errors.size( ) );
		for ( auto i = 0u ; i < nErrors ; i ++ ) {
			WriteSRDError( sb , e.errors[ i ] );
		}
		sb << "No parsing happened due to format errors\n" << '\0';
		fprintf( stderr , "%s" , sb.data( ) );
		return false;
	}

	// Parse the fuck
	T_OpsParser parser;
	if ( parser.parse( srdOut.list( ) ) ) {
		printf( "Parser successful. Compiling...\n" );
		T_OpsCompiler compiler;
		program = compiler.compile( *parser.result( ) );
		return true;
	}

	T_StringBuilder sb;
	sb << "- PARSE ERRORS -------------------------\n";
	const auto nErrors( parser.errors( ).size( ) );
	for ( auto i = 0u ; i < nErrors ; i ++ ) {
		WriteSRDError( sb , parser.errors( )[ i ] );
	}
	sb << "----------------------------------------\n";
	sb << '\0';
	fprintf( stderr , "%s" , sb.data( ) );
	return false;
}


void T_Demo::runInit( )
{
	context = NewOwned< ops::T_OpContext >( *program );
	try {
		context->run( ops::T_OpContext::R_INIT , 0 , width , height );
	} catch ( ops::X_OpFailure const& fail ) {
		printf( "FAILED TO RUN INIT!\n\t%s\n" , fail.what( ) );
		context->aborted = true;
		return;
	}

	Globals::Sync( ).clearInputs( );
	const auto n( context->initialInputs.size( ) );
	assert( n == program->inputs.size( ) );
	for ( auto i = 0u ; i < n ; i ++ ) {
		Globals::Sync( ).addInput( program->inputs[ i ] ,
				context->initialInputs[ i ] );
#ifdef INVASIVE_TRACES
		printf( "#%d %s pos %d\n" , i , program->inputs[ i ].toOSString( ).data( ) ,
				Globals::Sync( ).inputPos( program->inputs[ i ] ) );
#endif //INVASIVE_TRACES
	}
	Globals::Sync( ).updateCurveCaches( );
}