#include "externals.hh"

#include "c-syncoverrides.hh"

#include <ebcl/SRDParser.hh>


using namespace sov;

#define M_SETOPT_( FIELD , VAR ) \
	if ( FIELD ) { return false; } \
	FIELD = (VAR); \
	return true


/*= PARSER DEFINITIONS =========================================================*/

namespace {

using namespace ebcl;
using SP_Section = T_SharedPtr< T_SyncOverrideSection >;

bool EnterSection_( T_SRDParserData const& data )
{
	*( data.targetData ) = NewShared< T_SyncOverrideSection >( (*data.input)[ 1 ].stringValue( ) );
	return true;
}

bool ExitSection_( T_SRDParserData const& data )
{
	auto& section( data.currentData->value< SP_Section >( ) );
	auto& parent( data.targetData->value< SP_Section >( ) );

	if ( section->overrides.empty( ) && section->subsections.empty( ) ) {
		T_StringBuilder sb;
		sb << "empty section '" << section->title << "'";
		data.errors.add( std::move( sb ) , (*data.input)[ 0 ] );
	} else {
		parent->subsections.add( section.makeOwned( ) );
	}

	return true;
}

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

using SP_Float = T_SharedPtr< A_Float >;

bool EnterFloat1_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	SP_Float ptr{ NewShared< T_Float >( input[ 2 ].stringValue( ) ,
			input[ 1 ].stringValue( ) ) };
	ptr->location( ) = input[ 0 ].location( );
	*( data.targetData ) = std::move( ptr );
	return true;
}

bool EnterFloat2_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	SP_Float ptr{ NewShared< T_Float2 >(
			input[ 2 ].stringValue( ) , input[ 3 ].stringValue( ) ,
			input[ 1 ].stringValue( ) ) };
	if ( ptr->inputNames( ).size( ) != 2 ) {
		data.errors.add( "duplicate input names" , input[ 3 ].location( ) );
	}
	ptr->location( ) = input[ 0 ].location( );
	*( data.targetData ) = std::move( ptr );
	return true;
}

bool EnterFloat3_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	SP_Float ptr{ NewShared< T_Float3 >(
			input[ 2 ].stringValue( ) , input[ 3 ].stringValue( ) ,
			input[ 4 ].stringValue( ) ,
			input[ 1 ].stringValue( ) ) };
	if ( ptr->inputNames( ).size( ) != 3 ) {
		data.errors.add( "duplicate input names" , input[ 3 ].location( ) );
	}
	ptr->location( ) = input[ 0 ].location( );
	*( data.targetData ) = std::move( ptr );
	return true;
}

bool EnterFloat4_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	SP_Float ptr{ NewShared< T_Float4 >(
			input[ 2 ].stringValue( ) , input[ 3 ].stringValue( ) ,
			input[ 4 ].stringValue( ) , input[ 5 ].stringValue( ) ,
			input[ 1 ].stringValue( ) ) };
	if ( ptr->inputNames( ).size( ) != 4 ) {
		data.errors.add( "duplicate input names" , input[ 3 ].location( ) );
	}
	ptr->location( ) = input[ 0 ].location( );
	*( data.targetData ) = std::move( ptr );
	return true;
}

bool AddFloat_( T_SRDParserData const& data )
{
	auto& fl( data.currentData->value< SP_Float >( ) );
	auto& parent( data.targetData->value< SP_Section >( ) );

	if ( fl->min( ) > fl->max( ) ) {
		data.errors.add( "invalid bounds" , (*data.input)[ 0 ] );
	}
	parent->overrides.add( fl.makeOwned( ) );
	return true;
}

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

bool FloatSetSlider_( T_SRDParserData const& data )
{
	data.currentData->value< SP_Float >( )->setSlider( );
	return true;
}

bool FloatSetMin_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	if ( !data.currentData->value< SP_Float >( )->setMin( input[ 1 ].floatValue( ) ) ) {
		data.errors.add( "duplicate minimal value" , (*data.input)[ 0 ] );
	}
	return true;
}

