#include "externals.hh"

#include "common.hh"
#include "c-syncedit.hh"


/*= T_UndoSyncChanges ========================================================*/

void T_UndoSyncChanges::undo( ) const noexcept
{
	auto& sync{ Common::Sync( ) };
	const auto n{ changes_.size( ) };
	for ( auto i = 0u ; i < n ; i ++ ) {
		auto const& c( changes_[ i ] );
		if ( c.before ) {
			sync.setCurve( *c.before );
		} else {
			sync.removeCurve( c.inputId );
		}
	}
}

void T_UndoSyncChanges::redo( ) const noexcept
{
	auto& sync{ Common::Sync( ) };
	const auto n{ changes_.size( ) };
	for ( auto i = 0u ; i < n ; i ++ ) {
		auto const& c( changes_[ i ] );
		if ( c.after ) {
			sync.setCurve( *c.after );
		} else {
			sync.removeCurve( c.inputId );
		}
	}
}

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

T_UndoSyncChanges& T_UndoSyncChanges::curveCreation(
		T_SyncCurve curve ) noexcept
{
#ifndef NDEBUG
	{
		const auto n{ changes_.size( ) };
		for ( auto i = 0u ; i < n ; i ++ ) {
			assert( changes_[ i ].inputId != curve.name );
		}
	}
#endif
	changes_.addNew( true , std::move( curve ) );
	return *this;
}

T_UndoSyncChanges& T_UndoSyncChanges::curveDeletion(
		T_SyncCurve curve ) noexcept
{
#ifndef NDEBUG
	{
		const auto n{ changes_.size( ) };
		for ( auto i = 0u ; i < n ; i ++ ) {
			assert( changes_[ i ].inputId != curve.name );
		}
	}
#endif
	changes_.addNew( std::move( curve ) );
	return *this;
}

T_UndoSyncChanges& T_UndoSyncChanges::curveReplacement(
		T_SyncCurve before ,
		T_SyncCurve after ) noexcept
{
	assert( before.name == after.name );
#ifndef NDEBUG
	{
		const auto n{ changes_.size( ) };
		for ( auto i = 0u ; i < n ; i ++ ) {
			assert( changes_[ i ].inputId != before.name );
		}
	}
#endif
	changes_.addNew( std::move( before ) , std::move( after ) );
	return *this;
}


/*= T_UndoDurationChanges ====================================================*/

T_UndoDurationChanges::T_UndoDurationChanges(
		const uint32_t units ,
		const uint32_t oldUnits ,
		const float unitSize ,
		const float oldUnitSize ) noexcept
	: T_UndoSyncChanges( ) ,
		unitsBefore_{ oldUnits } ,
		unitsAfter_{ units } ,
		uSizeBefore_{ oldUnitSize } ,
		uSizeAfter_{ unitSize }
{ }

void T_UndoDurationChanges::undo( ) const noexcept
{
	Common::Sync( ).setDuration( uSizeBefore_ , unitsBefore_ );
	T_UndoSyncChanges::undo( );
}

void T_UndoDurationChanges::redo( ) const noexcept
{
	Common::Sync( ).setDuration( uSizeAfter_ , unitsAfter_ );
	T_UndoSyncChanges::redo( );
}


/*= SyncEditor ===============================================================*/

namespace {

T_SyncCurve SESDScaleCurve_(
		const float uSizeBefore ,
		const float uSizeAfter ,
		T_SyncCurve const& curve ) noexcept
{
	T_SyncCurve nCurve;
	nCurve.name = curve.name;
	for ( auto const& segment : curve.segments ) {
		const auto nsid{ nCurve.segments.add( segment ) };
		T_SyncSegment& nSeg{ nCurve.segments[ nsid ] };
		const auto nd{ nSeg.durations.size( ) };
		for ( auto i = 0u ; i < nd ; i ++ ) {
			const float duration{ nSeg.durations[ i ] * uSizeBefore };
			nSeg.durations[ i ] = std::max( 1u , uint32_t( std::roundf(
						duration / uSizeAfter ) ) );
			printf( "initial duration %f (%d units) ; new duration %f (%d units)\n" ,
					duration , segment.durations[ i ] ,
					uSizeAfter * nSeg.durations[ i ] ,
					nSeg.durations[ i ] );
		}
	}
	return nCurve;
}

} // namespace <anon>

