#pragma once

#include "Engine/UserDefinedStruct.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "VentuzBPEvent.generated.h"

DECLARE_DYNAMIC_DELEGATE_OneParam(FVentuzEventCallback, int, Argument);

UENUM()
enum class EulerRotationOrder : uint8
{
    ZXY UMETA( DisplayName = "ZXY" ),
    ZYX UMETA( DisplayName = "ZYX" ),
    YZX UMETA( DisplayName = "YZX" ),
    YXZ UMETA( DisplayName = "YXZ" ),
    XYZ UMETA( DisplayName = "XYZ" ),
    XZY UMETA( DisplayName = "XZY" )
};

UENUM()
enum class LimitedEulerRotationOrder : uint8
{
    XYZ UMETA( DisplayName = "XYZ" ),
};

UENUM()
enum class VentuzConnectionStatus : uint8
{
    Disconnected,
    Connected   
};

UCLASS(ClassGroup = Ventuz, Blueprintable)
class VENTUZPLUGIN_API UVentuzBPFunctionLibrary : public UBlueprintFunctionLibrary
{
public:
    GENERATED_UCLASS_BODY()

    // Events

	/**
	 * Register a custom event to be called whenever a specified Method is called in Ventuz
	 *
	 * @param	methodName  The name of the method on the Ventuz side
	 * @param	calledEvent	The custom event to be called when the Method is called in Ventuz
	 */
    UFUNCTION(BlueprintCallable, Category = "Ventuz")
    static void RegisterVentuzMethod( const FString& node, const FString& method, const FVentuzEventCallback calledEvent);

    /**
     * Trigger an event in Ventuz
     *
     * @param	eventName   The name of the event to trigger in Ventuz
     * @param	argument    The argument of the event
     */
    UFUNCTION( BlueprintCallable, Category = "Ventuz" )
    static void TriggerVentuzEvent( const FString& node, const FString& event, const int argument );

    // Getters

    /**
     * Get camera transform from Ventuz
     *
     * @param	isAvailable	True if the value transform was read successfully
     */
    UFUNCTION(BlueprintCallable, Category = "Ventuz")
    static FTransform GetVentuzCameraTransform(bool& isAvailable);

    /**
     * Get a Transform value from an Axis UE4 node from Ventuz
     *
     * @param	valueName  The name of the node to read from on the Ventuz side
     * @param	isAvailable	True if the value was read successfully
     * @return	The transform read from Ventuz, converted to the UE4 coordinate system
     */
    UFUNCTION(BlueprintCallable, Category = "Ventuz")
    static FTransform GetVentuzTransformValue(const FString& valueName, bool& isAvailable);

    // Senders

    /**
     * Send a Transform value to the named Ventuz Axis UE4 node
     *
     * @param	valueName  The name of the node to send to on the Ventuz side
     * @param	value	The transform to send to Ventuz, given in the UE4 coordinate system (conversion to Ventuz coordinates is done automatically)
     */
    UFUNCTION( BlueprintCallable, Category = "Ventuz" )
    static void SendVentuzTransformValue( const FString& valueName, const FTransform& value );

    // texture sharing

    /**
     * Create a texture share to send a live image to Ventuz
     *
     * @param	enabled	Initial enabled value
     */
    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void CreateVentuzTextureShare( /*const FString& textureShareName, const int stereoscopicPass = 0,*/ const bool enabled = true );

    /**
     * Destroy a texture share used to send a live image to Ventuz
     *
     * @param	textureShareName	The name of the texture share
     */
    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void DestroyVentuzTextureShare(/* const FString& textureShareName */);

    /**
     * Enable/disable a texture share used to send a live image to Ventuz
     *
     * @param	textureShareName	The name of the texture share
     * @param	enabled	Enabled value
     */
    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void EnableVentuzTextureShare( const FString& textureShareName, const bool enabled = true );

    // transformation helpers

    /**
     * Convert a position vector from the Ventuz coordinate system to the UE4 coordinate system
     *
     * @param	position	The position vector to convert in Ventuz coordinates
     * @return	The result position in UE4 coordinates
     */
    UFUNCTION( BlueprintPure, Category = "Ventuz" )
    static FVector VentuzToUE4_Position(const FVector& position );

    /**
     * Convert a position vector from the UE4 coordinate system to the Ventuz coordinate system
     *
     * @param	position	The position vector to convert in UE4 coordinates
     * @return	The result position in Ventuz coordinates
     */
    UFUNCTION( BlueprintPure, Category = "Ventuz" )
    static FVector UE4_ToVentuzPosition( const FVector& position );

    /**
     * Convert a scaling vector from the Ventuz coordinate system to the UE4 coordinate system
     *
     * @param	scale	The scaling vector to convert in Ventuz coordinates
     * @return	The result scaling vector in UE4 coordinates
     */
    UFUNCTION( BlueprintPure, Category = "Ventuz" )
    static FVector VentuzToUE4_Scale(const FVector& scale );

    /**
     * Convert a scaling vector from the UE4 coordinate system to the Ventuz coordinate system
     *
     * @param	scale	The scaling vector to convert in UE4 coordinates
     * @return	The result scaling vector in Ventuz coordinates
     */
    UFUNCTION( BlueprintPure, Category = "Ventuz" )
    static FVector UE4_ToVentuzScale( const FVector& scale );

    /**
     * Convert an Euler angle rotation from the Ventuz coordinate system to a UE4 rotator
     *
     * @param	rotation	The rotation values in Ventuz coordinates
     * @param	ventuzRotationOrder	The Ventuz rotation order in which to apply the rotation values
     * @return	The result rotator in UE4 coordinates
     */
    UFUNCTION( BlueprintPure, Category = "Ventuz" )
    static FRotator VentuzToUE4_Rotation(const FVector& rotation, const EulerRotationOrder ventuzRotationOrder = EulerRotationOrder::ZYX );

