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

#include <imgui_internal.h>


namespace sov {

namespace {

char const* BuildLabel_(
		uint32_t& counter ,
		T_StringBuilder& sb ) noexcept
{
	sb.clear( ) << "##temp label " << counter << '\0';
	counter ++;
	return sb.data( );
}

} // namespace <anon>


A_SyncData::~A_SyncData( ) { }


/*= FLOAT OVERRIDES ==========================================================*/

M_DECL_SOVUI( Float )
{
	using namespace ImGui;
	auto& ov{ dynamic_cast< T_Float& >( ovp ) };
	char const* const label( BuildLabel_( counter , sb ) );
	float v{ data[ 0 ] };
	const bool changed( ov.slider( )
			? SliderFloat( label , &v , ov.min( ) , ov.max( ) ,
				ov.decimals( ) , ov.power( ) )
			: DragFloat( label , &v , ov.step( ) , ov.min( ) ,
				ov.max( ) , ov.decimals( ) , ov.power( ) ) );
	if ( changed ) {
		data.set( 0 , v );
	}
	return changed;
}

M_DECL_SOVUI( Float2 )
{
	using namespace ImGui;
	auto& ov{ dynamic_cast< T_Float2& >( ovp ) };
	float v[ 2 ];
	for ( auto i = 0 ; i < 2 ; i ++ ) {
		v[ i ] = data[ i ];
	}

	char const* const label( BuildLabel_( counter , sb ) );
	const bool changed( ov.slider( )
			? SliderFloat2( label , v , ov.min( ) , ov.max( ) ,
				ov.decimals( ) , ov.power( ) )
			: DragFloat2( label , v , ov.step( ) , ov.min( ) ,
				ov.max( ) , ov.decimals( ) , ov.power( ) ) );
	if ( changed ) {
		for ( auto i = 0 ; i < 2 ; i ++ ) {
			data.set( i , v[ i ] );
		}
	}
	return changed;
}

M_DECL_SOVUI( Float3 )
{
	using namespace ImGui;
	auto& ov{ dynamic_cast< T_Float3& >( ovp ) };
	float v[ 3 ];
	for ( auto i = 0 ; i < 3 ; i ++ ) {
		v[ i ] = data[ i ];
	}

	char const* const label( BuildLabel_( counter , sb ) );
	const bool changed( ov.slider( )
			? SliderFloat3( label , v , ov.min( ) , ov.max( ) ,
				ov.decimals( ) , ov.power( ) )
			: DragFloat3( label , v , ov.step( ) , ov.min( ) ,
				ov.max( ) , ov.decimals( ) , ov.power( ) ) );
	if ( changed ) {
		for ( auto i = 0 ; i < 3 ; i ++ ) {
			data.set( i ,v[ i ]);
		}
	}
	return changed;
}

M_DECL_SOVUI( Float4 )
{
	using namespace ImGui;
	auto& ov{ dynamic_cast< T_Float4& >( ovp ) };
	float v[ 4 ];
	for ( auto i = 0 ; i < 4 ; i ++ ) {
		v[ i ] = data[ i ];
	}

	char const* const label( BuildLabel_( counter , sb ) );
	const bool changed( ov.slider( )
			? SliderFloat4( label , v , ov.min( ) , ov.max( ) ,
				ov.decimals( ) , ov.power( ) )
			: DragFloat4( label , v , ov.step( ) , ov.min( ) ,
				ov.max( ) , ov.decimals( ) , ov.power( ) ) );
	if ( changed ) {
		for ( auto i = 0 ; i < 4 ; i ++ ) {
			data.set( i ,v[ i ]);
		}
	}
	return changed;
}


/*= INTEGER OVERRIDES ========================================================*/

M_DECL_SOVUI( Integer )
{
	using namespace ImGui;
	auto& ov{ dynamic_cast< T_Integer& >( ovp ) };
	int32_t v = int32_t( data[ 0 ] );

	char const* const label( BuildLabel_( counter , sb ) );
	const bool changed( ov.slider( )
			? SliderInt( label , &v , ov.min( ) , ov.max( ) )
			: DragInt( label , &v , ov.step( ) , ov.min( ) , ov.max( ) ) );
	if ( changed ) {
		data.set( 0 ,v);
	}
	return changed;
}

M_DECL_SOVUI( Integer2 )
{
	using namespace ImGui;
	auto& ov{ dynamic_cast< T_Integer2& >( ovp ) };
	int32_t v[ 2 ];
	for ( auto i = 0 ; i < 2 ; i ++ ) {
		v[ i ] = data[ i ];
	}

	char const* const label( BuildLabel_( counter , sb ) );
	const bool changed( ov.slider( )
			? SliderInt2( label , v , ov.min( ) , ov.max( ) )
			: DragInt2( label , v , ov.step( ) , ov.min( ) , ov.max( ) ) );
	if ( changed ) {
		for ( auto i = 0 ; i < 2 ; i ++ ) {
			data.set( i ,v[ i ]);
		}
	}
	return changed;
}

M_DECL_SOVUI( Integer3 )
{
	using namespace ImGui;
	auto& ov{ dynamic_cast< T_Integer3& >( ovp ) };
	int32_t v[ 3 ];
	for ( auto i = 0 ; i < 3 ; i ++ ) {
		v[ i ] = data[ i ];
	}

	char const* const label( BuildLabel_( counter , sb ) );
	const bool changed( ov.slider( )
			? SliderInt3( label , v , ov.min( ) , ov.max( ) )
			: DragInt3( label , v , ov.step( ) , ov.min( ) , ov.max( ) ) );
	if ( changed ) {
		for ( auto i = 0 ; i < 3 ; i ++ ) {
			data.set( i ,v[ i ]);
		}
	}
	return changed;
}

M_DECL_SOVUI( Integer4 )
{
	using namespace ImGui;
	auto& ov{ dynamic_cast< T_Integer4& >( ovp ) };
	int32_t v[ 4 ];
	for ( auto i = 0 ; i < 4 ; i ++ ) {
		v[ i ] = data[ i ];
	}

	char const* const label( BuildLabel_( counter , sb ) );
	const bool changed( ov.slider( )
			? SliderInt4( label , v , ov.min( ) , ov.max( ) )
			: DragInt4( label , v , ov.step( ) , ov.min( ) , ov.max( ) ) );
	if ( changed ) {
		for ( auto i = 0 ; i < 4 ; i ++ ) {
			data.set( i ,v[ i ]);
		}
	}
	return changed;
}


/*= COLOR GRADING OVERRIDES ==================================================*/

M_DECL_SOVUI( ColorGrading )
{
	using namespace ImGui;
	auto& ov{ dynamic_cast< T_ColorGrading& >( ovp ) };
	float v[ 3 ];
	for ( auto i = 0 ; i < 3 ; i ++ ) {
		v[ i ] = data[ i ];
	}

	char const* const label{ BuildLabel_( counter , sb ) };
	const bool changed{ ColorGradingControls(
				label ,
				&v[ 0 ] , &v[ 1 ] , &v[ 2 ] ,
				ov.base( ) , ov.unit( ) ) };

	if ( changed ) {
		for ( auto i = 0 ; i < 3 ; i ++ ) {
			data.set( i ,v[ i ]);
		}
	}
	return changed;
}


/*= CAMERA OVERRIDES =========================================================*/

namespace {

glm::vec3 VectorFromInputs_(
		T_CamOverride::T_VectorConfig const& vc ,
		A_SyncData const& data ) noexcept
{
	return glm::vec3{ data[ vc.x ]  , data[ vc.y ]  , data[ vc.z ] };
}

void InputsFromVector_(
		T_CamOverride::T_VectorConfig const& vc ,
		A_SyncData& data ,
		glm::vec3 const& v ) noexcept
{
	data.set( vc.x ,v.x);
	data.set( vc.y ,v.y);
	data.set( vc.z ,v.z);
}

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

struct T_MouseCam_ : public virtual A_MouseCtrl
{
	T_CameraMouseControl camera;
	T_CamOverride& ov;
	T_OwnPtr< A_SyncData > data;