bool FloatSetMax_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	if ( !data.currentData->value< SP_Float >( )->setMax( input[ 1 ].floatValue( ) ) ) {
		data.errors.add( "duplicate maximal value" , (*data.input)[ 0 ] );
	}
	return true;
}

bool FloatSetStep_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	const auto v( input[ 1 ].floatValue( ) );
	if ( v <= 0 ) {
		data.errors.add( "invalid step value" , (*data.input)[ 1 ] );
	} else if ( !data.currentData->value< SP_Float >( )->setStep( v ) ) {
		data.errors.add( "duplicate step value" , (*data.input)[ 0 ] );
	}
	return true;
}

bool FloatSetPower_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	const auto v( input[ 1 ].floatValue( ) );
	if ( v == 0 ) {
		data.errors.add( "invalid power value" , (*data.input)[ 1 ] );
	} else if ( !data.currentData->value< SP_Float >( )->setPower( v ) ) {
		data.errors.add( "duplicate power value" , (*data.input)[ 0 ] );
	}
	return true;
}

bool FloatSetDecimals_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	const auto v( input[ 1 ].longValue( ) );
	if ( v < 0 || v > 20 ) {
		data.errors.add( "invalid decimals value" , (*data.input)[ 1 ] );
	} else if ( !data.currentData->value< SP_Float >( )->setDecimals( v ) ) {
		data.errors.add( "duplicate decimals value" , (*data.input)[ 0 ] );
	}
	return true;
}

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

using SP_Int = T_SharedPtr< A_Integer >;

bool EnterInt1_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	SP_Int ptr{ NewShared< T_Integer >( input[ 2 ].stringValue( ) ,
			input[ 1 ].stringValue( ) ) };
	ptr->location( ) = input[ 0 ].location( );
	*( data.targetData ) = std::move( ptr );
	return true;
}

bool EnterInt2_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	SP_Int ptr{ NewShared< T_Integer2 >(
			input[ 2 ].stringValue( ) , input[ 3 ].stringValue( ) ,
			input[ 1 ].stringValue( ) ) };
	if ( ptr->inputNames( ).size( ) != 2 ) {
		data.errors.add( "duplicate input names" , input[ 3 ].location( ) );
	}
	ptr->location( ) = input[ 0 ].location( );
	*( data.targetData ) = std::move( ptr );
	return true;
}

bool EnterInt3_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	SP_Int ptr{ NewShared< T_Integer3 >(
			input[ 2 ].stringValue( ) , input[ 3 ].stringValue( ) ,
			input[ 4 ].stringValue( ) ,
			input[ 1 ].stringValue( ) ) };
	if ( ptr->inputNames( ).size( ) != 3 ) {
		data.errors.add( "duplicate input names" , input[ 3 ].location( ) );
	}
	ptr->location( ) = input[ 0 ].location( );
	*( data.targetData ) = std::move( ptr );
	return true;
}

bool EnterInt4_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	SP_Int ptr{ NewShared< T_Integer4 >(
			input[ 2 ].stringValue( ) , input[ 3 ].stringValue( ) ,
			input[ 4 ].stringValue( ) , input[ 5 ].stringValue( ) ,
			input[ 1 ].stringValue( ) ) };
	if ( ptr->inputNames( ).size( ) != 4 ) {
		data.errors.add( "duplicate input names" , input[ 3 ].location( ) );
	}
	ptr->location( ) = input[ 0 ].location( );
	*( data.targetData ) = std::move( ptr );
	return true;
}

bool AddInt_( T_SRDParserData const& data )
{
	auto& fl( data.currentData->value< SP_Int >( ) );
	auto& parent( data.targetData->value< SP_Section >( ) );

	if ( fl->min( ) > fl->max( ) ) {
		data.errors.add( "invalid bounds" , (*data.input)[ 0 ] );
	}
	parent->overrides.add( fl.makeOwned( ) );
	return true;
}

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

