#include "externals.hh"
#include "syncview.hh"
#include "globals.hh"
#include "window.hh"
#include "syncedit.hh"

#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>

using namespace ebcl;

namespace {

/*= FAKE TABS HELPER =========================================================*/

bool FakeTab_(
		char const* const name ,
		const bool disabled ,
		const float width = 0.f )
{
	using namespace ImGui;
	if ( disabled ) {
		PushItemFlag( ImGuiItemFlags_Disabled , true );
		PushStyleVar( ImGuiStyleVar_Alpha ,
				GetStyle( ).Alpha * .5f );
	}
	const bool rv( Button( name , ImVec2{ width , 0.f } ) );
	if ( disabled ) {
		PopItemFlag( );
		PopStyleVar( );
	}
	return rv;
}


/*= T_ChangeDurationDialog_ ==================================================*/

class T_ChangeDurationDialog_ : public A_ModalDialog
{
    private:
	const uint32_t units0_;
	const float uSize0_;
	const uint32_t uPerMinute0_;
	uint32_t units_;
	float uSize_;
	uint32_t uPerMinute_;
	bool scale_{ true };

    protected:
	bool drawDialog( ) noexcept override;
	void onOK( ) noexcept override;

    public:
	T_ChangeDurationDialog_(
			uint32_t units ,
			float uSize ) noexcept;
};

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

T_ChangeDurationDialog_::T_ChangeDurationDialog_(
		const uint32_t units ,
		const float uSize ) noexcept
	: A_ModalDialog{ "Set demo duration...##duration-dialog" } ,
		units0_{ units } , uSize0_{ std::max( 1.f / 60.f , uSize ) } ,
		uPerMinute0_{ uint32_t( ImClamp( roundf( 60.f / uSize0_ ) , 1.f , 3600.f ) ) } ,
		units_{ units0_ } , uSize_{ uSize0_ } , uPerMinute_{ uPerMinute0_ }
{
	setInitialSize( 300.f , 180.f );
}

bool T_ChangeDurationDialog_::drawDialog( ) noexcept
{
	using namespace ImGui;

	int tUnits( units_ );
	if ( DragInt( "Duration##units" , &tUnits , .1f , 1 , INT32_MAX , "%.0f unit(s)" ) ) {
		units_ = uint32_t( std::min( INT32_MAX , std::max( 1 , tUnits ) ) );
	}

	float tDuration( units_ * uSize_ );
	if ( DragFloat( "##seconds" , &tDuration , 1.f , .001f , FLT_MAX , "%.3f second(s)" ) ) {
		units_ = std::min( uint32_t( INT32_MAX ) , std::max( 1u ,
				uint32_t( roundf( tDuration / uSize_ ) ) ) );
	}

	Separator( );

	int tUsize( floorf( uSize_ * 1000.f ) );
	if ( SliderInt( "Units" , &tUsize , 16 , 2000 , "%.0f ms" ) ) {
		const float pDur{ uSize_ * units_ };
		uSize_ = std::min( 2.f , std::max( 1.f / 60.f ,
					.001f * tUsize ) );
		uPerMinute_ = roundf( 60.f / uSize_ );
		units_ = uint32_t( roundf( pDur / uSize_ ) );
	}

	int tPerMin( uPerMinute_ );
	if ( SliderInt( "Units/minute" , &tPerMin , 30 , 3600 ) ) {
		const float pDur{ uSize_ * units_ };
		uPerMinute_ = std::max( 30u , std::min( 3600u , uint32_t( tPerMin ) ) );
		uSize_ = 60.f / uPerMinute_;
		units_ = uint32_t( roundf( pDur / uSize_ ) );
	}

	if ( uPerMinute0_ == uPerMinute_ ) {
		PushItemFlag( ImGuiItemFlags_Disabled , true );
		PushStyleVar( ImGuiStyleVar_Alpha ,
				GetStyle( ).Alpha * .5f );
	}
	Checkbox( "Scale curves" , &scale_ );
	if ( uPerMinute0_ == uPerMinute_ ) {
		PopItemFlag( );
		PopStyleVar( );
	}

	return units_ != units0_ || uPerMinute_ != uPerMinute0_;
}

void T_ChangeDurationDialog_::onOK( ) noexcept
{
	SyncEditor::SetDuration( units_ ,
			uPerMinute_ != uPerMinute0_ ? uSize_ : uSize0_ ,
			scale_ );
}


/*= T_SyncViewImpl_ ==========================================================*/

struct T_SyncViewImpl_
{
	static constexpr float SeqHeaderHeight = 24;
	static constexpr float BarWidth = 40;

