#pragma once
#include "c-filewatcher.hh"
#include "c-project.hh"

#include <ebcl/SRDParserConfig.hh>
#include <ebcl/Sets.hh>
#include <ebcl/Algorithms.hh>


// Duration and current playing time
struct T_SyncTime
{
	float uDuration = 1.f / 60.f;	// Duration - unit size
	uint32_t iDuration = 3600;	// Duration - total units
	float time = 0;

	void setDuration(
			const float uDuration ,
			const uint32_t iDuration );

	float duration( ) const noexcept
		{ return uDuration * iDuration; }

	void setTime( const float t ) noexcept
		{ time = std::max( 0.f , std::min( t , duration( ) ) ); }
};

/*============================================================================*/

// Segment of an input's curve
struct T_SyncSegment
{
	enum E_SegmentType
	{
		LINEAR ,
		RAMP ,
		SMOOTH ,
		//HERMITE
	};

	E_SegmentType type;
	T_Array< float > values{ T_Array< float >( 16 ) };
	T_Array< uint32_t > durations{ T_Array< uint32_t >( 16 ) };

	// Find the amount of time units before point
	uint32_t findTimeOfPoint( uint32_t index ) const noexcept;

	// Compute the value for the specified position, which is relative
	// to the start of the segment.
	float computeValue( float relTimeUnits ) const noexcept;

	// Is the segment constant? If it is, return the value.
	T_Optional< float > isConstant( ) const noexcept;
};
M_LSHIFT_OP( T_StringBuilder , T_SyncSegment::E_SegmentType );

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

// Possible results when trying to check whether two curves have a similar
// structure.
enum class E_SyncCurveMatch
{
	// Both curves have the same structure.
	IDENTICAL ,

	// One of the curves has less information than the other, but the
	// information in the shortest curve matches the corresponding
	// information in the longest curve (i.e. it is only missing
	// segments). _SHORT/_LONG indicate that the curve for which the
	// comparison method was called is the shortest/longest (resp.)
	MATCHING_SHORT ,
	MATCHING_LONG ,

	// One of the curves has less information than the other, and its
	// last segment is shorter than the corresponding segment in the
	// longest record (but its end matches a point in the longest curve's
	// segment).
	LASTSEG_SHORT ,
	LASTSEG_LONG ,

	// The curves do not match.
	MISMATCH
};

// An input curve
struct T_SyncCurve
{
	T_String name;
	T_Array< T_SyncSegment > segments{ 16 };

	T_SyncCurve( ) noexcept { }

	T_SyncCurve( T_String const& name ) noexcept
		: name( name )
	{ }

	T_SyncCurve( char const* name ) noexcept
		: name( T_String( name ).usePool( ) )
	{ }

	// Compute the value at the specified time.
	float computeValue( float timeUnits ) const noexcept;

	// Compare this curve to another.
	E_SyncCurveMatch matches(
			T_SyncCurve const& other ) const noexcept;

	// Count the points in the curve. A "point" corresponds to a value
	// inside a segment, or the first and last value in the curve, or
	// a junction between segments.
	uint32_t points( ) const noexcept;

	// Does the curve represent a constant? It does if all values in
	// all segments are identical; in that case the value is returned.
	T_Optional< float > isConstant( ) const noexcept;
};

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

// All configured curves. Some may not actually correspond to an input and may
// have been defined for inputs that have been removed temporarily (e.g.
// because some include was commented out), in which case we don't want to
// waste them.
struct T_SyncCurves
{

	T_ObjectTable< T_String , T_SyncCurve > curves;

	T_SyncCurves( )
		: curves( []( T_SyncCurve const& c ) -> T_String { return c.name; } )
	{ }

	void clear( ) noexcept
		{ curves.clear( ); }
	void setCurve( T_SyncCurve curve ) noexcept
		{ curves.set( std::move( curve ) ); }
	bool removeCurve( T_String const& curve ) noexcept
		{ return curves.remove( curve ); }

	// Returns -1 on lookup failure
	int32_t indexOf( T_String const& name ) noexcept;
	int32_t indexOf( char const* name ) noexcept
	{
		return indexOf( T_String( name ) );
	}
};

// Curves data file loader/writer
struct T_SyncCurvesIO : public ebcl::A_PrivateImplementation
{
	T_SyncCurvesIO( ) noexcept;

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

	T_Data load( T_FSPath const& path ) const;
	void save( T_FSPath const& path ,
			T_SyncCurves const& curves ,
			T_SyncTime const& time ) const;
};

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

// Pre-computed data for a curve
struct T_SyncCurveCache
{
	using T_SegRef = std::pair< uint32_t , uint32_t >;

	uint32_t curve;
	T_Array< T_SegRef > segRefs;
	T_Array< float > segStarts;
	T_Array< float > segEnds;
	uint32_t curPos;