bool IntSetSlider_( T_SRDParserData const& data )
{
	data.currentData->value< SP_Int >( )->setSlider( );
	return true;
}

bool IntSetMin_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	if ( !data.currentData->value< SP_Int >( )->setMin( input[ 1 ].longValue( ) ) ) {
		data.errors.add( "duplicate minimal value" , (*data.input)[ 0 ] );
	}
	return true;
}

bool IntSetMax_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	if ( !data.currentData->value< SP_Int >( )->setMax( input[ 1 ].longValue( ) ) ) {
		data.errors.add( "duplicate maximal value" , (*data.input)[ 0 ] );
	}
	return true;
}

bool IntSetStep_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	const auto v( input[ 1 ].floatValue( ) );
	if ( v <= 0 ) {
		data.errors.add( "invalid step value" , (*data.input)[ 1 ] );
	} else if ( !data.currentData->value< SP_Int >( )->setStep( v ) ) {
		data.errors.add( "duplicate step value" , (*data.input)[ 0 ] );
	}
	return true;
}

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

using SP_Cg = T_SharedPtr< T_ColorGrading >;

bool EnterColorGrading_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	SP_Cg ptr{ NewShared< T_ColorGrading >(
			input[ 2 ].stringValue( ) ,
			input[ 3 ].stringValue( ) ,
			input[ 4 ].stringValue( ) ,
			input[ 1 ].stringValue( ) ) };
	ptr->location( ) = input[ 0 ].location( );
	*( data.targetData ) = std::move( ptr );
	return true;
}

bool AddColorGrading_( T_SRDParserData const& data )
{
	auto& fl( data.currentData->value< SP_Cg >( ) );
	auto& parent( data.targetData->value< SP_Section >( ) );
	parent->overrides.add( fl.makeOwned( ) );
	return true;
}

bool CgSetBase_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	if ( !data.currentData->value< SP_Cg >( )->setBase( input[ 1 ].floatValue( ) ) ) {
		data.errors.add( "duplicate base value" , (*data.input)[ 0 ] );
	}
	return true;
}

bool CgSetUnit_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	const float v( input[ 1 ].floatValue( ) );
	if ( v == 0 ) {
		data.errors.add( "invalid unit value" , (*data.input)[ 1 ] );
	} else if ( !data.currentData->value< SP_Cg >( )->setUnit( v ) ) {
		data.errors.add( "duplicate unit value" , (*data.input)[ 0 ] );
	}
	return true;
}

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

using SP_Cam = T_SharedPtr< T_CamOverride >;

bool EnterCam_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	SP_Cam ptr{ NewShared< T_CamOverride >( input[ 1 ].stringValue( ) ) };
	ptr->location( ) = input[ 0 ].location( );
	*( data.targetData ) = std::move( ptr );
	return true;
}

bool ExitCam_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	auto& ov( data.currentData->value< SP_Cam >( ) );
	auto& parent( data.targetData->value< SP_Section >( ) );

	if ( !ov->isFovConfigured( ) ) {
		data.errors.add( "field of view or near plane missing" , input[ 0 ] );
	}
	if ( !ov->isTargetConfigured( ) ) {
		data.errors.add( "target vector missing" , input[ 0 ] );
	}
	if ( !ov->checkValidConfig( ) ) {
		data.errors.add( "invalid camera configuration" , input[ 0 ] );
	}

	parent->overrides.add( ov.makeOwned( ) );
	return true;
}

bool CamSetFov_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	auto& ov( data.currentData->value< SP_Cam >( ) );
	const auto s{ ov->setFieldOfView( input[ 1 ].stringValue( ) ) };
	if ( s == T_CamOverride::S_DEF ) {
		data.errors.add( "field of view or near plane already set" , input[ 0 ] );
	} else if ( s == T_CamOverride::S_INPUTS ) {
		data.errors.add( "input already in use" , input[ 1 ] );
	}
	return true;
}

