#include "externals.hh"
#include "sync.hh"
#include "globals.hh"

#include <imgui_internal.h>
#include <ebcl/Files.hh>
#include <ebcl/SRDText.hh>
#include <ebcl/SRDParser.hh>
using ebcl::T_SRDParserConfig;

namespace {

const std::map< std::string , T_SyncSegment::E_SegmentType > SegmentTypes_( ([] {
	std::map< std::string , T_SyncSegment::E_SegmentType > t;
	t.emplace( "linear" , T_SyncSegment::LINEAR );
	t.emplace( "ramp" , T_SyncSegment::RAMP );
	t.emplace( "smooth" , T_SyncSegment::SMOOTH );
	return t;
})());

}


/*= SRD parser for the curves ================================================*/

namespace {

struct T_ParserOutput_
{
	T_SyncCurves curves;
	T_Optional< T_SyncTime > time;
	ebcl::T_SRDLocation tLocation;
};

using namespace ebcl;

bool CPEnterCurve_( T_SRDParserData const& data )
{
	*( data.targetData ) = T_SyncCurve{ (*data.input)[ 0 ].stringValue( ) };
	return true;
}

bool CPExitCurve_( T_SRDParserData const& data )
{
	auto& curve( data.currentData->value< T_SyncCurve >( ) );
	auto& po( *( data.targetData->value< T_SharedPtr< T_ParserOutput_ > >( ) ) );

	if ( curve.segments.empty( ) ) {
		T_StringBuilder sb;
		sb << "curve '" << curve.name << "' is empty";
		data.errors.add( std::move( sb ) , (*data.input)[ 0 ] );
	}

	if ( po.curves.curves.contains( curve.name ) ) {
		T_StringBuilder sb;
		sb << "duplicate curve '" << curve.name << "'";
		data.errors.add( std::move( sb ) , (*data.input)[ 0 ] );
	} else {
		po.curves.setCurve( std::move( curve ) );
	}

	return true;
}

void CPHandleSegment_(
		const T_SyncSegment::E_SegmentType type ,
		T_SRDList const& lValues ,
		T_SRDList const& lDurations ,
		T_SRDErrors& errors ,
		T_SyncCurve& curve )
{
	bool failed = false;
	if ( lDurations.size( ) != lValues.size( ) - 1 ) {
		errors.add( "values / durations count mismatch" , lValues[ 0 ] );
		failed = true;
	}

	// Check durations
	const auto nd( lDurations.size( ) );
	for ( auto i = 1u ; i < nd ; i ++ ) {
		auto const& tok( lDurations[ i ] );
		const uint64_t v( tok.longValue( ) );
		if ( v < 1 || v > UINT32_MAX ) {
			errors.add( "invalid duration" , tok );
			failed = true;
		}
	}

	if ( !failed ) {
		T_SyncSegment& segment( curve.segments.addNew( ) );
		segment.type = type;
		segment.durations.ensureCapacity( nd - 1 );
		for ( auto i = 1u ; i < nd ; i ++ ) {
			auto const& tok( lDurations[ i ] );
			segment.durations.add( uint32_t( tok.longValue( ) ) );
		}

		segment.values.ensureCapacity( nd );
		for ( auto i = 1u ; i <= nd ; i ++ ) {
			auto const& tok( lValues[ i ] );
			segment.values.add( tok.floatValue( ) );
		}
	}
}

bool CPSegmentVD_( T_SRDParserData const& data )
{
	auto const& input( *data.input );
	const auto ev( data.config.enumValue( "segment-type" , input[ 1 ].stringValue( ) ) );
	assert( ev );
	CPHandleSegment_( (T_SyncSegment::E_SegmentType) *ev ,
			input[ 2 ].list( ) , input[ 3 ].list( ) ,
			data.errors , data.targetData->value< T_SyncCurve >( ) );
	return true;
}

bool CPSegmentDV_( T_SRDParserData const& data )
{
	auto const& input( *data.input );
	const auto ev( data.config.enumValue( "segment-type" , input[ 1 ].stringValue( ) ) );
	assert( ev );
	CPHandleSegment_( (T_SyncSegment::E_SegmentType) *ev ,
			input[ 3 ].list( ) , input[ 2 ].list( ) ,
			data.errors , data.targetData->value< T_SyncCurve >( ) );
	return true;
}

bool CPSetDuration_( T_SRDParserData const& data )
{
	auto const& input( *data.input );
	auto& po( *( data.currentData->value< T_SharedPtr< T_ParserOutput_ > >( ) ) );
	if ( po.time ) {
		T_StringBuilder eb;
		eb << "duplicate duration specification; previous: "
			<< po.tLocation;
		data.errors.add( std::move( eb ) , input[ 0 ].location( ) );
	} else {
		po.time = T_SyncTime( );
		po.time->uDuration = std::min( 2. , std::max( 1. / 60. , input[ 1 ].floatValue( ) ) );
		po.time->iDuration = std::max( 1l , input[ 2 ].longValue( ) );
	}
	return true;
}

T_SRDParserConfig MakeCurvesParser_( )
{
	using namespace ebcl::SRD;

	T_SRDParserDefs defs( "default" );
	defs << OnStart( []( T_SRDParserData const& data ) -> bool {
		*( data.currentData ) = NewShared< T_ParserOutput_ >( );
		return true;
	} );

	defs.enumeration( "segment-type" )
		<< "linear" << "ramp" << "smooth";

	defs.context( "default" )
		<< ( Rule( ) << Text( ) << EnterContext( "segments" )
			<< OnEnter( CPEnterCurve_ ) << OnExit( CPExitCurve_ ) )
		<< ( Rule( ) << "duration" << Float( ) << Int32( )
				<< CPSetDuration_ );
	defs.context( "segments" )
		<< ( Rule( ) << "segment" << Enum( "segment-type" )
			<< ( List( ) << "values" << ( AtLeast( 2 ) << Numeric( ) ) )
			<< ( List( ) << "durations" << ( AtLeast( 1 ) << Integer( ) ) )
			<< CPSegmentVD_ )
		<< ( Rule( ) << "segment" << Enum( "segment-type" )
			<< ( List( ) << "durations" << ( AtLeast( 1 ) << Integer( ) ) )
			<< ( List( ) << "values" << ( AtLeast( 2 ) << Numeric( ) ) )
			<< CPSegmentDV_ );

	return defs;
}

}