	const uint32_t ColFrame{ ImGui::GetColorU32( ImVec4{ 0 , 0 , 0 , .8 } ) };
	const uint32_t ColHeader{ ImGui::GetColorU32( ImVec4{ .5 , .5 , .5 , .8 } ) };
	const uint32_t ColHeaderText{ ImGui::GetColorU32( ImVec4{ 0 , 0 , 0 , 1 } ) };
	const uint32_t ColMain{ ImGui::GetColorU32( ImVec4{ .4 , .4 , .4 , .8 } ) };
	const uint32_t ColSelection{ ImGui::GetColorU32( ImVec4{ .8 , 1 , .8 , .2 } ) };
	const ImVec2 BtSize{ 20 , 0 };

	float zoomLevel{ 0.f };
	float startPos{ 0.f };
	bool followTime{ true };

	bool display( ) noexcept;

	void computeMetrics( float innerWidth ) noexcept;
	void sequencerWidget( ) noexcept;
	void sequencerHeader( ImRect const& bb ) noexcept;
	void sequencerBody( ImRect const& bb ) noexcept;

	void displayCurveSelectorWindow( ) noexcept;
	void displayCurveSelector( ) noexcept;
	void displayOverrideSelector( ) noexcept;

	// Misc stuff
	T_StringBuilder stringBuffer;	// XXX damn this shit to fucking hell

	// Computed metrics
	float barWidth;
	float cursorPos;
	uint32_t startBar;
	float startBarPos;
	float timePerBar;
	float totalPixels;
	float startPixel;

	// Zoom area selection
	bool zoomInProgress{ false };
	bool justZoomed{ false };
	float firstZoomPixel;
	float curZoomPixel;