bool CamSetNP_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	auto& ov( data.currentData->value< SP_Cam >( ) );
	const auto s{ ov->setNearPlane( input[ 1 ].stringValue( ) ) };
	if ( s == T_CamOverride::S_DEF ) {
		data.errors.add( "field of view or near plane already set" , input[ 0 ] );
	} else if ( s == T_CamOverride::S_INPUTS ) {
		data.errors.add( "input already in use" , input[ 1 ] );
	}
	return true;
}

bool CamSetTarget_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	auto& ov( data.currentData->value< SP_Cam >( ) );
	const auto s{ ov->setTarget( input[ 1 ].stringValue( ) ,
			input[ 2 ].stringValue( ) ,
			input[ 3 ].stringValue( ) ) };
	if ( s == T_CamOverride::S_DEF ) {
		data.errors.add( "camera target already set" , input[ 0 ] );
	} else if ( s == T_CamOverride::S_INPUTS ) {
		data.errors.add( "inputs already in use" , input[ 1 ] );
	}
	return true;
}

bool CamSetPosition_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	auto& ov( data.currentData->value< SP_Cam >( ) );
	const auto s{ ov->setPositionVector( input[ 1 ].stringValue( ) ,
			input[ 2 ].stringValue( ) , input[ 3 ].stringValue( ) ) };
	if ( s == T_CamOverride::S_DEF ) {
		data.errors.add( "camera position already set" , input[ 0 ] );
	} else if ( s == T_CamOverride::S_INPUTS ) {
		data.errors.add( "inputs already in use" , input[ 1 ] );
	}
	return true;
}

bool CamSetUpVector_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	auto& ov( data.currentData->value< SP_Cam >( ) );
	const auto s{ ov->setUpVector( input[ 1 ].stringValue( ) ,
			input[ 2 ].stringValue( ) , input[ 3 ].stringValue( ) ) };
	if ( s == T_CamOverride::S_DEF ) {
		data.errors.add( "'up' vector already set" , input[ 0 ] );
	} else if ( s == T_CamOverride::S_INPUTS ) {
		data.errors.add( "inputs already in use" , input[ 1 ] );
	}
	return true;
}

bool CamSetAngles_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	auto& ov( data.currentData->value< SP_Cam >( ) );
	const auto s{ ov->setAngles( input[ 1 ].stringValue( ) ,
			input[ 2 ].stringValue( ) , input[ 3 ].stringValue( ) ) };
	if ( s == T_CamOverride::S_DEF ) {
		data.errors.add( "camera angles already set" , input[ 0 ] );
	} else if ( s == T_CamOverride::S_INPUTS ) {
		data.errors.add( "inputs already in use" , input[ 1 ] );
	}
	return true;
}

bool CamSetDistance_( T_SRDParserData const& data )
{
	auto const& input( *( data.input ) );
	auto& ov( data.currentData->value< SP_Cam >( ) );
	const auto s{ ov->setDistance( input[ 1 ].stringValue( ) ) };
	if ( s == T_CamOverride::S_DEF ) {
		data.errors.add( "camera distance already set" , input[ 0 ] );
	} else if ( s == T_CamOverride::S_INPUTS ) {
		data.errors.add( "input already in use" , input[ 1 ] );
	}
	return true;
}


} // namespace
/*------------------------------------------------------------------------------*/