/*= T_SyncTime ===============================================================*/

void T_SyncTime::setDuration(
		const float uDuration ,
		const uint32_t iDuration )
{
	this->uDuration = std::max( 1e-3f , uDuration );
	this->iDuration = std::max( 1u , iDuration );
	time = std::min( time , duration( ) );
}


/*= T_SyncCurves =============================================================*/

int32_t T_SyncCurves::indexOf(
		T_String const& name ) noexcept
{
	const auto idx( curves.indexOf( name ) );
	return idx == ebcl::T_HashIndex::INVALID_INDEX ? -1 : int32_t( idx );
}


/*= T_SyncCurveCache =========================================================*/

T_SyncCurveCache::T_SyncCurveCache(
		T_SyncTime const& time ,
		T_SyncCurves const& curves ,
		const uint32_t curve ) noexcept
	: curve( curve ) , curPos( 0 )
{
	auto const& c( curves.curves[ curve ] );
	const auto ns( c.segments.size( ) );
	assert( ns > 0 );

	uint32_t s = 0;
	for ( auto i = 0u ; i < ns ; i ++ ) {
		auto const& v( c.segments[ i ] );
		assert( v.durations.size( ) == v.values.size( ) - 1 );

		const auto nd( v.durations.size( ) );
		for ( auto j = 0u ; j < nd ; j ++ ) {
			const auto sStart( s * time.uDuration );
			if ( time.time >= sStart ) {
				curPos = segStarts.size( );
			}
			segStarts.add( sStart );
			segRefs.add( std::make_pair( i , j ) );
			s += v.durations[ j ];
			segEnds.add( std::min( s , time.iDuration ) * time.uDuration );
			if ( s > time.iDuration ) {
				return;
			}
		}
	}
}

/*----------------------------------------------------------------------------*/

