#pragma once
#include "filewatcher.hh"
#include "utilities.hh"

#include <ebcl/SRDParserConfig.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< uint32_t > durations;	// n(values) - 1 items
};

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

	T_SyncCurve( ) noexcept { }

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

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

// 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( );
	void setCurve( T_SyncCurve 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 ) );
	}
};

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

// 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;
};

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

// Synchronisation manager; handles all the synchronization data and makes it
// work together.
struct T_SyncManager
{
	T_SyncManager( );

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

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

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

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

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

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

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

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

	void checkCurveFile( );
	void clearCurves( );
	void setCurve( T_SyncCurve curve );

    private:
	void curvesChanged_( );
	bool loadCurves_( bool& missing );

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

    public:
	void updateCurveCaches( );
	void updateValues( );

    private:
	ebcl::T_SRDParserConfig pConfig_;
	P_WatchedFiles watcher_;
	T_SyncTime time_;
	T_SyncValues values_;
	T_SyncCurves curves_;
	T_Array< P_SyncCurveCache > curveCaches_;
};


/*============================================================================*/
// DISREGARD EVERYTHING BELOW, IT SUCKS!
/*============================================================================*/

#if 0
// Definition of a single input - id & range
struct T_SyncInputDfn
{
	std::string identifier;
	float min = -std::numeric_limits< float >::infinity( ),
	      max = std::numeric_limits< float >::infinity( );

	T_SyncInputDfn( std::string const& identifier ,
			const float min ,
			const float max ) noexcept
		: identifier( identifier ) , min( min ) , max( max )
	{ }
};
using P_SyncInputDfn = std::unique_ptr< T_SyncInputDfn >;

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

// Definition of UI overrides for inputs
struct T_SyncUIOverride
{
	enum E_Type {
		FLOAT , VEC2 , VEC3 , VEC4 , INT ,
		COLOR , COLOR_GRADING , CAMERA
	};

	std::string title;
	E_Type type;
	std::vector< std::string > inputs;
	bool enabled;
};
using P_SyncUIOverride = std::unique_ptr< T_SyncUIOverride >;
using T_SyncUIOverrides = std::vector< P_SyncUIOverride >;

// UI override sections
struct T_SyncUISection;
using P_SyncUISection = std::unique_ptr< T_SyncUISection >;
using T_SyncUISections = std::vector< P_SyncUISection >;
struct T_SyncUISection
{
	std::string title;
	T_SyncUISections subsections;
	T_SyncUIOverrides overrides;
};

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

struct T_SyncManager
{
	float uDuration;		// Duration - unit size
	uint32_t iDuration;		// Duration - total units

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

	// Sync manager data for an input may include a definition, a curve,
	// or both. The idea behind supporting "curve only" is that a curve may
	// have been defined for an input that has been removed temporarily
	// (e.g. because some include was commented out), in which case we don't
	// want to waste it.
	struct T_Data
	{
		P_SyncInputDfn definition;
		P_SyncCurveCache curve;
		bool overriden;
	};

	// Data and ID<->index map
	std::vector< T_Data > data;
	std::map< std::string , uint32_t > posMap;

	// Current time & values
	float time;
	std::vector< float > values;

	// Root of the UI's tree
	T_SyncUISections uiRoot;

	void makeUI( );
	bool wOverrides = false;
	bool wCurves = false;

    private:
	void displayOvSections(
			T_SyncUISections& sections ,
			const bool topLevel = false );
	void displayOvControls(
			T_SyncUIOverrides& overrides );
};
#endif