ebcl::T_SRDParserConfig sov::GetParserConfig( )
{
	using namespace ebcl;
	using namespace ebcl::SRD;
	T_SRDParserDefs defs( "root" );

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

	defs.context( "root" )
		<< ( Rule() << "section" << Text( ) << EnterContext( "section" )
				<< OnEnter( EnterSection_ )
				<< OnExit( ExitSection_ ) )
	;
	defs.context( "section" )
		<< ( Rule() << "section" << Text( ) << EnterContext( "section" )
				<< OnEnter( EnterSection_ )
				<< OnExit( ExitSection_ ) )
		// Floating point controls
		<< ( Rule() << "float" << Text( ) << Word( )
				<< EnterContext( "float" )
				<< OnEnter( EnterFloat1_ )
				<< OnExit( AddFloat_ ) )
		<< ( Rule() << "float2" << Text( )
				<< ( SRD::Times( 2 ) << Word( ) )
				<< EnterContext( "float" )
				<< OnEnter( EnterFloat2_ )
				<< OnExit( AddFloat_ ) )
		<< ( Rule() << "float3" << Text( )
				<< ( SRD::Times( 3 ) << Word( ) )
				<< EnterContext( "float" )
				<< OnEnter( EnterFloat3_ )
				<< OnExit( AddFloat_ ) )
		<< ( Rule() << "float4" << Text( )
				<< ( SRD::Times( 4 ) << Word( ) )
				<< EnterContext( "float" )
				<< OnEnter( EnterFloat4_ )
				<< OnExit( AddFloat_ ) )
		// Integer controls
		<< ( Rule() << "int" << Text( ) << Word( )
				<< EnterContext( "int" )
				<< OnEnter( EnterInt1_ )
				<< OnExit( AddInt_ ) )
		<< ( Rule() << "int2" << Text( )
				<< ( SRD::Times( 2 ) << Word( ) )
				<< EnterContext( "int" )
				<< OnEnter( EnterInt2_ )
				<< OnExit( AddInt_ ) )
		<< ( Rule() << "int3" << Text( )
				<< ( SRD::Times( 3 ) << Word( ) )
				<< EnterContext( "int" )
				<< OnEnter( EnterInt3_ )
				<< OnExit( AddInt_ ) )
		<< ( Rule() << "int4" << Text( )
				<< ( SRD::Times( 4 ) << Word( ) )
				<< EnterContext( "int" )
				<< OnEnter( EnterInt4_ )
				<< OnExit( AddInt_ ) )
		// Color grading controls
		<< ( Rule() << "color-grading" << Text( )
				<< ( SRD::Times( 3 ) << Word( ) )
				<< EnterContext( "color-grading" )
				<< OnEnter( EnterColorGrading_ )
				<< OnExit( AddColorGrading_ ) )
		// Camera controls
		<< ( Rule() << "camera" << Text( ) << EnterContext( "camera" )
				<< OnEnter( EnterCam_ )
				<< OnExit( ExitCam_ ) )
	;

	// Floating point control parameters
	defs.context( "float" )
		<< ( Rule() << "slider" << FloatSetSlider_ )
		<< ( Rule() << "min" << Numeric() << FloatSetMin_ )
		<< ( Rule() << "max" << Numeric() << FloatSetMax_ )
		<< ( Rule() << "step" << Numeric() << FloatSetStep_ )
		<< ( Rule() << "power" << Numeric() << FloatSetPower_ )
		<< ( Rule() << "decimals" << Integer() << FloatSetDecimals_ )
	;

	// Integer control parameters
	defs.context( "int" )
		<< ( Rule() << "slider" << IntSetSlider_ )
		<< ( Rule() << "min" << Int32() << IntSetMin_ )
		<< ( Rule() << "max" << Int32() << IntSetMax_ )
		<< ( Rule() << "step" << Numeric() << IntSetStep_ )
	;

	// Color grading controls
	defs.context( "color-grading" )
		<< ( Rule() << "base" << Numeric( ) << CgSetBase_ )
		<< ( Rule() << "unit" << Numeric( ) << CgSetUnit_ )
	;

	// Camera controls
	defs.context( "camera" )
		<< ( Rule() << "fov" << Word() << CamSetFov_ )
		<< ( Rule() << "near-plane" << Word() << CamSetNP_ )
		//
		<< ( Rule() << ( Alt() << "target" << "look-at" )
				<< ( SRD::Times( 3 ) << Word() )
				<< CamSetTarget_ )
		//
		<< ( Rule() << "position" << ( SRD::Times( 3 ) << Word() ) << CamSetPosition_ )
		<< ( Rule() << "up" << ( SRD::Times( 3 ) << Word() ) << CamSetUpVector_ )
		//
		<< ( Rule() << "angles" << ( SRD::Times( 3 ) << Word() ) << CamSetAngles_ )
		<< ( Rule() << "distance" << Word() << CamSetDistance_ )
	;

	return T_SRDParserConfig{ defs };
}