uint32_t T_SyncCurveCache::findSegment(
		const float time ) const noexcept
{
	const auto ns( segStarts.size( ) );
	for ( auto i = 0u ; i < ns ; i ++ ) {
		if ( segStarts[ i ] <= time && segEnds[ i ] > time ) {
			return i;
		}
	}
	return ns;
}

/*----------------------------------------------------------------------------*/

float T_SyncCurveCache::valueAt(
		T_SyncTime const& time ,
		T_SyncCurves const& curves ,
		const float position ) const noexcept
{
	return segmentValue( time.time , findSegment( position ) ,
			curves.curves[ curve ].segments );
}

float T_SyncCurveCache::value(
		T_SyncTime const& time ,
		T_SyncCurves const& curves ) noexcept
{
	const auto t( time.time );

	// Check / update curPos
	const float ss0( curPos == segStarts.size( )
			? time.duration( )
			: segStarts[ curPos ] );
	if ( ss0 > t ) {
		curPos = findSegment( t );
	} else {
		while ( curPos < segStarts.size( ) && t >= segEnds[ curPos ] ) {
			curPos ++;
		}
	}

	// We got the actual index in curPos, now compute the value.
	return segmentValue( t , curPos , curves.curves[ curve ].segments );
}

/*----------------------------------------------------------------------------*/

float T_SyncCurveCache::segmentValue(
		float time ,
		uint32_t segIndex ,
		T_Array< T_SyncSegment > const& segments ) const noexcept
{
	const auto sss( segStarts.size( ) );
	if ( segIndex >= sss ) {
		assert( sss != 0 );
		segIndex = sss - 1;
		time = segEnds[ segIndex ];
	}

	auto const& idxp( segRefs[ segIndex ] );
	auto const& seg( segments[ idxp.first ] );

	// Interpolation factor
	const float st( segStarts[ segIndex ] );
	const float et( segEnds[ segIndex ] );
	const float v0 = ( time - st ) / ( et - st );
	float v = v0;
	if ( seg.type != T_SyncSegment::LINEAR ) {
		v *= v0;
		if ( seg.type == T_SyncSegment::SMOOTH ) {
			v *= 3 - 2 * v0;
		}
	}

	const auto pid( idxp.second );
	const float sv( seg.values[ pid ] );
	const float ev( seg.values[ pid + 1 ] );
#if 0
	printf( "[%.2f] gidx %d - seg %d idx %d - %f\n" , time , segIndex ,
			idxp.first , idxp.second , v * ( ev - sv ) + sv );
#endif
	return v * ( ev - sv ) + sv;
}


/*= T_SyncValues =============================================================*/

T_SyncValues::T_SyncValues( )
{
	values.add( 0 );
}

// ---------------------------------------------------------------------

void T_SyncValues::clear( )
{
	index.clear( );
	identifiers.clear( );
	values.clear( );
	overriden.clear( );
	values.add( 0 );
}

bool T_SyncValues::addValue(
		T_String const& name ,
		const float initial ) noexcept
{
	const uint32_t hash{ ComputeHash( name ) };
	uint32_t existing{ index.first( hash ) };
	while ( existing != T_HashIndex::INVALID_INDEX ) {
		if ( name == identifiers[ existing ] ) {
			return false;
		}
		existing = index.next( existing );
	}

	const auto li( values.size( ) );
	index.add( hash );
	identifiers.add( name );
	values.add( initial );
	std::swap( values[ li ] , values[ li - 1 ] );
	overriden.add( false );
	return true;
}

uint32_t T_SyncValues::indexOf(
		T_String const& name ) const noexcept
{
	const uint32_t hash{ ComputeHash( name ) };
	uint32_t existing{ index.first( hash ) };
	while ( existing != T_HashIndex::INVALID_INDEX ) {
		if ( name == identifiers[ existing ] ) {
			return existing;
		}
		existing = index.next( existing );
	}
	return values.size( ) - 1;
}


/*= A_SyncOverride ===========================================================*/