	// Curve display / edition
	enum E_SubWindow {
		SW_NONE ,
		SW_CURVE_SELECTOR ,
		SW_OVERRIDE_SELECTOR ,
	};
	E_SubWindow sub{ SW_NONE };
	T_KeyValueTable< T_String , bool > sCurves;
	T_Set< T_String > sOverrides;
	T_String curveFinder;
};

constexpr float T_SyncViewImpl_::BarWidth;

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

bool T_SyncViewImpl_::display( ) noexcept
{
	using namespace ImGui;
	auto const& dspSize( GetIO( ).DisplaySize );
	auto& sync( Globals::Sync( ) );

	// Window set-up
	SetNextWindowSize( ImVec2( dspSize.x , dspSize.y * .34f ) , ImGuiSetCond_Appearing );
	SetNextWindowPos( ImVec2( 0 , dspSize.y * .66f ) , ImGuiSetCond_Appearing );
	bool displayed{ true };
	Begin( "Sequencer" , &displayed , ImGuiWindowFlags_NoCollapse
				| ImGuiWindowFlags_NoScrollWithMouse );
	if ( !displayed ) {
		End( );
		return false;
	}

	//----------------------------------------------------------------------
	// Toolbar

	if ( Button( sync.playing( ) ? ICON_FA_STOP : ICON_FA_PLAY , BtSize ) ) {
		sync.playing( ) = !sync.playing( ) && !sync.finished( );
	}
	if ( IsItemHovered( ) ) {
		BeginTooltip( );
		Text( sync.playing( ) ? "Stop" : "Play" );
		EndTooltip( );
	}

	SameLine( );

	if ( Button( ICON_FA_BACKWARD , BtSize ) ) {
		sync.setTime( 0 );
	}
	if ( IsItemHovered( ) ) {
		BeginTooltip( );
		Text( "Rewind to 00:00.000" );
		EndTooltip( );
	}

	SameLine( );
	VerticalSeparator( );
	SameLine( );

	Text( ICON_FA_SEARCH );
	bool zoomHovered{ IsItemHovered( ) };
	SameLine( );
	PushItemWidth( 100 );
	SliderFloat( "##zoom" , &zoomLevel , 0 , 1 , "%.2f" );
	if ( zoomHovered || IsItemHovered( ) ) {
		BeginTooltip( );
		Text( "Zoom level" );
		EndTooltip( );
	}
	PopItemWidth( );

	SameLine( );

	if ( Button( followTime ? ICON_FA_LOCK : ICON_FA_UNLOCK , BtSize ) ) {
		followTime = !followTime;
	}
	if ( IsItemHovered( ) ) {
		BeginTooltip( );
		Text( followTime ? "Follows the current position.\nClick to untie."
				: "Not tied to the  current position.\nClick to follow." );
		EndTooltip( );
	}

	SameLine( );
	VerticalSeparator( );
	SameLine( );

	if ( Button( ICON_FA_CLOCK_O , BtSize ) ) {
		Globals::Window( ).pushDialog( NewOwned< T_ChangeDurationDialog_ >(
				sync.durationUnits( ) , sync.durationUnitSize( ) ) );
	}
	if ( IsItemHovered( ) ) {
		BeginTooltip( );
		Text( "Change duration and time units." );
		EndTooltip( );
	}

	SameLine( );
	VerticalSeparator( );
	SameLine( );

	if ( Button( ICON_FA_LINE_CHART ) ) {
		const bool displaySelector{ sub == SW_CURVE_SELECTOR
			|| sub == SW_OVERRIDE_SELECTOR };
		sub = displaySelector ? SW_NONE : SW_CURVE_SELECTOR;
		curveFinder = T_String{};
	}
	if ( IsItemHovered( ) ) {
		BeginTooltip( );
		Text( "Select curves or sets thereof to display & edit." );
		EndTooltip( );
	}

	//----------------------------------------------------------------------
	// Sequencer widget & subwindows

	BeginChild( "##sequencer" , ImVec2{ 0 , 0 } , false ,
			ImGuiWindowFlags_NoScrollWithMouse );
	PushItemWidth( -1 );
	sequencerWidget( );
	PopItemWidth( );
	EndChild( );
	End( );

	switch ( sub ) {
	    case SW_NONE:
		break;

	    case SW_CURVE_SELECTOR:
	    case SW_OVERRIDE_SELECTOR:
		displayCurveSelectorWindow( );
		break;
	}

	return true;
}

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

void T_SyncViewImpl_::sequencerWidget( ) noexcept
{
	using namespace ImGui;

	const auto width{ CalcItemWidth( ) };
	auto* const win( GetCurrentWindow( ) );
	const auto seqId{ win->GetID( "##sequencer" ) };

	const ImVec2 cPos( win->DC.CursorPos );
	const ImVec2 ws( GetWindowContentRegionMax( ) );

	auto& style( ImGui::GetStyle( ) );
	const ImRect bbHeader{ cPos , cPos + ImVec2( width , SeqHeaderHeight ) };
	const ImRect bbDisplay{ ImVec2{ cPos.x , bbHeader.Max.y } ,
		ImVec2{ cPos.x + width , GetWindowPos( ).y + ws.y - style.FramePadding.y * 2 } };
	const ImRect bbAll{ bbHeader.Min , bbDisplay.Max };

	ItemSize( bbAll , style.FramePadding.y );
	if ( !ItemAdd( bbAll , seqId ) ) {
		return;
	}
	const bool hovered{ ItemHoverable( bbAll , seqId ) };
	computeMetrics( std::max( 0.f , width - 2.f ) );

	BeginGroup( );
	const auto hdrId{ win->GetID( "##header" ) };
	const auto dspId{ win->GetID( "##display" ) };
	PushID( seqId );
	if ( ItemAdd( bbHeader , hdrId ) ) {
		PushID( hdrId );
		sequencerHeader( bbHeader );
		PopID( );
	}
	if ( bbDisplay.Min.y < bbDisplay.Max.y && ItemAdd( bbDisplay , dspId ) ) {
		PushID( dspId );
		sequencerBody( bbDisplay );
		PopID( );
	}
	PopID( );

	auto& io( GetIO( ) );
	if ( hovered && ( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) ) {
		SetActiveID( seqId , win );
		FocusWindow( win );
	}

	const bool active( GetCurrentContext( )->ActiveId == seqId );
	if ( hovered && !active && io.MouseWheel != 0 ) {
		zoomLevel = ImSaturate( zoomLevel + .025 * io.MouseWheel );
	} else if ( active ) {
		if ( io.MouseDown[ 0 ] ) {
			const float p{ io.MousePos.x - bbAll.Min.x + startPixel };
			auto& sync( Globals::Sync( ) );
			sync.setTime( p * Globals::Sync( ).duration( ) / totalPixels );
		}
		if ( io.MouseDown[ 1 ] ) {
			const float p{ io.MousePos.x - bbAll.Min.x + startPixel };
			if ( !zoomInProgress ) {
				firstZoomPixel = p;
				zoomInProgress = true;
			}
			curZoomPixel = p;
		} else if ( zoomInProgress ) {
			zoomInProgress = false;
			justZoomed = true;
			const auto zMin{ std::min( firstZoomPixel , curZoomPixel ) } ,
			      zMax{ std::max( firstZoomPixel , curZoomPixel ) } ,
			      diff{ zMax - zMin };
			if ( diff > 4 ) {
				auto& sync( Globals::Sync( ) );
				const float u( sync.durationUnits( ) );
				startPos = zMin * u / totalPixels;
				if ( ( width - 2.f ) / u >= BarWidth ) {
					zoomLevel = 0;
				} else {
					const auto length{ std::min( u , diff * u / totalPixels ) };
					const auto ppu{ std::min( ( width - 2 ) / length , BarWidth ) };
					zoomLevel = ( ppu - BarWidth ) / ( BarWidth - width / u ) + 1;
				}
			}
		}
		if ( !( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) ) {
			ClearActiveID( );
		}
	}

	EndGroup( );
}

void T_SyncViewImpl_::computeMetrics(
		const float innerWidth ) noexcept
{
	auto& sync( Globals::Sync( ) );
	const uint32_t units{ sync.durationUnits( ) };
	zoomLevel = ImSaturate( zoomLevel );
	const float zoom1Pixels{ std::max( units * BarWidth , innerWidth ) };
	totalPixels = zoomLevel * ( zoom1Pixels - innerWidth ) + innerWidth;
	const uint32_t totalBars{ [=](){
			const float b{ std::max( std::min( totalPixels / BarWidth , float( units ) ) , 1.f ) };
			const float mod{ fmod( b , 1.f ) };
			return uint32_t( b + ( mod ? ( 1  - mod ) : 0 ) );
		}() };
	const float unitsPerBar{ float( units ) / totalBars };
	barWidth = totalPixels / totalBars;
	const float absCursorPos{ sync.time( ) * totalPixels / sync.duration( ) };
	if ( followTime ) {
		const float dispUnits{ innerWidth * units / totalPixels };
		const float uSize{ sync.durationUnitSize( ) };
		const float endPos{ std::min( startPos + dispUnits , float( units ) ) };
		startPos = endPos - dispUnits;
		const float spp{ startPos * totalPixels / units };
		const float epp{ endPos * totalPixels / units };
		if ( absCursorPos < spp || absCursorPos > epp ) {
			if ( justZoomed ) {
				followTime = false;
			} else {
				startPos = std::max( 0.f , sync.time( ) / uSize - unitsPerBar * .5f );
			}
		}
	}
	const float unadjustedStartPixel{ totalPixels * startPos / units };
	if ( unadjustedStartPixel + innerWidth > totalPixels ) {
		startPos = std::max( 0.f , totalPixels - innerWidth );
	}
	startPixel = totalPixels * startPos / units;
	startBar = [=](){
			const float b{ startPixel * totalBars / totalPixels };
			const float mod{ fmod( b , 1.f ) };
			return uint32_t( std::max( 0 , int32_t( b - ( mod ? mod : 1 ) ) ) );
		}();
	startBarPos = startBar * barWidth - startPixel;
	cursorPos = absCursorPos - startPixel;
	timePerBar = unitsPerBar * sync.durationUnitSize( );
	assert( startBarPos <= 0 );
	assert( totalPixels >= innerWidth );

	justZoomed = false;
}

void T_SyncViewImpl_::sequencerHeader(
		ImRect const& bb ) noexcept
{
	using namespace ImGui;
	auto* const dl( GetWindowDrawList( ) );
	const ImRect inner{ bb.Min + ImVec2{ 1 , 1 } , bb.Max - ImVec2{ 1 , 1 } };

	dl->AddRectFilled( inner.Min , inner.Max , ColHeader );
	dl->AddRect( bb.Min , bb.Max , ColFrame );

	if ( cursorPos >= 0 && cursorPos <= inner.GetWidth( ) ) {
		auto* const dl( GetWindowDrawList( ) );
		dl->AddLine( inner.Min + ImVec2{ cursorPos , 0 } ,
				ImVec2{ inner.Min.x + cursorPos , inner.Max.y - 1 } ,
				GetColorU32( ImVec4{ 1 , 1 , 1 , .5 } ) );
	}

	PushFont( Globals::Window( ).smallFont( ) );
	PushStyleColor( ImGuiCol_Text , ColHeaderText );
	auto pos{ startBarPos };
	auto bar{ startBar };
	const auto max{ bb.GetWidth( ) + barWidth - 2.f };
	char buffer[ 12 ];
	while ( pos < max ) {
		const ImVec2 taStart{ inner.Min + ImVec2{ pos - barWidth * .5f , 0 } };
		const ImVec2 taEnd{ taStart + ImVec2{ barWidth , inner.Max.y - inner.Min.y } };

		const float time{ bar * timePerBar };
		const float msf{ fmod( time , 1.f ) };
		const float sf{ fmod( time - msf , 60.f ) };
		snprintf( buffer , sizeof( buffer ) , "%02d:%02d.%03d" ,
				uint32_t( ( time - msf - sf ) / 60.f ) ,
				uint32_t( sf ) , uint32_t( msf * 1000.f ) );
		RenderTextClipped( taStart , taEnd , buffer , nullptr , nullptr ,
				ImVec2{ .5f , .2f + ( ( bar % 2 ) ? .6f : 0.f ) } , &inner );
		pos += barWidth;
		bar ++;
	}
	PopStyleColor( );
	PopFont( );
}

void T_SyncViewImpl_::sequencerBody(
		ImRect const& bb ) noexcept
{
	using namespace ImGui;
	auto* const dl( GetWindowDrawList( ) );
	const ImRect inner{ bb.Min + ImVec2{ 1 , 1 } , bb.Max - ImVec2{ 1 , 1 } };

	dl->AddRectFilled( inner.Min , inner.Max , ColMain );
	dl->AddRect( bb.Min , bb.Max , ColFrame );
	if ( zoomInProgress ) {
		const float z0{ ImClamp( firstZoomPixel - startPixel + inner.Min.x , inner.Min.x , inner.Max.x ) } ,
			      z1{ ImClamp( curZoomPixel - startPixel + bb.Min.x , inner.Min.x , inner.Max.x ) };
		const float zMin{ std::min( z0 , z1 ) } , zMax{ std::max( z0 , z1 ) };
		if ( zMin != zMax ) {
			dl->AddRectFilled( ImVec2{ zMin , inner.Min.y } ,
					ImVec2{ zMax , inner.Max.y } ,
					ColSelection );
		}
	}

	auto pos{ startBarPos };
	auto bar{ startBar };
	const auto max{ bb.GetWidth( ) + barWidth - 2.f };
	while ( pos < max ) {
		if ( pos >= 0 && pos < inner.GetWidth( ) ) {
			dl->AddLine( bb.Min + ImVec2{ pos , 0 } ,
					ImVec2{ inner.Min.x + pos , inner.Max.y } ,
					0xff000000 );
		}
		pos += barWidth;
		bar ++;
	}

	if ( cursorPos >= 0 && cursorPos <= inner.GetWidth( ) ) {
		auto* const dl( GetWindowDrawList( ) );
		dl->AddLine( inner.Min + ImVec2{ cursorPos , -1 } ,
				ImVec2{ inner.Min.x + cursorPos , inner.Max.y - 1 } ,
				0xffffffff );
	}
}

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

void T_SyncViewImpl_::displayCurveSelectorWindow( ) noexcept
{
	using namespace ImGui;
	auto const& dspSize( GetIO( ).DisplaySize );

	// Window set-up
	SetNextWindowSize( ImVec2( dspSize.x * .25f , dspSize.y * .66f - 20 ) , ImGuiSetCond_Appearing );
	SetNextWindowPos( ImVec2( dspSize.x * .75f , 20 ) , ImGuiSetCond_Appearing );
	bool displayed{ true };
	Begin( "Display curves" , &displayed , ImGuiWindowFlags_NoCollapse );
	if ( !displayed ) {
		End( );
		sub = SW_NONE;
		return;
	}

	// "Tabs"
	const ImVec2 ws( GetWindowContentRegionMax( ) );
	auto const& style( GetStyle( ) );
	const float innerWidth{ ws.x - 2 * style.FramePadding.x };
	constexpr float nButtons{ 2.f };
	const float buttonWidth{ std::max( 50.f ,
			( innerWidth - nButtons * style.FramePadding.x ) / nButtons ) };

	if ( FakeTab_( "Individual inputs" , sub == SW_CURVE_SELECTOR , buttonWidth ) ) {
		sub = SW_CURVE_SELECTOR;
	}
	SameLine( 0 );
	if ( FakeTab_( "Overrides" , sub == SW_OVERRIDE_SELECTOR , buttonWidth ) ) {
		sub = SW_OVERRIDE_SELECTOR;
	}

	// Content
	switch ( sub ) {
	    case SW_CURVE_SELECTOR:
		displayCurveSelector( );
		break;
	    case SW_OVERRIDE_SELECTOR:
		displayOverrideSelector( );
		break;
	    default:
		fprintf( stderr , "unexpected bullshit in sync view\n" );
		std::abort( );
	}
	End( );
}

void T_SyncViewImpl_::displayCurveSelector( ) noexcept
{
	using namespace ImGui;
	T_Array< T_String > names{ Globals::Sync( ).inputNames( ) };

	// Search box; FIXME, this is utterly hacky
	stringBuffer.clear( ) << curveFinder;
	while ( stringBuffer.size( ) < 100 ) {
		stringBuffer << '\0';
	}
	Text( ICON_FA_SEARCH );
	SameLine( );
	PushItemWidth( -1 );
	if ( InputText( "##find" , const_cast< char* >( stringBuffer.data( ) ) ,
				stringBuffer.size( ) ) ) {
		curveFinder = T_String{ stringBuffer.data( ) ,
			uint32_t( strlen( stringBuffer.data( ) ) ) };
	}
	PopItemWidth( );
	if ( curveFinder ) {
		for ( auto i = 0u ; i < names.size( ) ; ) {
			auto const& n( names[ i ] );
			if ( n.find( curveFinder ) == -1 ) {
				names.removeSwap( i );
			} else {
				i ++;
			}
		}
	}
	names.sort( );

	// The list
	ImGui::BeginChild( "content" );
	for ( auto const& n : names ) {
		const bool present{ sCurves.contains( n ) };
		const bool overriden{ present && *sCurves.get( n ) };

		if ( overriden ) {
			PushItemFlag( ImGuiItemFlags_Disabled , true );
			PushStyleVar( ImGuiStyleVar_Alpha ,
					GetStyle( ).Alpha * .5f );
		}

		bool select{ present };
		stringBuffer.clear( ) << n << '\0';
		if ( Checkbox( stringBuffer.data( ) , &select ) ) {
			if ( select ) {
				sCurves.add( n , false );
			} else {
				sCurves.remove( n );
			}
		}

		if ( overriden ) {
			PopItemFlag( );
			PopStyleVar( );
		}
	}
	EndChild( );
}

void T_SyncViewImpl_::displayOverrideSelector( ) noexcept
{
	using namespace ImGui;

	BeginChild( "content" );
	Globals::Sync( ).visitOverrides( [&]( T_SyncOverrideVisitor::T_Element element , const bool exit ) {
		if ( element.hasType< T_SyncOverrideSection* >( ) ) {
			auto const& sos{ *element.value< T_SyncOverrideSection* >( ) };
			if ( sos.title == "*root*" ) {
				return true;
			}
			if ( exit ) {
				TreePop( );
			} else {
				return TreeNodeEx( &sos.cTitle[ 0 ] ,
						ImGuiTreeNodeFlags_DefaultOpen );
			}
		} else if ( exit ) {
			auto const& ov{ *element.value< A_SyncOverride* >( ) };
			auto const& id{ ov.id( ) };
			auto const& in{ ov.inputNames( ) };
			const bool present{ sOverrides.contains( id ) };
			const bool hasCurves{ !present && [&](){
				for ( auto i = 0u ; i < in.size( ) ; i ++ ) {
					if ( sCurves.contains( in[ i ] ) ) {
						return true;
					}
				}
				return false;
			}() };

			if ( hasCurves ) {
				PushItemFlag( ImGuiItemFlags_Disabled , true );
				PushStyleVar( ImGuiStyleVar_Alpha ,
						GetStyle( ).Alpha * .5f );
			}

			bool select{ present };
			if ( Checkbox( ov.title( ) , &select ) ) {
				if ( select ) {
					sOverrides.add( id );
					for ( auto i = 0u ; i < in.size( ) ; i ++ ) {
						sCurves.add( in[ i ] , true );
					}
				} else {
					sOverrides.remove( id );
					for ( auto i = 0u ; i < in.size( ) ; i ++ ) {
						sCurves.remove( in[ i ] );
					}
				}
			}

			if ( hasCurves ) {
				PopItemFlag( );
				PopStyleVar( );
			}
		}
		return true;
	} );
	EndChild( );
}

} // namespace <anon>


/*= T_SyncView ===============================================================*/

T_SyncView::T_SyncView( ) noexcept
	: A_PrivateImplementation( new T_SyncViewImpl_( ) )
{ }

bool T_SyncView::display( ) noexcept
{
	return p< T_SyncViewImpl_ >( ).display( );
}