/*= A_Float ====================================================================*/

bool A_Float::setMin(
		const float v ) noexcept
{
	M_SETOPT_( min_ , v );
}

bool A_Float::setMax(
		const float v ) noexcept
{
	M_SETOPT_( max_ , v );
}

bool A_Float::setStep(
		const float v ) noexcept
{
	assert( v > 0 );
	M_SETOPT_( step_ , v );
}

bool A_Float::setDecimals(
		const uint32_t n ) noexcept
{
	assert( n >= 0 && n <= 20 );
	if ( decimals_ ) {
		return false;
	}

	T_StringBuilder sb;
	sb << "%." << n << 'f' << '\0';
	assert( sb.size( ) < 12 );
	decimals_.setNew( );
	for ( auto i = 0u ; i < sb.size( ) ; i ++ ) {
		decimals_->add( sb.data( )[ i ] );
	}

	return true;
}

bool A_Float::setPower(
		const float v ) noexcept
{
	assert( v != 0 );
	M_SETOPT_( power_ , v );
}

void A_Float::setSlider( ) noexcept
{
	slider_ = true;
}


/*= T_Float ====================================================================*/

T_Float::T_Float(
		T_String const& input ,
		T_String const& title ) noexcept
	: A_Float( "float" , title )
{
	inputs_.add( input );
}

P_SyncOverride T_Float::clone( ) const noexcept
{
	auto c{ NewOwned< T_Float >( inputs_[ 0 ] , &title_[ 0 ] ) };
	copyTo( *c );
	return c;
}


/*= T_Float2 ===================================================================*/

T_Float2::T_Float2(
		T_String const& input0 ,
		T_String const& input1 ,
		T_String const& title ) noexcept
	: A_Float( "float2" , title )
{
	inputs_.add( input0 );
	inputs_.add( input1 );
}

P_SyncOverride T_Float2::clone( ) const noexcept
{
	auto c{ NewOwned< T_Float2 >( inputs_[ 0 ] , inputs_[ 1 ] , &title_[ 0 ] ) };
	copyTo( *c );
	return c;
}


/*= T_Float3 ===================================================================*/

T_Float3::T_Float3(
		T_String const& input0 ,
		T_String const& input1 ,
		T_String const& input2 ,
		T_String const& title ) noexcept
	: A_Float( "float3" , title )
{
	inputs_.add( input0 );
	inputs_.add( input1 );
	inputs_.add( input2 );
}

P_SyncOverride T_Float3::clone( ) const noexcept
{
	auto c{ NewOwned< T_Float3 >( inputs_[ 0 ] , inputs_[ 1 ] ,
			inputs_[ 2 ] , &title_[ 0 ] ) };
	copyTo( *c );
	return c;
}


/*= T_Float4 ===================================================================*/

T_Float4::T_Float4(
		T_String const& input0 ,
		T_String const& input1 ,
		T_String const& input2 ,
		T_String const& input3 ,
		T_String const& title ) noexcept
	: A_Float( "float4" , title )
{
	inputs_.add( input0 );
	inputs_.add( input1 );
	inputs_.add( input2 );
	inputs_.add( input3 );
}

P_SyncOverride T_Float4::clone( ) const noexcept
{
	auto c{ NewOwned< T_Float4 >( inputs_[ 0 ] , inputs_[ 1 ] ,
			inputs_[ 2 ] , inputs_[ 3 ] , &title_[ 0 ] ) };
	copyTo( *c );
	return c;
}


/*= A_Integer ==================================================================*/

