#include "externals.hh"
#include "common.hh"
#include "ui.hh"
#include "ui-actions.hh"
#include "ui-app.hh"
#include "ui-overrides.hh"
#include "ui-sync.hh"
#include "ui-utilities.hh"


/*= T_SyncInputData_ =========================================================*/
namespace {

class T_SyncInputData_ : public sov::A_SyncData
{
    private:
	T_AutoArray< float* , 16 > ptr_;

    public:
	void setFromOverride( A_SyncOverride const& sov ) noexcept;

	float operator[]( uint32_t index ) const noexcept override;
	bool set( uint32_t index , float value ) noexcept override;

	T_OwnPtr< sov::A_SyncData > clone( ) const noexcept override;
};


void T_SyncInputData_::setFromOverride(
		A_SyncOverride const& sov ) noexcept
{
	auto const& iPos{ sov.inputPositions() };
	const auto np{ iPos.size( ) };
	ptr_.ensureCapacity( np );
	ptr_.clear( );

	auto& inputs{ Common::Sync( ).inputs( ) };
	for ( auto i = 0u ; i < np ; i ++ ) {
		ptr_.add( &inputs[ iPos[ i ] ] );
	}
}

float T_SyncInputData_::operator[](
		const uint32_t index ) const noexcept
{
	return *ptr_[ index ];
}

bool T_SyncInputData_::set(
		const uint32_t index ,
		const float value ) noexcept
{
	*ptr_[ index ] = value;
	return true;
}

T_OwnPtr< sov::A_SyncData > T_SyncInputData_::clone( ) const noexcept
{
	return NewOwned< T_SyncInputData_ >( *this );
}


} // namespace <anon>
/*= T_UISync =================================================================*/

T_UISync::T_UISync( )
{
	UI::Main( ).addAction( T_UIAction{ "Save curves" , []() {
		if ( Common::Sync( ).curvesFileChanged( ) ) {
			UI::Main( ).msgbox(
				"Curves file changed" ,
				"The file containing the curves has been modified "
				"on the disk. These changes will be overwritten. "
				"Do you want to continue?" ,
				[]( auto b ) {
					if ( b == T_MessageBox::BT_YES ) {
						Common::Sync( ).saveCurves( );
					}
				} , { T_MessageBox::BT_YES , T_MessageBox::BT_NO } );
		} else {
			Common::Sync( ).saveCurves( );
		}
	} }.setEnabledCheck( []() {
		return Common::Sync( ).curvesModified( );
	} ).setIcon( ICON_FA_FLOPPY_O )
		.setShortcut( T_KeyboardShortcut{ 's' , E_KbdMod::CTRL } ) );
	//-----------------------------------------------------------------------------
	UI::Main( ).addAction( T_UIAction{ "Reload curves" , []() {
		UI::Main( ).msgbox(
			"Reload curves?" ,
			"Changes you made to the curves will be lost. Do you "
			"want to continue?" ,
			[]( auto b ) {
				if ( b == T_MessageBox::BT_YES ) {
					Common::Sync( ).loadCurves( );
				}
			} , { T_MessageBox::BT_YES , T_MessageBox::BT_NO } );
	} }.setEnabledCheck( []() {
		return Common::Sync( ).curvesModified( );
	} ).setIcon( ICON_FA_DOWNLOAD )
		.setShortcut( T_KeyboardShortcut{ 'r' ,
				{ E_KbdMod::CTRL , E_KbdMod::SHIFT } } ) );
	//-----------------------------------------------------------------------------
	const auto addui{ [this]( char const* type , F_Override ov ) {
		const bool ok{ sovuis_.add( T_String{ type } , std::move( ov ) ) };
		assert( ok ); (void)ok;
	} };
	addui( "float" , sov::UIFloat );
	addui( "float2" , sov::UIFloat2 );
	addui( "float3" , sov::UIFloat3 );
	addui( "float4" , sov::UIFloat4 );
	addui( "int" , sov::UIInteger );
	addui( "int2" , sov::UIInteger2 );
	addui( "int3" , sov::UIInteger3 );
	addui( "int4" , sov::UIInteger4 );
	addui( "cg" , sov::UIColorGrading );
	addui( "cam" , sov::UICamera );
}

T_UISync::~T_UISync( )
{
	UI::Main( ).clearMouseDelegate( );
}

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

