#pragma once
#include "c-sync.hh"
#include "c-undo.hh"


/*= GENERAL STRUCTURE FOR SYNC EDITOR UNDOS ==================================*/

class T_UndoSyncChanges : public A_UndoAction
{
    protected:
	struct T_CurveChange_
	{
		T_String inputId;
		T_Optional< T_SyncCurve > before;
		T_Optional< T_SyncCurve > after;

		explicit T_CurveChange_( T_SyncCurve before ) noexcept
			: inputId{ before.name } ,
				before{ std::move( before ) } ,
				after{ }
		{}

		T_CurveChange_( const bool ,
				T_SyncCurve after ) noexcept
			: inputId{ after.name } , before{ } ,
				after{ std::move( after ) }
		{}

		T_CurveChange_( T_SyncCurve before ,
				T_SyncCurve after ) noexcept
			: inputId{ before.name } ,
				before{ std::move( before ) } ,
				after{ std::move( after ) }
		{}
	};
	T_AutoArray< T_CurveChange_ , 4 , 32 > changes_;

    public:
	T_UndoSyncChanges( ) noexcept = default;
	DEF_MOVE( T_UndoSyncChanges );
	NO_COPY( T_UndoSyncChanges );

	void undo( ) const noexcept override;
	void redo( ) const noexcept override;

	T_UndoSyncChanges& curveCreation(
			T_SyncCurve curve ) noexcept;
	T_UndoSyncChanges& curveDeletion(
			T_SyncCurve curve ) noexcept;
	T_UndoSyncChanges& curveReplacement(
			T_SyncCurve before ,
			T_SyncCurve after ) noexcept;
};


/*= DURATION CHANGES =========================================================*/

class T_UndoDurationChanges final : public T_UndoSyncChanges
{
    private:
	uint32_t unitsBefore_ , unitsAfter_;
	float uSizeBefore_ , uSizeAfter_;

    public:
	T_UndoDurationChanges(
			uint32_t units ,
			uint32_t oldUnits ,
			float unitSize ,
			float oldUnitSize ) noexcept;

	T_UndoDurationChanges( ) noexcept = delete;
	DEF_MOVE( T_UndoDurationChanges );
	NO_COPY( T_UndoDurationChanges );

	void undo( ) const noexcept override;
	void redo( ) const noexcept override;
};


/*= TRACK IDENTIFIER =========================================================*/

struct T_SyncTrackId
{
	T_String id;
	bool isOverride;

	bool operator ==( T_SyncTrackId const& other ) const noexcept
	{
		return id == other.id && isOverride == other.isOverride;
	}

	bool operator !=( T_SyncTrackId const& other ) const noexcept
	{
		return id != other.id || isOverride != other.isOverride;
	}
};

inline uint32_t ComputeHash( T_SyncTrackId const& id ) noexcept
{
	return ComputeHash( id.id ) ^ ( id.isOverride ? 0xffffffff : 0 );
}


/*= EDITION FUNCTIONS ========================================================*/

struct SyncEditor final
{
	// Change the duration of the demo using the specified unit count and
	// size. If scaleCurves is true and the unit size has changed, the
	// curves' durations will be scaled so that the timings match as
	// closely as possible.
	static void SetDuration(
			uint32_t units ,
			float uSize ,
			bool scaleCurves ) noexcept;

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

	// Replaces a curve with a new record
	static void ReplaceCurve(
			T_SyncCurve replacement ) noexcept;

	// Delete a curve's (or an override's) record completely.
	static void DeleteCurve(
			T_SyncTrackId const& id ) noexcept;

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

	// Append a segment with the specified amount of units at the end of
	// the curve. If the curve does not exist it will be created.
	static void AppendSegment(
			T_SyncTrackId const& id ,
			uint32_t nsDuration ) noexcept;

	// Delete a segment from a curve.
	static void DeleteSegment(
			T_SyncTrackId const& id ,
			uint32_t segmentIndex ) noexcept;

	// Change the type of a segment in a curve.
	static void SetSegmentType(
			T_SyncCurve const& initial ,
			uint32_t segmentIndex ,
			T_SyncSegment::E_SegmentType newType ) noexcept;

	// Change the type of a segment for all inputs covered by an override.
	static void SetSegmentTypes(
			A_SyncOverride const& ovr ,
			uint32_t segmentIndex ,
			T_SyncSegment::E_SegmentType newType ) noexcept;

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

	// Insert a point in a segment. The pointIndex parameter indicates the
	// index of the new point; it must not be 0 or nbPoints( segment )
	static void InsertPoint(
			T_SyncTrackId const& id ,
			uint32_t segmentIndex ,
			uint32_t pointIndex ) noexcept;

	// Delete a point from a segment. The point must not be the first or last
	// point in the segment.
	static void DeletePoint(
			T_SyncTrackId const& id ,
			uint32_t segmentIndex ,
			uint32_t pointIndex ) noexcept;

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

	// Make an override consistent by aligning all segment boundaries and
	// durations on the first longest curve.
	static void MakeOverrideConsistent(
			T_String const& id ) noexcept;
};