bool A_Integer::setMin(
		const int32_t v ) noexcept
{
	M_SETOPT_( min_ , v );
}

bool A_Integer::setMax(
		const int32_t v ) noexcept
{
	M_SETOPT_( max_ , v );
}

bool A_Integer::setStep(
		const float v ) noexcept
{
	assert( v > 0 );
	M_SETOPT_( step_ , v );
}

void A_Integer::setSlider( ) noexcept
{
	slider_ = true;
}


/*= T_Integer ==================================================================*/

T_Integer::T_Integer(
		T_String const& input ,
		T_String const& title ) noexcept
	: A_Integer( "int" , title )
{
	inputs_.add( input );
}

P_SyncOverride T_Integer::clone( ) const noexcept
{
	auto c{ NewOwned< T_Integer >( inputs_[ 0 ] , &title_[ 0 ] ) };
	copyTo( *c );
	return c;
}


/*= T_Integer2 =================================================================*/

T_Integer2::T_Integer2(
		T_String const& input0 ,
		T_String const& input1 ,
		T_String const& title ) noexcept
	: A_Integer( "int2" , title )
{
	inputs_.add( input0 );
	inputs_.add( input1 );
}

P_SyncOverride T_Integer2::clone( ) const noexcept
{
	auto c{ NewOwned< T_Integer2 >( inputs_[ 0 ] , inputs_[ 1 ] , &title_[ 0 ] ) };
	copyTo( *c );
	return c;
}


/*= T_Integer3 =================================================================*/

T_Integer3::T_Integer3(
		T_String const& input0 ,
		T_String const& input1 ,
		T_String const& input2 ,
		T_String const& title ) noexcept
	: A_Integer( "int3" , title )
{
	inputs_.add( input0 );
	inputs_.add( input1 );
	inputs_.add( input2 );
}

P_SyncOverride T_Integer3::clone( ) const noexcept
{
	auto c{ NewOwned< T_Integer3 >( inputs_[ 0 ] , inputs_[ 1 ] ,
			inputs_[ 2 ] , &title_[ 0 ] ) };
	copyTo( *c );
	return c;
}


/*= T_Integer4 =================================================================*/

T_Integer4::T_Integer4(
		T_String const& input0 ,
		T_String const& input1 ,
		T_String const& input2 ,
		T_String const& input3 ,
		T_String const& title ) noexcept
	: A_Integer( "int4" , title )
{
	inputs_.add( input0 );
	inputs_.add( input1 );
	inputs_.add( input2 );
	inputs_.add( input3 );
}

P_SyncOverride T_Integer4::clone( ) const noexcept
{
	auto c{ NewOwned< T_Integer4 >( inputs_[ 0 ] , inputs_[ 1 ] ,
			inputs_[ 2 ] , inputs_[ 3 ] , &title_[ 0 ] ) };
	copyTo( *c );
	return c;
}


/*= T_ColorGrading =============================================================*/

T_ColorGrading::T_ColorGrading(
		T_String const& iRed ,
		T_String const& iGreen ,
		T_String const& iBlue ,
		T_String const& title ) noexcept
	: A_SyncOverride( "cg" , title )
{
	inputs_.add( iRed );
	inputs_.add( iGreen );
	inputs_.add( iBlue );
}

bool T_ColorGrading::setBase(
		const float v ) noexcept
{
	M_SETOPT_( base_ , v );
}

bool T_ColorGrading::setUnit(
		const float v ) noexcept
{
	assert( v != 0 );
	M_SETOPT_( unit_ , v );
}

P_SyncOverride T_ColorGrading::clone( ) const noexcept
{
	auto c{ NewOwned< T_ColorGrading >( inputs_[ 0 ] ,
			inputs_[ 1 ] , inputs_[ 2 ] , &title_[ 0 ] ) };
	c->location( ) = location( );
	c->base_ = base_;
	c->unit_ = unit_;
	return c;
}


/*= T_CamOverride ==============================================================*/