	T_SyncCurveCache(
		T_SyncTime const& time ,
		T_SyncCurves const& curves ,
		const uint32_t curve ) noexcept;

	// Find the index of the segment for the specified time
	uint32_t findSegment(
		const float time ) const noexcept;

	// Compute the value of the curve at the specified location, ignoring
	// curPos.
	float valueAt(
		T_SyncTime const& time ,
		T_SyncCurves const& curves ,
		const float position ) const noexcept;

	// Compute the value of the curve at the current time, using and
	// updating curPos as necessary.
	float value(
		T_SyncTime const& time ,
		T_SyncCurves const& curves ) noexcept;

	float segmentValue(
			float time ,
			uint32_t segIndex ,
			T_Array< T_SyncSegment > const& segments ) const noexcept;
};
using P_SyncCurveCache = T_OwnPtr< T_SyncCurveCache >;

/*============================================================================*/

// Synchronization values. The values vector always contains an extra entry
// used for missing inputs.
struct T_SyncValues
{
	T_HashIndex index;
	T_Array< T_String > identifiers;
	T_Array< float > values;
	T_Array< bool > overriden;

	T_SyncValues( );
	void clear( );

	// Returns true on success, false on duplicate
	bool addValue(
		T_String const& name ,
		const float initial = 0.f ) noexcept;

	bool addValue(
		char const* name ,
		const float initial = 0.f ) noexcept
	{
		return addValue( T_String( name ) , initial );
	}

	// If the name isn't found, the last entry of values[] is returned
	uint32_t indexOf(
		T_String const& name ) const noexcept;
};

/*============================================================================*/

class A_SyncOverride;
using P_SyncOverride = T_OwnPtr< A_SyncOverride >;

struct T_SyncOverrideSection;
using P_SyncOverrideSection = T_OwnPtr< T_SyncOverrideSection >;

// Base class for overrides
class A_SyncOverride
{
    private:
	const T_String type_;
	bool enabled_{ false };
	ebcl::T_SRDLocation location_;
	T_String id_;

    protected:
	ebcl::T_Buffer< char > title_;
	ebcl::T_Set< T_String > inputs_{
			ebcl::UseTag< ebcl::ArrayBacked< 8 > >( ) };
	T_AutoArray< uint32_t , 8 > inputPos_;
	T_String fullTitle_;

	A_SyncOverride( char const* type ,
			T_String const& title ) noexcept;

    public:
	A_SyncOverride( ) = delete;
	virtual ~A_SyncOverride( ) = 0;

	T_String const& type( ) const noexcept
		{ return type_; }

	auto const& inputNames( ) const noexcept
		{ return inputs_; }
	auto const& inputPositions( ) const noexcept
		{ return inputPos_; }

	bool& enabled( ) noexcept
		{ return enabled_; }
	bool enabled( ) const noexcept
		{ return enabled_; }

	T_String const& id( ) const noexcept
		{ return id_; }
	char const* title( ) const noexcept
		{ return &title_[ 0 ]; }

	ebcl::T_SRDLocation& location( ) noexcept
		{ return location_; }
	ebcl::T_SRDLocation const& location( ) const noexcept
		{ return location_; }

	T_String const& fullTitle( ) const noexcept
		{ return fullTitle_; }
	void fullTitle( T_String ft ) noexcept
		{ assert( !fullTitle_ ); fullTitle_ = std::move( ft ); }

	// Connect the required inputs to the sync manager. Called once
	// the inputs have been added.
	virtual void setup( ) noexcept;

	// Create a clone of the current override.
	virtual T_OwnPtr< A_SyncOverride > clone( ) const noexcept = 0;
};

// Overrides section
struct T_SyncOverrideSection
{
	const T_String title;
	const ebcl::T_Buffer< char > cTitle;
	bool open{ false };
	T_Array< P_SyncOverrideSection > subsections;
	T_Array< P_SyncOverride > overrides;

	T_SyncOverrideSection( ) = delete;
	NO_COPY( T_SyncOverrideSection );
	NO_MOVE( T_SyncOverrideSection );

	explicit T_SyncOverrideSection( T_String title ) noexcept;

	void merge( T_SyncOverrideSection& other ) noexcept;

	// Generate the UI. A counter and temporary string builder are passed
	// to help generating "fake" labels for use with ImGui.
	void makeUI( uint32_t& counter ,
			T_StringBuilder& sb ,
			bool topLevel = false ) noexcept;

	T_SyncOverrideSection& section(
			T_String const& name ) noexcept;
	T_SyncOverrideSection const* section(
			T_String const& name ) const noexcept;
};

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