A_SyncOverride::A_SyncOverride(
		char const* const type ,
		T_String const& title ) noexcept
	: type_( T_String::Pooled( type ) ) , title_( title.size( ) + 1 )
{
	char const* src( title.data( ) );
	for ( auto i = 0u ; i < title_.size( ) - 1 ; i ++ ) {
		title_[ i ] =  src[ i ];
	}
	title_[ title_.size( ) - 1 ] = 0;
}

A_SyncOverride::~A_SyncOverride( ) { }

void A_SyncOverride::setup( ) noexcept
{
	const auto ni( inputs_.size( ) );
	assert( ni != 0 );
	assert( inputPos_.size( ) == 0 );

	T_StringBuilder sb;
	inputPos_.ensureCapacity( ni );
	for ( auto i = 0u ; i < ni ; i ++ ) {
		// FIXME: insufficient; the manager should be made aware of
		// the presence of an override for that value (and it should
		// fail for missing values).
		inputPos_.add( Globals::Sync( ).inputPos( inputs_[ i ] ) );
		if ( sb.size( ) ) {
			sb << ';';
		}
		sb << inputs_[ i ];
	}
	id_ = std::move( sb );
}

char const* A_SyncOverride::buildLabel(
		uint32_t& counter ,
		T_StringBuilder& sb ) noexcept
{
	sb.clear( ) << "temp label " << counter << '\0';
	counter ++;
	return sb.data( );
}


void A_SyncOverride::makeUI(
		uint32_t& counter ,
		T_StringBuilder& sb ) noexcept
{
	using namespace ImGui;

	if ( Checkbox( &title_[ 0 ] , &enabled_ ) ) {
		Globals::Sync( ).setOverridesActive( enabled_ , inputPos_.size( ) , &inputPos_[ 0 ] );
	}
	if ( !enabled_ ) {
		PushItemFlag( ImGuiItemFlags_Disabled , true );
		PushStyleVar( ImGuiStyleVar_Alpha , GetStyle( ).Alpha * 0.5f );
	}
	Indent( );
	PushItemWidth( -1 );
	makeEditWidgets( counter , sb );
	PopItemWidth( );
	Unindent( );
	if ( !enabled_ ) {
		PopItemFlag( );
		PopStyleVar( );
	}
}


/*= T_SyncOverrideSection ====================================================*/

T_SyncOverrideSection::T_SyncOverrideSection(
		T_String title ) noexcept
	: title( std::move( title ) ) ,
		cTitle( this->title.toOSString( ) )
{ }

void T_SyncOverrideSection::merge(
		T_SyncOverrideSection& other ) noexcept
{
	for ( auto& os : other.subsections ) {
		section( os->title ).merge( *os );
	}
	for ( auto& ov : other.overrides ) {
		overrides.add( ov->clone( ) );
	}
}

void T_SyncOverrideSection::makeUI(
		uint32_t& counter ,
		T_StringBuilder& tempSb ,
		const bool topLevel ) noexcept
{
	const bool display( topLevel
			? ImGui::CollapsingHeader( &cTitle[ 0 ] )
			: ImGui::TreeNode( &cTitle[ 0 ] ) );
	if ( !display ) {
		return;
	}

	for ( auto& os : subsections ) {
		os->makeUI( counter , tempSb );
	}
	if ( subsections.size( ) && overrides.size( ) ) {
		ImGui::Separator( );
	}
	for ( auto& ov : overrides ) {
		ov->makeUI( counter , tempSb );
	}

	if ( !topLevel ) {
		ImGui::TreePop( );
	}
}

T_SyncOverrideSection& T_SyncOverrideSection::section(
		T_String const& name ) noexcept
{
	for ( auto& ov : subsections ) {
		if ( ov->title == name ) {
			return *ov;
		}
	}
	return *subsections[ subsections.add(
			NewOwned< T_SyncOverrideSection >( name ) ) ];
}

T_SyncOverrideSection const* T_SyncOverrideSection::section(
		T_String const& name ) const noexcept
{
	for ( auto& ov : subsections ) {
		if ( ov->title == name ) {
			return ov.get( );
		}
	}
	return nullptr;
}


/*= T_SyncOverrideVisitor ====================================================*/