namespace {

bool HandleOverrideSection_(
		T_SyncOverrideSection& sos ,
		bool exit ,
		T_AutoArray< bool , 32 >& stack )
{
	if ( exit ) {
		assert( !stack.empty( ) );
		if ( stack.last( ) && stack.size( ) > 1 ) {
			ImGui::TreePop( );
		}
		stack.removeLast( );
		return true;
	}

	const bool display( stack.empty( )
			? ImGui::CollapsingHeader( &sos.cTitle[ 0 ] )
			: ImGui::TreeNode( &sos.cTitle[ 0 ] ) );
	stack.add( display );
	return display;
}

void HandleOverride_(
		A_SyncOverride& ov ,
		T_SyncInputData_& data ,
		uint32_t& counter ,
		T_StringBuilder& sb ) noexcept
{
	using namespace ImGui;

	bool& enabled{ ov.enabled( ) };
	if ( Checkbox( &ov.title( )[ 0 ] , &enabled ) ) {
		auto const& ipos( ov.inputPositions( ) );
		Common::Sync( ).setOverridesActive( ov.enabled( ) ,
				ipos.size( ) , &ipos[ 0 ] );
	}
	if ( !enabled ) {
		PushDisabled( );
	}
	Indent( );
	PushItemWidth( -1 );
	data.setFromOverride( ov );
	(UI::Sync( ).uiFor( ov ))( ov , data , counter , sb );
	PopItemWidth( );
	Unindent( );
	if ( !enabled ) {
		PopDisabled( );
	}
}

} // namespace <anon>

T_UISync::F_Override T_UISync::uiFor(
		A_SyncOverride& target ) const noexcept
{
	auto const* const rv{ sovuis_.get( target.type( ) ) };
	return rv ? *rv : []( A_SyncOverride& , sov::A_SyncData& ,
			uint32_t& , T_StringBuilder& ) {
		ImGui::Text( "(missing UI)" );
		return false;
	};
}

void T_UISync::makeOverridesWindow( )
{
	if ( !ovWindow_ ) {
		return;
	}

	using namespace ImGui;
	auto const& dspSize( GetIO( ).DisplaySize );
	SetNextWindowSize( ImVec2( dspSize.x * .25f , dspSize.y * .66f - 20 ) , ImGuiSetCond_Appearing );
	SetNextWindowPos( ImVec2( 0 , 20 ) , ImGuiSetCond_Appearing );
	Begin( "Input overrides" , &ovWindow_ ,
			ImGuiWindowFlags_NoCollapse );

	T_AutoArray< bool , 32 > stack;
	T_SyncInputData_ data;
	T_StringBuilder temp;
	uint32_t counter{ 0 };
	bool found{ false };
	using T_Ove_ = T_SyncOverrideVisitor::T_Element;
	Common::Sync( ).visitOverrides( [&]( T_Ove_ element , bool exit ) {
		// Display sections
		if ( element.hasType< T_SyncOverrideSection* >( ) ) {
			auto& sos( *element.value< T_SyncOverrideSection* >( ) );
			if ( sos.title == "*root*" ) {
				return true;
			}
			found = true;
			return HandleOverrideSection_( sos , exit , stack );
		}
		if ( exit ) {
			HandleOverride_( *element.value< A_SyncOverride* >( ) ,
					data , counter , temp );
			found = true;
		}
		return true;
	} );

	if ( !found ) {
		Text( "No overrides have been defined." );
	}

	End( );
}

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

void T_UISync::updateTime( ) noexcept
{
	auto& s{ Common::Sync( ) };
	if ( s.playing( ) ) {
		const float time( SDL_GetTicks( ) * 1e-3f );
		if ( playingPrevious_ ) {
			s.timeDelta( time - lastFrame_ );
		}
		lastFrame_ = time;
	}
	playingPrevious_ = s.playing( );
}

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

void T_UISync::delegateMouse(
		T_String const& id ,
		P_MouseCtrl delegate ) noexcept
{
	mouseDelegateName_ = id;
	mouseDelegate_ = std::move( delegate );
	UI::Main( ).setMouseDelegate( this );
}

void T_UISync::clearMouseDelegate( ) noexcept
{
	mouseDelegateName_ = T_String{ };
	mouseDelegate_ = P_MouseCtrl{ };
	UI::Main( ).clearMouseDelegate( );
}

void T_UISync::handleDragAndDrop(
		ImVec2 const& move ,
		T_KbdMods modifiers ,
		T_MouseButtons buttons ) noexcept
{
	if ( mouseDelegate_ ) {
		mouseDelegate_->handleDragAndDrop( move , modifiers , buttons );
	}
}

void T_UISync::handleWheel(
		const float wheel ,
		T_KbdMods modifiers ,
		T_MouseButtons buttons ) noexcept
{
	if ( mouseDelegate_ ) {
		mouseDelegate_->handleWheel( wheel , modifiers , buttons );
	}
}