    /**
     * Convert a UE4 rotator to Ventuz coordinate system Euler angle values
     * WARNING: this node is currently limited to the XYZ Ventuz Euler angle rotation order
     *
     * @param	rotation	The input rotator in the UE4 coordinate system
     * @param	ventuzRotationOrder	The Ventuz rotation order in which to apply the rotation values (Limited to XYZ for this node currently)
     * @return	The result rotation values in Ventuz coordinates
     */
    UFUNCTION( BlueprintPure, Category = "Ventuz" )
    static FVector UE4_ToVentuzRotation( const FRotator& rotation, const LimitedEulerRotationOrder ventuzRotationOrder = LimitedEulerRotationOrder::XYZ );

    /**
     * Convert Ventuz coordinate system transformation values to a UE4 coordinate system Transform
     *
     * @param	position	The position in Ventuz coordinates
     * @param	rotation	The Euler angle rotation values in the Ventuz coordinate system
     * @param	ventuzRotationOrder	The Ventuz rotation order in which to apply the rotation values
     * @param	scale	The scale values in Ventuz coordinates
     * @return	The result Transform in the UE4 coordinate system
     */
    UFUNCTION( BlueprintPure, Category = "Ventuz" )
    static FTransform VentuzToUE4_Transform(const FVector& position, const FVector& rotation,  const EulerRotationOrder ventuzRotationOrder = EulerRotationOrder::ZYX, const FVector& scale = FVector(1, 1, 1) );

    /**
     * Convert a UE4 Transform to the Ventuz coordinate system 
     * WARNING: this node is currently limited to the XYZ Ventuz Euler angle rotation order
     *
     * @param	transform	The input transform in the UE4 coordinate system
     * @param	position	The resulting position in Ventuz coordinates
     * @param	rotation	The resulting Euler angle rotation values in the Ventuz coordinate system
     * @param	ventuzRotationOrder	The Ventuz rotation order in which to apply the rotation values (Limited to XYZ for this node currently)
     * @param	scale	The resulting scale values in Ventuz coordinates
     */
    UFUNCTION( BlueprintPure, Category = "Ventuz" )
    static void UE4_ToVentuzTransform( const FTransform& transform, FVector& position, FVector& rotation, FVector& scale, const LimitedEulerRotationOrder ventuzRotationOrder = LimitedEulerRotationOrder::XYZ );

    /**
     * Query the status of the Ventuz connection
     *
     * @return	Returns true if Ventuz is connected
     */
    UFUNCTION( BlueprintPure, Category = "Ventuz" )
    static bool IsVentuzConnected();

    /**
     * Query the status of the Ventuz connection
     *
     * @param   frameCounter          Cluster Clock frame counter which represents the elapsed time as frame index
     * @param   time                  Cluster Clock value which represents the elapsed time in seconds
     * @param   frameRateNumerator    Frame-rate numerator value to calculate exact frame-rate
     * @param   frameRateDenominator  Frame-rate denominator value to calculate exact frame-rate
     *
     * @return	True if the values are valid
     */
    UFUNCTION( BlueprintPure, Category = "Ventuz")
    static bool VentuzClusterClock(int64 &frameCounter, float& time, int& frameRateNumerator, int& frameRateDenominator);

    UFUNCTION( BlueprintCallable, meta = ( ExpandEnumAsExecs = connectionStatus ), Category = "Ventuz" )
    static void VentuzConnectionState( VentuzConnectionStatus& connectionStatus );

    // internal helpers

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static int GetVentuzIntValueByNodeName( const FString& nodeName, const FString& fieldName, bool& isAvailable );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static float GetVentuzFloatValueByNodeName( const FString& nodeName, const FString& fieldName, bool& isAvailable );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static FString GetVentuzStringValueByNodeName( const FString& nodeName, const FString& fieldName, bool& isAvailable );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static bool GetVentuzBoolValueByNodeName( const FString& nodeName, const FString& fieldName, bool& isAvailable );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static TArray<int> GetVentuzIntArrayByNodeName( const FString& nodeName, const FString& fieldName, bool& isAvailable );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static TArray<float> GetVentuzFloatArrayByNodeName( const FString& nodeName, const FString& fieldName, bool& isAvailable );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static TArray<FString> GetVentuzStringArrayByNodeName( const FString& nodeName, const FString& fieldName, bool& isAvailable );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static TArray<bool> GetVentuzBoolArrayByNodeName( const FString& nodeName, const FString& fieldName, bool& isAvailable );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void SendVentuzIntValueByNodeName( const FString& nodeName, const FString& fieldName, const int value );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void SendVentuzFloatValueByNodeName( const FString& nodeName, const FString& fieldName, const float value );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void SendVentuzStringValueByNodeName( const FString& nodeName, const FString& fieldName, const FString value );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void SendVentuzBoolValueByNodeName( const FString& nodeName, const FString& fieldName, const bool value );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void SendVentuzIntArrayByNodeName( const FString& nodeName, const FString& fieldName, const TArray<int> value );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void SendVentuzFloatArrayByNodeName( const FString& nodeName, const FString& fieldName, const TArray<float> value );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void SendVentuzStringArrayByNodeName( const FString& nodeName, const FString& fieldName, const TArray<FString> value );

    UFUNCTION( BlueprintInternalUseOnly, BlueprintCallable, Category = "Ventuz" )
    static void SendVentuzBoolArrayByNodeName( const FString& nodeName, const FString& fieldName, const TArray<bool> value );
};