	T_MouseCam_( T_Camera& cam ,
			T_CamOverride& ov ,
			A_SyncData const& data ) noexcept
		: camera( cam ) , ov( ov ) , data( data.clone( ) )
	{ }

	void handleDragAndDrop(
			ImVec2 const& move ,
			T_KbdMods modifiers ,
			T_MouseButtons buttons ) noexcept override;

	void handleWheel(
			float wheel ,
			T_KbdMods modifiers ,
			T_MouseButtons buttons ) noexcept override;
};

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

void T_MouseCam_::handleDragAndDrop(
		ImVec2 const& move ,
		T_KbdMods modifiers ,
		T_MouseButtons buttons ) noexcept
{
	if ( !ov.enabled( ) ) {
		UI::Sync( ).clearMouseDelegate( );
		return;
	}

	camera.handleDragAndDrop( move , modifiers , buttons );

	auto& cam{ camera.camera };
	InputsFromVector_( ov.target( ) , *data , cam.lookAt( ) );
	if ( ov.mode( ) == T_CamOverride::CM_ANGLES ) {
		InputsFromVector_( ov.angles( ) , *data , cam.angles( ) );
		data->set( ov.distance( ) ,cam.distance( ));
	} else {
		InputsFromVector_( ov.position( ) , *data , cam.position( ) );
		InputsFromVector_( ov.up( ) , *data , cam.upVector( ) );
	}
}

void T_MouseCam_::handleWheel(
		const float wheel ,
		T_KbdMods modifiers ,
		T_MouseButtons buttons ) noexcept
{
	if ( !ov.enabled( ) ) {
		UI::Sync( ).clearMouseDelegate( );
		return;
	}

	camera.handleWheel( wheel , modifiers , buttons );

	auto& cam{ camera.camera };
	if ( modifiers & E_KbdMod::SHIFT ) {
		auto const& fc( ov.fovConfig( ) );
		if ( fc.mode == T_CamOverride::FM_FOV ) {
			data->set( fc.inputIndex ,cam.fieldOfView( ));
		} else {
			data->set( fc.inputIndex ,cam.nearPlane( ));
		}
	} else {
		InputsFromVector_( ov.target( ) , *data , cam.lookAt( ) );
		if ( ov.mode( ) == T_CamOverride::CM_ANGLES ) {
			InputsFromVector_( ov.angles( ) , *data , cam.angles( ) );
			data->set( ov.distance( ) ,cam.distance( ));
		} else {
			InputsFromVector_( ov.position( ) , *data , cam.position( ) );
			InputsFromVector_( ov.up( ) , *data , cam.upVector( ) );
		}
	}
}

}  // namespace <anon>

M_DECL_SOVUI( Camera )
{
	auto& ov{ dynamic_cast< T_CamOverride& >( ovp ) };
	auto& camera{ ov.camData( ) };
	auto const& fc{ ov.fovConfig( ) };

	if ( !ov.enabled( ) || !ov.prevEnabled( ) ) {
		// Set field of view / near plane
		if ( fc.mode == T_CamOverride::FM_FOV ) {
			camera.fieldOfView( data[ fc.inputIndex ]  );
		} else {
			camera.nearPlane( data[ fc.inputIndex ]  );
		}

		// Set camera parameters
		const glm::vec3 lookAt{ VectorFromInputs_(
				ov.target( ) , data ) };
		if ( ov.mode( ) == T_CamOverride::CM_ANGLES ) {
			const glm::vec3 angles{ VectorFromInputs_(
					ov.angles( ) , data ) };
			const float distance{ data[ ov.distance( ) ]  };
			camera.camera( lookAt , angles , distance );
		} else {
			const glm::vec3 position{ VectorFromInputs_(
					ov.position( ) , data ) };
			const glm::vec3 up{ VectorFromInputs_(
					ov.up( ) , data ) };
			camera.camera( lookAt , position , up );
		}
	}
	ov.prevEnabled( ) = ov.enabled( );

	// Draw UI
	char const* const name( BuildLabel_( counter , sb ) );
	auto& sui{ UI::Sync( ) };
	bool mouseHandler{ sui.isCurrentDelegate( ov.id( ) ) };
	using namespace ImGui;
	PushItemWidth( 0 ); // Compensate for -1
	PushID( GetCurrentWindow( )->GetID( name ) );
	const bool handlerChanged{ Checkbox( "Mouse control" , &mouseHandler ) };
	Separator( );
	const auto changes{ CameraUI( camera ) };
	PopID( );
	PopItemWidth( );

	if ( handlerChanged ) {
		if ( mouseHandler ) {
			sui.delegateMouse( ov.id( ) ,
				NewOwned< T_MouseCam_ >( camera , ov , data ) );
		} else {
			sui.clearMouseDelegate( );
		}
	}

	// Update values
	if ( changes & E_CameraChange::FOV ) {
		if ( fc.mode == T_CamOverride::FM_FOV ) {
			data.set( fc.inputIndex ,camera.fieldOfView( ));
		} else {
			data.set( fc.inputIndex ,camera.nearPlane( ));
		}
	}
	if ( changes & E_CameraChange::MATRIX ) {
		InputsFromVector_( ov.target( ) , data , camera.lookAt( ) );
		if ( ov.mode( ) == T_CamOverride::CM_ANGLES ) {
			InputsFromVector_( ov.angles( ) , data , camera.angles( ) );
			data.set( ov.distance( ) ,camera.distance( ));
		} else {
			InputsFromVector_( ov.position( ) , data , camera.position( ) );
			InputsFromVector_( ov.up( ) , data , camera.upVector( ) );
		}
	}
	return changes;
}


} // namespace sov