T_CamOverride::T_CamOverride(
		T_String const& title ) noexcept
	: A_SyncOverride( "cam" , title ) ,
		camMode_( CM_INVALID )
{}

P_SyncOverride T_CamOverride::clone( ) const noexcept
{
	auto c{ NewOwned< T_CamOverride >( &title_[ 0 ] ) };
	c->location( ) = location( );
	const auto n{ inputs_.size( ) };
	for ( auto i = 0u ; i < n ; i ++ ) {
		c->inputs_.add( inputs_[ i ] );
	}
	c->fovConfig_ = fovConfig_;
	c->target_ = target_;
	c->upVector_ = upVector_;
	c->position_ = position_;
	c->angles_ = angles_;
	c->distance_ = distance_;
	c->camMode_ = camMode_;
	return c;
}

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

T_CamOverride::E_SetState T_CamOverride::setFieldOfView(
		T_String const& input ) noexcept
{
	if ( fovConfig_ ) {
		return S_DEF;
	}
	if ( inputs_.contains( input ) ) {
		return S_INPUTS;
	}
	fovConfig_.setNew( FM_FOV , inputs_.size( ) );
	inputs_.add( input );
	return S_OK;
}

T_CamOverride::E_SetState T_CamOverride::setNearPlane(
		T_String const& input ) noexcept
{
	if ( fovConfig_ ) {
		return S_DEF;
	}
	if ( inputs_.contains( input ) ) {
		return S_INPUTS;
	}
	fovConfig_.setNew( FM_NEARPLANE , inputs_.size( ) );
	inputs_.add( input );
	return S_OK;
}

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

T_CamOverride::E_SetState T_CamOverride::setTarget(
		T_String const& inX ,
		T_String const& inY ,
		T_String const& inZ ) noexcept
{
	return setVector( target_ , inX , inY , inZ );
}

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

T_CamOverride::E_SetState T_CamOverride::setUpVector(
		T_String const& inX ,
		T_String const& inY ,
		T_String const& inZ ) noexcept
{
	return setVector( upVector_ , inX , inY , inZ );
}

T_CamOverride::E_SetState T_CamOverride::setPositionVector(
		T_String const& inX ,
		T_String const& inY ,
		T_String const& inZ ) noexcept
{
	return setVector( position_ , inX , inY , inZ );
}

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

T_CamOverride::E_SetState T_CamOverride::setAngles(
		T_String const& in1 ,
		T_String const& in2 ,
		T_String const& in3 ) noexcept
{
	return setVector( angles_ , in1 , in2 , in3 );
}

T_CamOverride::E_SetState T_CamOverride::setDistance(
		T_String const& input ) noexcept
{
	if ( distance_ ) {
		return S_DEF;
	}
	if ( inputs_.contains( input ) ) {
		return S_INPUTS;
	}
	distance_ = inputs_.size( );
	inputs_.add( input );
	return S_OK;
}

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

bool T_CamOverride::checkValidConfig( ) noexcept
{
	if ( camMode_ != CM_INVALID ) {
		return true;
	}

	if ( angles_ && distance_ && !( position_ || upVector_ ) ) {
		camMode_ = CM_ANGLES;
	} else if ( position_ && upVector_ && !( angles_ || distance_ ) ) {
		camMode_ = CM_VECTORS;
	}
	return ( camMode_ != CM_INVALID );
}

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

T_CamOverride::E_SetState T_CamOverride::setVector(
		T_Optional< T_VectorConfig >& vector ,
		T_String const& inX ,
		T_String const& inY ,
		T_String const& inZ ) noexcept
{
	if ( vector ) {
		return S_DEF;
	}
	if ( inputs_.contains( inX ) || inputs_.contains( inY ) || inputs_.contains( inZ ) ) {
		return S_INPUTS;
	}

	const uint32_t s( inputs_.size( ) );
	vector.setNew( s , s+1 , s+2 );
	inputs_.add( inX );
	inputs_.add( inY );
	inputs_.add( inZ );
	return S_OK;
}