T_SyncOverrideVisitor::T_OpElement T_SyncOverrideVisitor::nodeBrowser(
		const T_Element element ,
		const uint32_t child )
{
	if ( element.hasType< A_SyncOverride* >( ) ) {
		return {};
	}

	auto& section( *element.value< T_SyncOverrideSection* >( ) );
	const auto nss( section.subsections.size( ) );
	if ( child < nss ) {
		return T_OpElement{ section.subsections[ child ].get( ) };
	}
	if ( child - nss < section.overrides.size( ) ) {
		return T_OpElement{ section.overrides[ child - nss ].get( ) };
	}
	return {};
}


/*= T_SyncManager ============================================================*/

T_SyncManager::T_SyncManager( )
	: pConfig_( MakeCurvesParser_( ) ) ,
		watcher_{ Globals::Watcher( ) , [this](){ curvesChanged_( ); } } ,
		soRoot_( "*root*" )
{
	watcher_.watch( "curves.srd" );
	loadCurves( );
}

/*----------------------------------------------------------------------------*/

void T_SyncManager::setDuration(
		const float uDuration ,
		const uint32_t iDuration )
{
	time_.setDuration( uDuration , iDuration );
	updateCurveCaches( );
}

void T_SyncManager::setTime(
		const float time )
{
	time_.setTime( time );
	updateValues( );
}

void T_SyncManager::updateTime( ) noexcept
{
	if ( playing_ ) {
		const float time( SDL_GetTicks( ) * 1e-3 );
		if ( playingPrevious_ ) {
			timeDelta( time - lastFrame_ );
			playing_ = !finished( );
		}
		lastFrame_ = time;
	}
	playingPrevious_ = playing_;
}

/*----------------------------------------------------------------------------*/

void T_SyncManager::clearInputs( ) noexcept
{
	clearOverrides( );
	values_.clear( );
}

/*----------------------------------------------------------------------------*/

void T_SyncManager::clearCurves( )
{
	curves_.clear( );
	updateCurveCaches( );
	modified_ = true;
}

void T_SyncManager::setCurve(
		T_SyncCurve curve )
{
	curves_.setCurve( curve );
	updateCurveCaches( );
	modified_ = true;
}

void T_SyncManager::removeCurve(
		T_String const& curve ) noexcept
{
	if ( curves_.removeCurve( curve ) ) {
		updateCurveCaches( );
		modified_ = true;
	}
}

void T_SyncManager::curvesChanged_( )
{
	if ( modified_ ) {
		fileChanged_ = true;
	} else {
		loadCurves( );
	}
}

bool T_SyncManager::loadCurves( )
{
	printf( "Loading curves data\n" );
	try {
		using namespace ebcl;
		const T_SRDParserConfig cfg( MakeCurvesParser_( ) );
		T_File file( "curves.srd" , E_FileMode::READ_ONLY );
		file.open( );

		T_FileInputStream fis( file );
		T_SRDParser parser( cfg );
		T_SRDTextReader reader( parser );
		reader.read( "curves.srd" , fis );

		auto p( parser.getData< T_SharedPtr< T_ParserOutput_ > >( ) );
		curves_ = std::move( p->curves );
		if ( p->time ) {
			time_.iDuration = p->time->iDuration;
			time_.uDuration = p->time->uDuration;
		} else {
			time_.iDuration = 3600;
			time_.uDuration = 1.f / 60.f;
		}
	} catch ( ebcl::X_StreamError const& e ) {
		printf( "... ERR %s\n" , e.what( ) );
		return false;

	} catch ( ebcl::X_SRDErrors const& e ) {
		T_StringBuilder sb;
		auto const ne( e.errors.size( ) );
		for ( auto i = 0u ; i < ne ; i ++ ) {
			auto const& err( e.errors[ i ] );
			sb << "... ERR " << err.location( ) << ": " << err.error( ) << '\n';
		}
		sb << '\0';
		printf( "%s" , sb.data( ) );
		return false;
	}
	printf( "... success\n" );

	updateCurveCaches( );
	modified_ = fileChanged_ = false;
	return true;
}

/*----------------------------------------------------------------------------*/