void SyncEditor::SetDuration(
		const uint32_t units ,
		const float uSize ,
		const bool scaleCurves ) noexcept
{
	auto& sync{ Common::Sync( ) };
	const float oldUnitSize{ sync.durationUnitSize( ) };
	const uint32_t oldUnits{ sync.durationUnits( ) };
	if ( oldUnits == units && oldUnitSize == uSize ) {
		return;
	}

	auto& undo{ dynamic_cast< T_UndoDurationChanges& >(
			Common::Undo( ).add< T_UndoDurationChanges >(
				units , oldUnits ,
				uSize , oldUnitSize ) ) };
	sync.setDuration( uSize , units );
	if ( !scaleCurves || uSize == oldUnitSize ) {
		return;
	}

	auto const& curves( sync.curves( ) );
	for ( auto const& curve : curves ) {
		auto nc{ SESDScaleCurve_( oldUnitSize , uSize , curve ) };
		undo.curveReplacement( curve , nc );
		sync.setCurve( std::move( nc ) );
	}
}

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

void SyncEditor::ReplaceCurve(
		T_SyncCurve replacement ) noexcept
{
	auto* const curve{ Common::Sync( ).getCurve( replacement.name ) };
	if ( !curve ) {
		return;
	}

	// Create undo entry
	auto& undo{ dynamic_cast< T_UndoSyncChanges& >(
			Common::Undo( ).add< T_UndoSyncChanges >( ) ) };
	undo.curveReplacement( std::move( *curve ) , replacement );
	Common::Sync( ).setCurve( std::move( replacement ) );
}

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

void SyncEditor::DeleteCurve(
		T_String const& id ) noexcept
{
	auto const* const curve{ Common::Sync( ).getCurve( id ) };
	if ( !curve ) {
		return;
	}

	// Create undo entry
	auto& undo{ dynamic_cast< T_UndoSyncChanges& >(
			Common::Undo( ).add< T_UndoSyncChanges >( ) ) };
	undo.curveDeletion( *curve );

	// Delete curve
	Common::Sync( ).removeCurve( id );
}

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

void SyncEditor::AppendSegment(
		T_String const& id ,
		uint32_t nsDuration ) noexcept
{
	assert( nsDuration > 0 );

	auto& sync{ Common::Sync( ) };
	auto const* const curve{ sync.getCurve( id ) };
	auto& undo{ dynamic_cast< T_UndoSyncChanges& >(
			Common::Undo( ).add< T_UndoSyncChanges >( ) ) };

	if ( curve && !curve->segments.empty( ) ) {
		const float lastValue{ [&](){
			auto const& lSeg{ curve->segments.last( ) };
			return lSeg.values.last( );
		}( ) };
		auto nCurve{ *curve };
		auto& nSeg{ nCurve.segments.addNew( ) };
		nSeg.type = T_SyncSegment::LINEAR;
		nSeg.values.add( lastValue );
		nSeg.values.add( lastValue );
		nSeg.durations.add( nsDuration );

		undo.curveReplacement( *curve , nCurve );
		sync.setCurve( std::move( nCurve ) );
	} else {
		T_SyncCurve nCurve;
		nCurve.name = id;

		const auto value{ sync.inputs( )[ sync.inputPos( id ) ] };
		auto& nSeg{ nCurve.segments.addNew( ) };
		nSeg.type = T_SyncSegment::LINEAR;
		nSeg.values.add( value );
		nSeg.values.add( value );
		nSeg.durations.add( nsDuration );

		undo.curveCreation( nCurve );
		sync.setCurve( std::move( nCurve ) );
	}
}

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

void SyncEditor::DeleteSegment(
		T_String const& id ,
		const uint32_t segmentIndex ) noexcept
{
	auto& sync{ Common::Sync( ) };

	auto const* const curve{ sync.getCurve( id ) };
	if ( !curve || segmentIndex >= curve->segments.size( ) ) {
		return;
	}

	auto& undo{ dynamic_cast< T_UndoSyncChanges& >(
			Common::Undo( ).add< T_UndoSyncChanges >( ) ) };
	if ( curve->segments.size( ) == 1 ) {
		undo.curveDeletion( *curve );
		sync.removeCurve( id );
	} else {
		auto nCurve{ *curve };
		nCurve.segments.remove( segmentIndex );
		undo.curveReplacement( *curve , nCurve );
		sync.setCurve( std::move( nCurve ) );
	}
}

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

void SyncEditor::SetSegmentType(
		T_SyncCurve const& initial ,
		const uint32_t segmentIndex ,
		const T_SyncSegment::E_SegmentType newType ) noexcept
{
	assert( segmentIndex < initial.segments.size( ) );
	T_SyncSegment const& iSegment{ initial.segments[ segmentIndex ] };
	if ( iSegment.type == newType ) {
		return;
	}

	// Create new curve
	T_SyncCurve copy{ initial };
	copy.segments[ segmentIndex ].type = newType;

	// Create undo entry
	auto& undo{ dynamic_cast< T_UndoSyncChanges& >(
			Common::Undo( ).add< T_UndoSyncChanges >( ) ) };
	undo.curveReplacement( initial , copy );

	// Replace curve
	Common::Sync( ).setCurve( std::move( copy ) );
}