struct T_SyncOverrideVisitor
{
    public:
	using T_Element = ebcl::T_Union<
		T_SyncOverrideSection* ,
		A_SyncOverride* >;
	using T_OpElement = T_Optional< T_Element >;
	using T_Visitor = ebcl::T_Visitor< T_Element , T_Element >;
	using F_NodeAction = T_Visitor::F_NodeAction;

	static T_OpElement nodeBrowser( T_Element element , uint32_t child );

	T_Visitor visitor{ nodeBrowser };
};

/*============================================================================*/

// Synchronisation manager; handles all the synchronization data and makes it
// work together.
struct T_SyncManager : public virtual A_ProjectPathListener
{
	T_SyncManager( ) noexcept;
	~T_SyncManager( );

	void enable( ) noexcept;
	void disable( ) noexcept
		{ enabled_ = false; }
	bool enabled( ) const noexcept
		{ return enabled_; }

	// ---------------------------------------------------------------------
	// Duration & time controls

	void setDuration(
			const float uDuration ,
			const uint32_t iDuration );
	float duration( ) const noexcept
		{ return time_.duration( ); }

	uint32_t durationUnits( ) const noexcept
		{ return time_.iDuration; }
	float durationUnitSize( ) const noexcept
		{ return time_.uDuration; }

	void setTime( const float time );
	void timeDelta( const float delta )
		{ setTime( time_.time + delta ); }
	float time( ) const noexcept
		{ return time_.time; }

	bool playing( ) const noexcept
		{ return playing_; }
	bool& playing( ) noexcept
		{ return playing_; }

	bool finished( ) const noexcept
		{ return time_.time >= time_.duration( ); }
	void updateTime( ) noexcept;


	// ---------------------------------------------------------------------
	// Value access

	void clearInputs( ) noexcept;
	bool addInput( T_String const& name ,
			const float initial = 0.f ) noexcept
		{ return values_.addValue( name , initial ); }
	bool hasInput( T_String const& name ) const noexcept
		{ return values_.identifiers.contains( name ); }

	T_Array< T_String > const& inputNames( ) const noexcept
		{ return values_.identifiers; }
	uint32_t inputPos( T_String const& name ) const noexcept
		{ return values_.indexOf( name ); }
	T_Array< float > const& inputs( ) const noexcept
		{ return values_.values; }
	T_Array< float >& inputs( ) noexcept
		{ return values_.values; }


	// ---------------------------------------------------------------------
	// Curves

	void clearCurves( );
	void setCurve( T_SyncCurve curve );
	void removeCurve( T_String const& curve ) noexcept;
	T_SyncCurve const* getCurve(
			T_String const& name ) const noexcept;

	T_Array< T_SyncCurve > const& curves( ) const noexcept
		{ return curves_.curves.values( ); }

	bool curvesModified( ) const noexcept
		{ return modified_; }
	bool curvesFileChanged( ) const noexcept
		{ return fileChanged_; }

	bool loadCurves( bool undoable = true );
	bool saveCurves( );

    private:
	void curvesChanged_( );
	void addReloadUndoData_(
			T_SyncCurvesIO::T_Data const& data ) const noexcept;


	// ---------------------------------------------------------------------
	// Overrides

    public:
	void clearOverrides( ) noexcept;
	void mergeOverrides( T_SyncOverrideSection& overrides );

	bool overrideExists( T_String const& id ) const noexcept;
	A_SyncOverride* getOverride( T_String const& id ) const noexcept;

	// Mark a bunch of inputs as overridden / not overridden
	void setOverridesActive( bool active ,
			uint32_t n ,
			uint32_t const* pos );

	// Visit the overrides tree
	void visitOverrides( T_SyncOverrideVisitor::F_NodeAction visitor );

	// ---------------------------------------------------------------------
	// Update

	void updateCurveCaches( );
	void updateValues( );


	// ---------------------------------------------------------------------
	// Private data

    private:
	bool enabled_{ false };				// Interactive mode enabled?
	T_SyncCurvesIO io_;				// Curves loader/writer
	T_FSPath curvesFile_;				// Path to the curves file
	T_WatchedFiles watcher_;			// Curves file watcher
	bool saving_{ false };				// True if file is being saved
	bool modified_;					// Locally modified
	bool fileChanged_;				// File modified

	T_SyncTime time_;				// Duration/time information
	bool playing_{ false };				// Is it playing?

	T_SyncValues values_;				// Value storage
	T_SyncCurves curves_;				// Curves storage
	T_Array< P_SyncCurveCache > curveCaches_;	// Cache for curve segments

	T_SyncOverrideSection soRoot_;			// Root for overrides
	T_SyncOverrideVisitor soVisitor_;
	T_KeyValueTable< T_String , A_SyncOverride* > soTable_;
							// Table of sync overrides, by ID

    protected:
	void projectPathChanged( ) noexcept;
};