void T_SyncManager::updateCurveCaches( )
{
	curveCaches_.clear( );
	const uint32_t nv( values_.identifiers.size( ) );
	for ( auto i = 0u ; i < nv ; i ++ ) {
		auto const& id( values_.identifiers[ i ] );
		const auto cp( curves_.indexOf( id ) );
		if ( cp < 0 ) {
			curveCaches_.addNew( );
		} else {
			curveCaches_.add( NewOwned< T_SyncCurveCache >(
				time_ , curves_ , cp
			) );
		}
	}
	updateValues( );
}

void T_SyncManager::updateValues( )
{
	const auto nv( values_.identifiers.size( ) );
	assert( nv == curveCaches_.size( ) );
	for ( auto i = 0u ; i < nv ; i ++ ) {
		auto const& cc( curveCaches_[ i ] );
		if ( !cc || values_.overriden[ i ] ) {
			continue;
		}
		values_.values[ i ] = cc->value( time_ , curves_ );
	}
}

/*----------------------------------------------------------------------------*/

void T_SyncManager::clearOverrides( ) noexcept
{
	mouseDelegate_ = nullptr;
	soRoot_.subsections.clear( );
	soRoot_.overrides.clear( );
	soTable_.clear( );
}

void T_SyncManager::mergeOverrides(
		T_SyncOverrideSection& overrides )
{
	assert( overrides.overrides.empty( ) );
	soRoot_.merge( overrides );

	soVisitor_.visitor.visit( &soRoot_ ,
		[this]( T_SyncOverrideVisitor::T_Element node , bool exit ) -> bool {
			if ( exit || node.hasType< T_SyncOverrideSection* >( ) ) {
				return true;
			}
			auto& ovr( *node.value< A_SyncOverride* >( ) );
			ovr.setup( );
			soTable_.add( ovr.id( ) , &ovr );
			return false;
		}
	);
}

void T_SyncManager::setOverridesActive(
		const bool active ,
		const uint32_t n ,
		uint32_t const* const pos )
{
	for ( auto i = 0u ; i < n ; i ++ ) {
		assert( values_.overriden[ pos[ i ] ] != active );
		values_.overriden[ pos[ i ] ] = active;
	}
	if ( !active ) {
		for ( auto i = 0u ; i < n ; i ++ ) {
			const auto p{ pos[ i ] };
			auto const& cc( curveCaches_[ p ] );
			if ( cc ) {
				values_.values[ p ] = cc->value( time_ , curves_ );
			}
		}
	}
}

void T_SyncManager::visitOverrides(
		T_SyncOverrideVisitor::F_NodeAction visitor )
{
	soVisitor_.visitor.visit( &soRoot_ , visitor );
}

/*------------------------------------------------------------------------------*/

void T_SyncManager::makeOverridesWindow( )
{
	if ( !ovWindow_ ) {
		return;
	}

	using namespace ImGui;
	auto const& dspSize( GetIO( ).DisplaySize );
	SetNextWindowSize( ImVec2( dspSize.x * .25f , dspSize.y * .66f - 20 ) , ImGuiSetCond_Appearing );
	SetNextWindowPos( ImVec2( 0 , 20 ) , ImGuiSetCond_Appearing );
	Begin( "Input overrides" , &ovWindow_ ,
			ImGuiWindowFlags_NoCollapse );

	if ( soRoot_.subsections.empty( ) ) {
		Text( "No overrides have been defined." );
	} else {
		T_StringBuilder temp;
		uint32_t counter{ 0 };
		for ( auto& section : soRoot_.subsections ) {
			section->makeUI( counter , temp , true );
		}
	}
	End( );
}

/*------------------------------------------------------------------------------*/

void T_SyncManager::handleDragAndDrop(
		ImVec2 const& move ,
		T_KeyboardModifiers modifiers ,
		T_MouseButtons buttons ) noexcept
{
	if ( mouseDelegate_ ) {
		mouseDelegate_->handleDragAndDrop( move , modifiers , buttons );
	}
}

void T_SyncManager::handleWheel(
		const float wheel ,
		T_KeyboardModifiers modifiers ,
		T_MouseButtons buttons ) noexcept
{
	if ( mouseDelegate_ ) {
		mouseDelegate_->handleWheel( wheel , modifiers , buttons );
	}
}