#include "VentuzPluginModule.h"
#include "Runtime/Renderer/Private/PostProcess/SceneRenderTargets.h"
#include "Framework/Application/SlateApplication.h"
#include "Windows/WindowsApplication.h"
#include "RendererInterface.h"

#include "EngineGlobals.h"
#include "Engine.h"
#include <WinSock.h>
#include "Slate/SceneViewport.h"
#include "Scalability.h"
#include "Runtime/Engine/Classes/Engine/UserInterfaceSettings.h"
#include "Runtime/Engine/Classes/Engine/RendererSettings.h"
#include "GameFramework/GameUserSettings.h"
#include "Runtime/Launch/Resources/Version.h"

#if WITH_EDITOR
#include "Editor/EditorPerformanceSettings.h"
#include "Editor/UnrealEd/Classes/Editor/EditorEngine.h"
#include "UnrealEd.h"
#include "IAssetViewport.h"
#include "LevelEditor.h"
#include "LevelEditorViewport.h"
#include "SceneView.h"
#endif

uint ChecksumCRC32( const uint8* data, __int64 size );

class OscParser
{
public:
    uint8* Scan;
    uint8* End;

    OscParser()
    {
        End = Scan = (uint8*)"";
    }
    void Start( uint8* ptr, int bytes )
    {
        Scan = ptr;
        End = Scan + bytes;
    }

    char* String()
    {
        char* result = (char*)Scan;
        int n = 0;
        while ( Scan[ n++ ] != 0 );
        Scan += ( n + 3 ) & ~3;

        return result;
    }
    uint UInt()
    {
        uint r = ( Scan[ 0 ] << 24 ) | ( Scan[ 1 ] << 16 ) | ( Scan[ 2 ] << 8 ) | ( Scan[ 3 ] << 0 );
        Scan += 4;
        return r;
    }
    int Int()
    {
        int r = ( Scan[ 0 ] << 24 ) | ( Scan[ 1 ] << 16 ) | ( Scan[ 2 ] << 8 ) | ( Scan[ 3 ] << 0 );
        Scan += 4;
        return r;
    }
    double Float()
    {
        uint32 data = ( Scan[ 0 ] << 24 ) | ( Scan[ 1 ] << 16 ) | ( Scan[ 2 ] << 8 ) | ( Scan[ 3 ] << 0 );
        float r = *( (float*)&data );
        Scan += 4;
        return r;
    }
    double Double()
    {
        uint8 data[ 8 ];
        for ( int x = 0; x < 8; x++ )
            data[ 7 - x ] = Scan[ x ];
        double r = *( (double*)data );
        Scan += 8;
        return r;
    }
};

static const FName LevelEditorName(TEXT("LevelEditor"));

void FindSceneViewport(TWeakPtr<FSceneViewport>& OutSceneViewport)
{
#if WITH_EDITOR
    if (GIsEditor)
    {
        for (const FWorldContext& Context : GEngine->GetWorldContexts())
        {
            if (Context.WorldType == EWorldType::Editor)
            {
                if (FModuleManager::Get().IsModuleLoaded(LevelEditorName))
                {
                    FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(LevelEditorName);
                    TSharedPtr<IAssetViewport> ActiveLevelViewport = LevelEditorModule.GetFirstActiveViewport();
                    if (ActiveLevelViewport.IsValid())
                    {
                        OutSceneViewport = ActiveLevelViewport->GetSharedActiveViewport();
                    }
                }
            }
            else if (Context.WorldType == EWorldType::PIE)
            {
                FSlatePlayInEditorInfo* SlatePlayInEditorSession = GEditor->SlatePlayInEditorMap.Find(Context.ContextHandle);
                if (SlatePlayInEditorSession)
                {
                    if (SlatePlayInEditorSession->DestinationSlateViewport.IsValid())
                    {
                        TSharedPtr<IAssetViewport> DestinationLevelViewport = SlatePlayInEditorSession->DestinationSlateViewport.Pin();
                        OutSceneViewport = DestinationLevelViewport->GetSharedActiveViewport();
                    }
                    else if (SlatePlayInEditorSession->SlatePlayInEditorWindowViewport.IsValid())
                    {
                        OutSceneViewport = SlatePlayInEditorSession->SlatePlayInEditorWindowViewport;
                    }
                }
            }
        }
    }
    else
#endif
    {
        UGameEngine* GameEngine = Cast<UGameEngine>(GEngine);
        OutSceneViewport = GameEngine->SceneViewport;
    }
}

FVentuzInputDevice::FVentuzInputDevice( const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler/*, TArray<UPixelStreamerInputComponent*>& InInputComponents*/ )
    : appWrapper( MakeShareable( new FVentuzApplicationWrapper( FSlateApplication::Get().GetPlatformApplication() ) ) )
    , MessageHandler( InMessageHandler )
{
    for ( int x = 0; x < 10; x++ )
        touchIndexMap[ x ] = -1;
}

void FVentuzInputDevice::ParseOSCBundle( std::string data )
{
    OscParser osc;
    osc.Start( (uint8*)data.data(), data.length() );

    if ( strcmp( osc.String(), "#bundle" ) != 0 )
        return;

    uint64 Timecode;
    Timecode = osc.UInt();
    Timecode = ( Timecode << 32 ) | osc.UInt();

    uint8* pos = osc.Scan;
    uint size = 0;

    GenericApplication* WindowsApplication = FSlateApplication::Get().GetPlatformApplication().Get();


    UGameViewportClient* vp = nullptr;
    if ( GEngine && GEngine->GameViewport )
        vp = GEngine->GameViewport;

    FVector2D viewportSize;

    if ( vp )
        vp->GetViewportSize( viewportSize );

    do
    {
        osc.Scan = pos + size;
        size = osc.UInt();
        pos = osc.Scan;

        if ( !size )
            break;

        const char* addr = osc.String();
        const char* args = *osc.Scan == ',' ? osc.String() : "";

        const char* arguments[ 10 ];
        int argCount = 0;

        for ( char* p = (char*)addr; *p; p++ )
        {
            if ( *p == '/' )
            {
                arguments[ argCount++ ] = p + 1;
                *p = 0;
            }
        }

        if ( argCount < 3 )
            continue;

        if ( strcmp( arguments[ 0 ], "ventuz" ) )
            continue;

        int receiverID{};
        if ( sscanf_s( arguments[ 1 ], "%d", &receiverID ) != 1 )
            continue;

        const char* deviceType = arguments[ 2 ];

        if ( !strcmp( deviceType, "InputTimecode" ) )
        {
            double timeCode = osc.Double();
            continue;
        }

        if ( argCount < 6 )
            continue;

        int deviceID;
        int touchGroup;
        const char* id = arguments[ 4 ];

        if ( sscanf_s( arguments[ 3 ], "%d", &deviceID ) != 1 )
            continue;

        if ( sscanf_s( arguments[ 5 ], "%d", &touchGroup ) != 1 )
            continue;

        int boolCount = 0;
        bool boolValues[ 10 ]{};
        int intCount = 0;
        int intValues[ 10 ]{};
        int floatCount = 0;
        float floatValues[ 10 ]{};
        int stringCount = 0;
        char* stringValues[ 10 ]{};

        for ( const char* params = args; *params; params++ )
        {
            switch ( *params )
            {
            case 'F':
                boolValues[ boolCount++ ] = false;
                break;
            case 'T':
                boolValues[ boolCount++ ] = true;
                break;
            case 'i':
                intValues[ intCount++ ] = osc.Int();
                break;
            case 'f':
                floatValues[ floatCount++ ] = osc.Float();
                break;
            case 's':
                stringValues[ stringCount++ ] = osc.String();
                break;
            default:
                break;
            }
        }

        if ( !strcmp( deviceType, "Mouse" ) && vp )
        {
            if ( !strcmp( id, "Position" ) )
            {
                int x = floatValues[ 0 ] * viewportSize.X;
                int y = floatValues[ 1 ] * viewportSize.Y;

                FIntPoint mappedPoint = vp->GetGameViewport()->ViewportToVirtualDesktopPixel(FVector2D(0, 0));

                FIntPoint p = mappedPoint + FIntPoint( x, y );

                FVector2D oldPos = WindowsApplication->Cursor->GetPosition();

                WindowsApplication->Cursor->SetPosition( p.X, p.Y );
                MessageHandler->OnRawMouseMove( p.X - oldPos.X, p.Y - oldPos.Y );

                //UE_LOG(LogTemp, Warning, TEXT("OSC Move event: %d %d (%d %d) %f %f (%f %f) - %d %d"), p.X, p.Y, x, y, viewportSize.X, viewportSize.Y, oldPos.X, oldPos.Y, mappedPoint.X, mappedPoint.Y);

                continue;
            }

            if ( !strcmp( id, "IsInside" ) )
            {
                if ( boolValues[ 0 ] )
                {
                    FSlateApplication::Get().OverridePlatformApplication( appWrapper );
                    FSlateApplication::Get().OnCursorSet();

                    // Make sure the viewport is active.
                    FSlateApplication::Get().ProcessApplicationActivationEvent( true );
                    WindowsApplication = appWrapper.Get();
                }
                else
                {
                    FSlateApplication::Get().OverridePlatformApplication( appWrapper->WrappedApplication );
                    WindowsApplication = appWrapper->WrappedApplication.Get();
                }

                continue;
            }

            if ( !strcmp( id, "Hover" ) )
            {
                continue;
            }

            if ( !strcmp( id, "ButtonDown" ) )
            {
                if ( !FSlateApplication::Get().IsActive() )
                {
                    FSlateApplication::Get().ProcessApplicationActivationEvent( true );
                }

                int button = intValues[ 0 ];
                if ( button & 0x100000 )
                    MessageHandler->OnMouseDown( GEngine->GameViewport->GetWindow()->GetNativeWindow(), EMouseButtons::Left );
                if ( button & 0x200000 )
                    MessageHandler->OnMouseDown( GEngine->GameViewport->GetWindow()->GetNativeWindow(), EMouseButtons::Right );
                if ( button & 0x400000 )
                    MessageHandler->OnMouseDown( GEngine->GameViewport->GetWindow()->GetNativeWindow(), EMouseButtons::Middle );
                if ( button & 0x800000 )
                    MessageHandler->OnMouseDown( GEngine->GameViewport->GetWindow()->GetNativeWindow(), EMouseButtons::Thumb01 );
                if ( button & 0x1000000 )
                    MessageHandler->OnMouseDown( GEngine->GameViewport->GetWindow()->GetNativeWindow(), EMouseButtons::Thumb02 );

                /*int x = floatValues[0] * viewportSize.X;
                int y = floatValues[1] * viewportSize.Y;

                FIntPoint p = vp->GetGameViewport()->ViewportToVirtualDesktopPixel(FVector2D()) + FIntPoint(x, y);


                UE_LOG(LogTemp, Warning, TEXT("OSC Click event: %x @ %d %d %f %f"), button, p.X, p.Y, viewportSize.X, viewportSize.Y);*/


                continue;
            }

            if ( !strcmp( id, "ButtonUp" ) )
            {
                int button = intValues[ 0 ];
                if ( button & 0x100000 )
                    MessageHandler->OnMouseUp( EMouseButtons::Left );
                if ( button & 0x200000 )
                    MessageHandler->OnMouseUp( EMouseButtons::Right );
                if ( button & 0x400000 )
                    MessageHandler->OnMouseUp( EMouseButtons::Middle );
                if ( button & 0x800000 )
                    MessageHandler->OnMouseUp( EMouseButtons::Thumb01 );
                if ( button & 0x1000000 )
                    MessageHandler->OnMouseUp( EMouseButtons::Thumb02 );
                continue;
            }

            if ( !strcmp( id, "DoubleClick" ) || !strcmp( id, "DoubleClick2" ) )
            {
                int button = 0x100000;

                if ( !strcmp( id, "DoubleClick2" ) )
                    button = intValues[ 0 ];

                if ( button & 0x100000 )
                    MessageHandler->OnMouseDoubleClick( GEngine->GameViewport->GetWindow()->GetNativeWindow(), EMouseButtons::Left );
                if ( button & 0x200000 )
                    MessageHandler->OnMouseDoubleClick( GEngine->GameViewport->GetWindow()->GetNativeWindow(), EMouseButtons::Right );
                if ( button & 0x400000 )
                    MessageHandler->OnMouseDoubleClick( GEngine->GameViewport->GetWindow()->GetNativeWindow(), EMouseButtons::Middle );
                if ( button & 0x800000 )
                    MessageHandler->OnMouseDoubleClick( GEngine->GameViewport->GetWindow()->GetNativeWindow(), EMouseButtons::Thumb01 );
                if ( button & 0x1000000 )
                    MessageHandler->OnMouseDoubleClick( GEngine->GameViewport->GetWindow()->GetNativeWindow(), EMouseButtons::Thumb02 );
                continue;
            }

            if ( !strcmp( id, "Wheel" ) )
            {
                const float SpinFactor = 1 / 120.0f;
                MessageHandler->OnMouseWheel( intValues[ 0 ] * SpinFactor );
                continue;
            }

            //UE_LOG(LogTemp, Warning, TEXT("Unhandled OSC event: %s %s %s %s %d %d | %f %f %f | %d %d %d"), *FString(addr), *FString(args), *FString(deviceType), *FString(id), deviceID, touchGroup, floatValues[0], floatValues[1], floatValues[2], intValues[0], intValues[1], intValues[2]);

            continue;
        }

        if ( !strcmp( deviceType, "Keyboard" ) )
        {
            int key = intValues[ 0 ];

            if ( !strcmp( id, "KeyDown" ) )
            {
                uint32 CharCode = ::MapVirtualKey( key & 0xffff, MAPVK_VK_TO_CHAR );
                //UE_LOG(LogTemp, Warning, TEXT("OSC keydown event: %d (%d)"), key, CharCode);
                MessageHandler->OnKeyDown( key, CharCode, false );
                continue;
            }
            if ( !strcmp( id, "KeyUp" ) )
            {
                uint32 CharCode = ::MapVirtualKey( key & 0xffff, MAPVK_VK_TO_CHAR );
                //UE_LOG(LogTemp, Warning, TEXT("OSC keyup event: %d (%d)"), key, CharCode);
                MessageHandler->OnKeyUp( key, CharCode, false );
                continue;
            }
            if ( !strcmp( id, "KeyPress" ) )
            {
                //UE_LOG(LogTemp, Warning, TEXT("OSC keypress event: %d"), key);
                MessageHandler->OnKeyChar( key, false );
                continue;
            }

            continue;
        }

        if ( !strcmp( deviceType, "MultiTouch" ) )
        {
            int touchType = intValues[ 0 ];
            char* touchId = stringValues[ 0 ];
            float x = floatValues[ 0 ];
            float y = floatValues[ 1 ];
            float z = floatValues[ 2 ];
            float pressure = floatValues[ 3 ];
            float angle = floatValues[ 4 ];

            int touchIDint = 0;

            if ( sscanf_s( touchId, "%d", &touchIDint ) != 1 )
                continue;

            FVector2D cursorPos = FVector2D( vp->GetGameViewport()->ViewportToVirtualDesktopPixel( FVector2D() ) ) + FVector2D( floatValues[ 0 ] * viewportSize.X, floatValues[ 1 ] * viewportSize.Y );

            if ( !strcmp( id, "TouchAdd" ) )
            {
                int touchIndex = -1;
                for ( int i = 0; i < 10; i++ )
                    if ( touchIndexMap[ i ] == -1 )
                    {
                        touchIndexMap[ i ] = touchIDint;
                        touchIndex = i;
                        break;
                    }

                if ( touchIndex == -1 )
                    continue;

                MessageHandler->OnTouchStarted( GEngine->GameViewport->GetWindow()->GetNativeWindow(), cursorPos, pressure, touchIndex, 0 );
                continue;
            }

            if ( !strcmp( id, "TouchMove" ) )
            {
                int touchIndex = -1;
                for ( int i = 0; i < 10; i++ )
                    if ( touchIndexMap[ i ] == touchIDint )
                    {
                        touchIndex = i;
                        break;
                    }

                if ( touchIndex == -1 )
                    continue;

                MessageHandler->OnTouchMoved( cursorPos, pressure, touchIndex, 0 );
                continue;
            }

            if ( !strcmp( id, "TouchRemove" ) )
            {
                int touchIndex = -1;
                for ( int i = 0; i < 10; i++ )
                    if ( touchIndexMap[ i ] == touchIDint )
                    {
                        touchIndexMap[ i ] = -1;
                        touchIndex = i;
                        break;
                    }

                if ( touchIndex == -1 )
                    continue;

                MessageHandler->OnTouchEnded( cursorPos, touchIndex, 0 );
                continue;
            }

            //UE_LOG(LogTemp, Warning, TEXT("OSC event: %s %s %s %s | type: %d id: %s x: %f y: %f z: %f pressure: %f angle: %f"), *FString(addr), *FString(args), *FString(deviceType), *FString(id), intValues[0], *FString(stringValues[0]), floatValues[0], floatValues[1], floatValues[2], floatValues[3], floatValues[4]);
            continue;
        }


    } while ( osc.Scan < osc.End );

}

void FVentuzInputDevice::ResetOriginalApplication()
{
    FSlateApplication::Get().OverridePlatformApplication(appWrapper->WrappedApplication);
}

void FVentuzInputDevice::Tick( float deltaTime )
{
}

void FVentuzInputDevice::SetMessageHandler( const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler )
{
    MessageHandler = InMessageHandler;
}

void FVentuzPlugin::OnPostWorldCreation( UWorld* world )
{
    client.ResetStoredValues();
    eventCallbacks.Reset();
}

FVentuzPlugin::FVentuzPlugin()
{
}

void FVentuzPlugin::Tick( float deltaTime )
{
    TRACE_CPUPROFILER_EVENT_SCOPE( TEXT( "FVentuzPlugin::Tick" ) );
    SetSyncEvent(Ventuz::UE4::SyncEvent::UnrealTickPlugin);

    if ( client.IsConnected() )
        client.SendDataToVentuz();
}

void FVentuzPlugin::OnBeginFrame()
{
    TRACE_CPUPROFILER_EVENT_SCOPE( TEXT( "FVentuzPlugin::OnBeginFrame" ) );
    SetSyncEvent(Ventuz::UE4::SyncEvent::UnrealBeginFrame);

    if ( GEngine && GEngine->GameViewport && GEngine->GameViewport->GetWorld() )
    {
        GEngine->LocalPlayerClassName = FSoftClassPath("/Script/VentuzPlugin.VentuzLocalPlayer");
        DisableEditorCPUThrottling();

        TWeakPtr<FSceneViewport> SceneViewport;
        FindSceneViewport(SceneViewport);
        if (SceneViewport.IsValid())
        {
            FindOrAddDisplayConfiguration(SceneViewport.Pin()->GetViewport());
        }

        SetGameRunning( GEngine->GameViewport->GetWorld()->IsGameWorld() );
    }
    else
    {
        DisplayExtensions.Reset();
        SetGameRunning( false );
    }

    if ( InputDevice && ( !GEngine || !GEngine->GameViewport ) )
    {
        InputDevice->ResetOriginalApplication();
    }

    if ( !client.IsConnected() )
    {
        auto version = FEngineVersion::Current();
        //client.SendUnrealVersion( version.GetMajor(), version.GetMinor(), version.GetPatch() );
        client.SetUnrealVersion( version.GetMajor(), version.GetMinor(), version.GetPatch() );

    }

    Ventuz::UE4::ConnectionError error = client.UpdateConnection( ventuzLaunchID, isLiveLink );

    if ( error != Ventuz::UE4::ConnectionError::OK )
    {
        textureSharesInSync = false;
        return;
    }

    if ( client.IsConnected() )
    {
        if ( !textureSharesInSync )
        {
            SendTextureShareList();
            textureSharesInSync = true;
        }

        {
            TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::TickANC"));
            TickAnc();
        }
        ProcessVentuzInputs();
        client.ProcessEventsFromVentuz( [ this ]( uint eventHash, uint argument )
                                        {
                                            TArray<FVentuzEventCallback*> callbacks;
                                            eventCallbacks.MultiFindPointer( eventHash, callbacks );
                                            for ( auto callback : callbacks )
                                            {
                                                callback->ExecuteIfBound( argument );
                                            }
                                        } );

        if ( GEngine && GEngine->GameViewport )
        {
            int width;
            int height;
            if ( client.HasResolutionChanged( width, height ) )
            {
                EWindowMode::Type WindowMode = GEngine->GameViewport->GetGameViewport()->GetWindowMode();
                FSystemResolution::RequestResolutionChange( width, height, WindowMode );

#if WITH_EDITOR
                if (GIsEditor && GEditor->PlayWorld && !GIsPlayInEditorWorld)
                {
                    UWorld* OldWorld = SetPlayInEditorWorld(GEditor->PlayWorld);

                    ULocalPlayer* Player = GEngine->GetDebugLocalPlayer();
                    if (Player && Player->ViewportClient && Player->ViewportClient->ViewportFrame)
                        Player->ViewportClient->ViewportFrame->ResizeFrame(width + 10, height + 34, WindowMode); // HACK: added values are based on the default 4.26 skin...

                    if (OldWorld)
                        RestoreEditorWorld(OldWorld);
                }
#endif
            }
        }
    }
    else
        textureSharesInSync = false;

    // update aspect ratio even when camera matrices haven't been updated
    if (GEngine && GEngine->GameViewport && GEngine->GameViewport->GetWorld())
    {
        auto* playerController = UGameplayStatics::GetPlayerController(GEngine->GameViewport->GetWorld(), 0);

        if (playerController)
        {
            playerController->DisableComponentsSimulatePhysics();
            if (playerController->GetPawn())
                playerController->GetPawn()->DisableComponentsSimulatePhysics();

            auto* viewTarget = playerController->GetViewTarget();
            if (playerController->PlayerCameraManager && client.IsConnected())
            {
                float aspect = GetVentuzCameraAspect();

                playerController->PlayerCameraManager->DefaultAspectRatio = aspect;
                playerController->PlayerCameraManager->bDefaultConstrainAspectRatio = true;

            }
        }
    }

    if ( client.sharedData.matricesUpdated )
    {
        client.sharedData.matricesUpdated = false;

        FTransform transform = GetVentuzCameraTransform();

        if ( GEngine && GEngine->GameViewport && GEngine->GameViewport->GetWorld() )
        {
            auto* playerController = UGameplayStatics::GetPlayerController( GEngine->GameViewport->GetWorld(), 0 );

            if ( playerController )
            {
                auto* viewTarget = playerController->GetViewTarget();
                if ( playerController->PlayerCameraManager )
                {
                    float fov = GetVentuzCameraFov();

                    playerController->PlayerCameraManager->bIsOrthographic = IsVentuzCameraOrtho();
                    playerController->PlayerCameraManager->ViewPitchMin = -179.9999;
                    playerController->PlayerCameraManager->ViewPitchMax = 180;
                    playerController->PlayerCameraManager->ViewYawMin = 0;
                    playerController->PlayerCameraManager->ViewYawMax = 359.9999;
                    playerController->PlayerCameraManager->ViewRollMin = -179.9999;
                    playerController->PlayerCameraManager->ViewRollMax = 180;
                    playerController->PlayerCameraManager->SetFOV( fov );
                }

                TInlineComponentArray<UCameraComponent*> Cameras;
                viewTarget->GetComponents<UCameraComponent>(/*out*/ Cameras );

                for ( UCameraComponent* CameraComponent : Cameras )
                {
                    if ( CameraComponent->IsActive() )
                    {
                        CameraComponent->SetWorldTransform( transform );
                    }
                }

                if ( viewTarget->IsA<APawn>() )
                {
                    auto* pawn = CastChecked<APawn>( viewTarget );
                    if ( pawn->Controller )
                        pawn->Controller->ClientSetRotation( transform.Rotator() );
                    pawn->BaseEyeHeight = 0;
                    pawn->SetActorLocation( transform.GetLocation() );
                    viewTarget->SetActorLocation( transform.GetLocation() );
                }
                else
                    viewTarget->SetActorTransform( transform );
            }
        }
    }

    // sync render thread

/*
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::GetDataFromVentuz::FFrameEndSync"));
        static FFrameEndSync sync;
        sync.Sync( false );
    }
*/

}

void FVentuzPlugin::OnBeginFrameRT()
{
    TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::OnBeginFrameRT"));
    SetSyncEvent(Ventuz::UE4::SyncEvent::UnrealBeginFrameRT);

/*
    ENQUEUE_RENDER_COMMAND(GetDepthViewportCommand)([](FRHICommandListImmediate& RHICmdList)
    {
        auto renderTargets = FSceneRenderTargets::Get(RHICmdList);
        RHICmdList.SetViewport()
        auto size = renderTargets.GetBufferSizeXY();
        UE_LOG(LogTemp, Warning, TEXT("Buffer size: %d %d "), size.X, size.Y);
    });
*/

    //FSceneRenderTargets::Get(RHICmdList).AdjustGBufferRefCount(RHICmdList, 1);
}

void FVentuzPlugin::OnEnginePreExit()
{
    TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::OnBeginPreExit"));
    if ( client.IsConnected() )
        client.Disconnect();
}
#if ENGINE_MAJOR_VERSION >= 5 
void FVentuzPlugin::OnSceneColorResolved( FRDGBuilder& GraphBuilder, const FSceneTextures& SceneTextures )
#else
void FVentuzPlugin::OnSceneColorResolved(FRHICommandListImmediate& RHICmdList, class FSceneRenderTargets& SceneContext)
#endif
{
    TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::OnSceneColorResolved"));

    SetSyncEvent(Ventuz::UE4::SyncEvent::UnrealOnSceneColorResolved);
}

void FVentuzPlugin::ProcessVentuzInputs()
{
    if (!client.IsConnected())
        return;

    auto bundles = client.GetInputBundles();

    if (!isGameRunning)
        return;

    for (auto& bundle : bundles)
    {
        if (InputDevice)
            InputDevice->ParseOSCBundle(bundle);
    }
}

void FVentuzPlugin::SendTextureShareList()
{
    std::vector<std::string> list;
    for ( auto& shareName : textureShares )
        list.emplace_back( std::string( TCHAR_TO_UTF8( *shareName ) ) );

    if ( client.IsConnected() )
        client.SendTextureShareList( list );
}

void FVentuzPlugin::SetGameRunning( bool isRunning )
{
    if (client.IsConnected())
        client.SetRunStatus(isRunning);

    if ( isRunning != isGameRunning )
    {
        client.MarkResolutionDirty();

        if (isRunning)
        {
            defaultQualitySettings = Scalability::GetQualityLevels();
            SetRenderQuality(renderQuality);
            ForceFXAA();

            bool isSimulating = false;

#if WITH_EDITOR
            if (UEditorEngine * editor = Cast<UEditorEngine>(GEngine))
                isSimulating = editor->bIsSimulatingInEditor;
#endif
            if (!isSimulating)
                UVentuzBPFunctionLibrary::CreateVentuzTextureShare();
        }
        else
        {
            Scalability::SetQualityLevels( defaultQualitySettings );
            UVentuzBPFunctionLibrary::DestroyVentuzTextureShare();
        }
    }

    isGameRunning = isRunning;
}

void FVentuzPlugin::SetRenderQuality(Ventuz::UE4::RenderQuality quality)
{
    Scalability::FQualityLevels newQuality;

    switch (quality)
    {
    case Ventuz::UE4::RenderQuality::Default:
        newQuality = defaultQualitySettings;
        break;
    case Ventuz::UE4::RenderQuality::Low:
        newQuality.SetFromSingleQualityLevel(0);
        break;
    case Ventuz::UE4::RenderQuality::Medium:
        newQuality.SetFromSingleQualityLevel(1);
        break;
    case Ventuz::UE4::RenderQuality::High:
        newQuality.SetFromSingleQualityLevel(2);
        break;
    case Ventuz::UE4::RenderQuality::Epic:
        newQuality.SetFromSingleQualityLevel(3);
        break;
    case Ventuz::UE4::RenderQuality::Cinematic:
        newQuality.SetFromSingleQualityLevel(4);
        break;
    default:
        newQuality.SetFromSingleQualityLevel(3);
        break;
    }

    if (isGameRunning)
        Scalability::SetQualityLevels(newQuality);

    renderQuality = quality;
}

void FVentuzPlugin::DisableEditorCPUThrottling()
{
#if WITH_EDITOR
    UEditorPerformanceSettings* Settings = GetMutableDefault<UEditorPerformanceSettings>();
    check(Settings);
    Settings->bThrottleCPUWhenNotForeground = false;
    Settings->PostEditChange();
    Settings->SaveConfig();
#endif
}

void FVentuzPlugin::ForceFXAA()
{
    if (!GEngine)
        return;
    UGameUserSettings* UserSettings = GEngine->GetGameUserSettings();
    UserSettings->SetAntiAliasingQuality(1);
    UserSettings->ApplySettings(false);
}

/*
unsigned int FVentuzPlugin::GetCurrentFrameViaHack()
{
    GUID connectionID = GetVentuzLaunchID();

    char guidText[ 256 ];
    sprintf_s( guidText, "%x%x%x%x%x_\0", (uint32_t)connectionID.Data1, (uint32_t)connectionID.Data2, (uint32_t)connectionID.Data3, *( (uint32_t*)&connectionID.Data4[ 0 ] ), *( (uint32_t*)&connectionID.Data4[ 4 ] ) );
    FString realShareName = FString( guidText );

    static ITextureShare& TextureShareAPI = ITextureShare::Get();

    TSharedPtr<ITextureShareItem> shareObject;

    if ( TextureShareAPI.GetShare( realShareName, shareObject ) )
        return *(unsigned int*)( ( (unsigned char*)shareObject.Get() + 96 ) + 3232 );

    return 0;
}
*/

bool FVentuzPlugin::IsTickableWhenPaused() const
{
    return true;
}

bool FVentuzPlugin::IsTickableInEditor() const
{
    return true;
}

TStatId FVentuzPlugin::GetStatId() const
{
    RETURN_QUICK_DECLARE_CYCLE_STAT( FVentuzPlugin, STATGROUP_Tickables );
}

void FVentuzPlugin::StartupModule()
{
    // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module

    IModularFeatures::Get().RegisterModularFeature( GetModularFeatureName(), this );
    FWorldDelegates::OnPostWorldCreation.AddRaw( this, &FVentuzPlugin::OnPostWorldCreation );

    FString commandLine = FString( GetCommandLineA() );
    TArray<FString> tokens;
    commandLine.ParseIntoArrayWS( tokens );

    for ( int x = 0; x < tokens.Num(); x++ )
    {
        if ( tokens[ x ].Find( TEXT( "-VentuzLaunchID=" ) ) == 0 )
        {
            FGuid cmdLineGuid = FGuid( tokens[ x ].Mid( 16 ) ); // 11 = length of "-VentuzLaunchID="
            ventuzLaunchID.Data1 = cmdLineGuid.A;
            ventuzLaunchID.Data2 = ( cmdLineGuid.B >> 16 ) & 0xffff;
            ventuzLaunchID.Data3 = cmdLineGuid.B & 0xffff;
            ventuzLaunchID.Data4[ 3 ] = ( cmdLineGuid.C ) & 0xff;
            ventuzLaunchID.Data4[ 2 ] = ( cmdLineGuid.C >> 8 ) & 0xff;
            ventuzLaunchID.Data4[ 1 ] = ( cmdLineGuid.C >> 16 ) & 0xff;
            ventuzLaunchID.Data4[ 0 ] = ( cmdLineGuid.C >> 24 ) & 0xff;
            ventuzLaunchID.Data4[ 7 ] = ( cmdLineGuid.D ) & 0xff;
            ventuzLaunchID.Data4[ 6 ] = ( cmdLineGuid.D >> 8 ) & 0xff;
            ventuzLaunchID.Data4[ 5 ] = ( cmdLineGuid.D >> 16 ) & 0xff;
            ventuzLaunchID.Data4[ 4 ] = ( cmdLineGuid.D >> 24 ) & 0xff;

            isLiveLink = false;
        }
        else
        {
            CoCreateGuid( &ventuzLaunchID );
            isLiveLink = true;
        }
    }

    FCoreDelegates::OnBeginFrame.AddRaw( this, &FVentuzPlugin::OnBeginFrame );
    FCoreDelegates::OnBeginFrameRT.AddRaw(this, &FVentuzPlugin::OnBeginFrameRT );
    FCoreDelegates::OnEnginePreExit.AddRaw( this, &FVentuzPlugin::OnEnginePreExit );

    static const FName RendererModuleName(TEXT("Renderer"));
    IRendererModule* RendererModule = FModuleManager::GetModulePtr<IRendererModule>(RendererModuleName);
    if (RendererModule)
        RendererModule->GetResolvedSceneColorCallbacks().AddRaw(this, &FVentuzPlugin::OnSceneColorResolved );

    DisableEditorCPUThrottling();
    ForceFXAA();

}

void FVentuzPlugin::ShutdownModule()
{
    // This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
    // we call this function before unloading the module.

    if ( client.IsConnected() )
        client.Disconnect();

    IModularFeatures::Get().UnregisterModularFeature( IInputDeviceModule::GetModularFeatureName(), this );

    FWorldDelegates::OnPostWorldCreation.RemoveAll( this );

    FCoreDelegates::OnBeginFrame.RemoveAll( this );
    FCoreDelegates::OnBeginFrameRT.RemoveAll(this);
    FCoreDelegates::OnEnginePreExit.RemoveAll( this );

    static const FName RendererModuleName(TEXT("Renderer"));
    IRendererModule* RendererModule = FModuleManager::GetModulePtr<IRendererModule>(RendererModuleName);
    if (RendererModule)
        RendererModule->GetResolvedSceneColorCallbacks().RemoveAll(this);

}

TSharedPtr<class IInputDevice> FVentuzPlugin::CreateInputDevice( const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler )
{
    InputDevice = MakeShareable( new FVentuzInputDevice( InMessageHandler ) );
    return InputDevice;
}

void FVentuzPlugin::RegisterEvent( uint eventNameHash, const FVentuzEventCallback& event )
{
    eventCallbacks.Add( eventNameHash, event );
}

void FVentuzPlugin::TriggerEvent( uint eventNameHash, int argument )
{
    if ( client.IsConnected() )
        client.TriggerEvent( eventNameHash, argument );
}

void FVentuzPlugin::TickAnc()
{
    if ( client.UpdateConnection( ventuzLaunchID, isLiveLink ) != Ventuz::UE4::ConnectionError::OK )
        return;

    if ( !client.IsConnected() )
        return;

    client.ParseANC( []( void* targetObject, const std::vector<Ventuz::UE4::FieldDescriptor>& fieldList )
                     {
                         ( (IVentuzNodeFieldSync*)targetObject )->UpdateNodeFields( fieldList );
                     }, fromVentuzFieldListRequests,
                     [this](Ventuz::UE4::RenderQuality quality)
                     {
                         SetRenderQuality(quality);
                     });
}

FTransform FVentuzPlugin::GetVentuzCameraTransform()
{
    FMatrix view;
    for ( int x = 0; x < 16; x++ )
        view.M[ x / 4 ][ x % 4 ] = client.sharedData.cameraMatrix[ x ];

    // camera's world matrix
    FMatrix camWorld = view.Inverse();

    // this matrix transforms the Ventuz camera space to UE camera space

    FMatrix adjustMatrix;
    adjustMatrix.SetIdentity();
    adjustMatrix.M[ 1 ][ 1 ] = 0;
    adjustMatrix.M[ 2 ][ 2 ] = 0;
    adjustMatrix.M[ 1 ][ 2 ] = 1;
    adjustMatrix.M[ 2 ][ 1 ] = -1;

    FMatrix vtzToUE = adjustMatrix * FScaleMatrix( 100.f );
    camWorld = camWorld * vtzToUE;

    FVector xAxis_Right;
    FVector yAxis_Up;
    FVector zAxis_forward;
    camWorld.GetUnitAxes( xAxis_Right, yAxis_Up, zAxis_forward );
    auto eye = camWorld.GetOrigin();

    FMatrix rotation( zAxis_forward, xAxis_Right, yAxis_Up, FVector::ZeroVector );
    return FTransform( rotation.Rotator(), eye, FVector::OneVector );
}

float FVentuzPlugin::GetVentuzCameraFov()
{
    return atan( 1.0f / client.sharedData.projectionMatrix[ 0 ] ) * 2.0 * 180.0 / PI;
}

float FVentuzPlugin::GetVentuzCameraAspect()
{
    return client.GetCameraAspect();
}

FMatrix FVentuzPlugin::GetVentuzProjectionMatrix()
{
    FMatrix result;
    for (int x = 0; x < 4; x++)
        for (int y = 0; y < 4; y++)
            result.M[x][y] = client.sharedData.projectionMatrix[x * 4 + y];
    return result;
}

bool FVentuzPlugin::IsVentuzCameraOrtho()
{
    return client.sharedData.projectionMatrix[15] == 1;
}

FVector2D FVentuzPlugin::GetVentuzProjectionOffset()
{
    return FVector2D( client.sharedData.projectionMatrix[ 8 ], client.sharedData.projectionMatrix[ 9 ] );
}

int FVentuzPlugin::GetIntValue( uint hash, bool& isAvailable )
{
    isAvailable = client.sharedData.intValues.find( hash ) != client.sharedData.intValues.end();
    if ( !isAvailable )
        return 0;

    return client.sharedData.intValues[ hash ];
}

float FVentuzPlugin::GetFloatValue( uint hash, bool& isAvailable )
{
    isAvailable = client.sharedData.floatValues.find( hash ) != client.sharedData.floatValues.end();
    if ( !isAvailable )
        return 0;

    return client.sharedData.floatValues[ hash ];
}

FString FVentuzPlugin::GetStringValue( uint hash, bool& isAvailable )
{
    isAvailable = client.sharedData.stringValues.find( hash ) != client.sharedData.stringValues.end();
    if ( !isAvailable )
        return FString();

    std::string& string = client.sharedData.stringValues[ hash ];
    return FString( UTF8_TO_TCHAR( string.data() ) );
}

bool FVentuzPlugin::GetBoolValue( uint hash, bool& isAvailable )
{
    isAvailable = client.sharedData.boolValues.find( hash ) != client.sharedData.boolValues.end();
    if ( !isAvailable )
        return 0;

    return client.sharedData.boolValues[ hash ];
}

TArray<int> FVentuzPlugin::GetIntArray( uint hash, bool& isAvailable )
{
    TArray<int> result;

    auto findResult = client.sharedData.intArrays.find( hash );
    isAvailable = findResult != client.sharedData.intArrays.end();

    if ( !isAvailable )
        return result;

    for ( int x = 0; x < findResult->second.size(); x++ )
        result.Add( findResult->second[ x ] );

    return result;
}

TArray<float> FVentuzPlugin::GetFloatArray( uint hash, bool& isAvailable )
{
    TArray<float> result;

    auto findResult = client.sharedData.floatArrays.find( hash );
    isAvailable = findResult != client.sharedData.floatArrays.end();

    if ( !isAvailable )
        return result;

    for ( int x = 0; x < findResult->second.size(); x++ )
        result.Add( findResult->second[ x ] );

    return result;
}

TArray<FString> FVentuzPlugin::GetStringArray( uint hash, bool& isAvailable )
{
    TArray<FString> result;

    auto findResult = client.sharedData.stringArrays.find( hash );
    isAvailable = findResult != client.sharedData.stringArrays.end();

    if ( !isAvailable )
        return result;

    int length = findResult->second.length();
    auto* strings = findResult->second.c_str();
    auto* end = strings + length;
    while ( strings < end )
    {
        result.Add( UTF16_TO_TCHAR( strings ) );
        while ( *strings )
            strings++;
        strings++;
    }

    return result;
}

TArray<bool> FVentuzPlugin::GetBoolArray( uint hash, bool& isAvailable )
{
    TArray<bool> result;

    auto findResult = client.sharedData.boolArrays.find( hash );
    isAvailable = findResult != client.sharedData.boolArrays.end();

    if ( !isAvailable )
        return result;

    for ( int x = 0; x < findResult->second.size(); x++ )
        result.Add( findResult->second[ x ] != 0 );

    return result;
}

FColor FVentuzPlugin::GetColorValue( uint hash, bool& isAvailable )
{
    isAvailable = client.sharedData.colorValues.find( hash ) != client.sharedData.colorValues.end();
    if ( !isAvailable )
        return FColor();

    auto color = client.sharedData.colorValues[ hash ];

    return FColor( color );
}

FTransform FVentuzPlugin::GetTransformValue( uint hash, bool& isAvailable )
{
    isAvailable = client.sharedData.transformValues.find( hash ) != client.sharedData.transformValues.end();
    if ( !isAvailable )
        return FTransform();

    FMatrix matrix;
    memcpy( matrix.M, client.sharedData.transformValues[ hash ], 16 * sizeof( float ) );
    return FTransform( matrix );
}

void FVentuzPlugin::RegisterTextureShare( const FString& textureShareName )
{
    bool bIsAlreadyInSetPtr = false;
    textureShares.Add( textureShareName.ToLower(), &bIsAlreadyInSetPtr );

    if ( !bIsAlreadyInSetPtr )
        SendTextureShareList();
}

void FVentuzPlugin::UnRegisterTextureShare( const FString& textureShareName )
{
    if ( textureShares.Remove( textureShareName.ToLower() ) > 0 )
        SendTextureShareList();
}

void FVentuzPlugin::SendFloatValue( uint hash, const float& value )
{
    if ( client.IsConnected() )
        client.SendFloatValue( hash, value );
}

void FVentuzPlugin::SendIntValue( uint hash, const int& value )
{
    if ( client.IsConnected() )
        client.SendIntValue( hash, value );
}

void FVentuzPlugin::SendStringValue( uint hash, const FString& value )
{
    auto utf8String = std::string( TCHAR_TO_UTF8( *value ) );
    if ( client.IsConnected() )
        client.SendStringValue( hash, utf8String );
}

void FVentuzPlugin::SendBoolValue( uint hash, const bool value )
{
    if ( client.IsConnected() )
        client.SendBoolValue( hash, value );
}

void FVentuzPlugin::SendUnrealVersion( int major, int minor, int patch )
{
    client.SendUnrealVersion( major, minor, patch );
}

void FVentuzPlugin::SendIntArray( uint hash, const TArray<int>& array )
{
    if ( client.IsConnected() )
        client.SendIntArray( hash, array.GetData(), array.Num() );
}

void FVentuzPlugin::SendFloatArray( uint hash, const TArray<float>& array )
{
    if ( client.IsConnected() )
        client.SendFloatArray( hash, array.GetData(), array.Num() );
}

void FVentuzPlugin::SendStringArray( uint hash, const TArray<FString>& array )
{
    std::vector<std::string> stringArray;
    for ( int x = 0; x < array.Num(); x++ )
        stringArray.emplace_back( std::string( TCHAR_TO_UTF8( *array[ x ] ) ) );
    if ( client.IsConnected() )
        client.SendStringArray( hash, stringArray );
}

void FVentuzPlugin::SendBoolArray( uint hash, const TArray<bool>& array )
{
    if ( client.IsConnected() )
        client.SendBoolArray( hash, array.GetData(), array.Num() );
}

void FVentuzPlugin::SendTransformValue( uint hash, const FTransform& value )
{
    auto matrix = value.ToMatrixWithScale();
    if ( client.IsConnected() )
        client.SendTransformValue( hash, matrix.M );
}

void FVentuzPlugin::SendFieldList( const GUID& requestID, const std::vector<Ventuz::UE4::FieldDescriptor>& fieldsToSend )
{
    if ( client.IsConnected() )
        client.SendFieldList( requestID, fieldsToSend );
}

void FVentuzPlugin::RequestFieldListUpdate( const FString& nodeName, IVentuzNodeFieldSync* node )
{
    auto requestedName = std::string( TCHAR_TO_UTF8( *nodeName.ToLower() ) );
    if ( client.IsConnected() )
        client.RequestFieldListUpdate( requestedName, node );
}

std::unordered_map<GUID, std::string> FVentuzPlugin::GetFieldListRequests()
{
    auto copy = fromVentuzFieldListRequests;
    fromVentuzFieldListRequests.clear();
    return copy;
}

bool FVentuzPlugin::IsConnected()
{
    return client.IsConnected();
}

Ventuz::UE4::FromVentuzClock FVentuzPlugin::GetClusterClock()
{
    return client.GetClusterClock();
}

GUID FVentuzPlugin::GetVentuzLaunchID()
{
    return ventuzLaunchID;
}

void FVentuzPlugin::SetViewports(int x1, int y1, int x2, int y2, int zWidth, int zHeight)
{
    if (client.IsConnected())
        client.SetViewports(x1,y1,x2,y2,zWidth,zHeight);
}

void FVentuzPlugin::SetAlphaPropagation( int propagation )
{
  if ( client.IsConnected() )
    client.SetAlphaType( propagation == 2 ? 2 : 0 );
}

void FVentuzPlugin::SetSyncEvent(Ventuz::UE4::SyncEvent event)
{
    if (client.IsConnected())
        client.SetSyncEvent(event);
}

void FVentuzPlugin::WaitForEvent(Ventuz::UE4::SyncEvent event, bool reset)
{
    if (client.IsConnected())
        client.WaitForEvent(event, reset);
}

TSharedPtr<FVentuzViewExtension, ESPMode::ThreadSafe> FVentuzPlugin::FindOrAddDisplayConfiguration(FViewport* InViewport)
{
    const TSharedPtr<FVentuzViewExtension, ESPMode::ThreadSafe>* Extension = DisplayExtensions.FindByPredicate([InViewport](const TSharedPtr<FVentuzViewExtension, ESPMode::ThreadSafe>& Other) { return  Other.IsValid() && InViewport == Other->GetAssociatedViewport(); });
    if (Extension)
    {
        return *Extension;
    }

    // Extension not found, create it and return its config
    TSharedPtr<FVentuzViewExtension, ESPMode::ThreadSafe> DisplayExtension = FSceneViewExtensions::NewExtension<FVentuzViewExtension>(InViewport);
    DisplayExtensions.Add(DisplayExtension);
    return DisplayExtension;
}

FVentuzViewExtension::FVentuzViewExtension(const FAutoRegister& AutoRegister, FViewport* AssociatedViewport)
    : LinkedViewport(AssociatedViewport)
{
}

void FVentuzViewExtension::SetupViewFamily(FSceneViewFamily& InViewFamily)
{
    TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::SetupViewFamily"));

    if (!IVentuzPlugin::IsAvailable())
        return;
    FModuleManager::LoadModuleChecked<FVentuzPlugin>("VentuzPlugin").SetSyncEvent(Ventuz::UE4::SyncEvent::UnrealSetupViewFamily);
}

void FVentuzViewExtension::SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView)
{
    TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::SetupView"));

    if (!IVentuzPlugin::IsAvailable())
        return;
    FModuleManager::LoadModuleChecked<FVentuzPlugin>("VentuzPlugin").SetSyncEvent(Ventuz::UE4::SyncEvent::UnrealSetupView);
}

void FVentuzViewExtension::BeginRenderViewFamily(FSceneViewFamily& ViewFamily)
{
    TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::BeginRenderViewFamily"));

    if (!IVentuzPlugin::IsAvailable())
        return;
    FModuleManager::LoadModuleChecked<FVentuzPlugin>("VentuzPlugin").SetSyncEvent(Ventuz::UE4::SyncEvent::UnrealBeginRenderViewFamily);

    if (!ViewFamily.Views.Num())
        return;

    // this is a bunch of code cobbled together from deep in the ue4 rendering pipeline
    // may not be perfect but it's the best we got at the moment to get the zbuffer viewport size

    auto view = ViewFamily.Views[0];

    float PrimaryResolutionFraction = 1.0f;
    float GlobalResolutionFraction = 1.0f;

    if (ViewFamily.EngineShowFlags.ScreenPercentage)
    {
        static const auto ScreenPercentageCVar = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.ScreenPercentage"));

        float GlobalFraction = ScreenPercentageCVar->GetValueOnAnyThread() / 100.0f;
        if (GlobalFraction <= 0.0)
        {
            GlobalFraction = 1.0f;
        }

        // Get global view fraction set by r.ScreenPercentage.
        GlobalResolutionFraction *= GlobalFraction;
    }

    if ( ViewFamily.SupportsScreenPercentage() && ViewFamily.GetScreenPercentageInterface() )
        GlobalResolutionFraction = ViewFamily.GetPrimaryResolutionFractionUpperBound();

    // Compute final resolution fraction.

    PrimaryResolutionFraction *= GlobalResolutionFraction;// * view->FinalPostProcessSettings.ScreenPercentage / 100.0f;

/*
    if ( ViewFamily.SupportsScreenPercentage() )
      PrimaryResolutionFraction *= view->FinalPostProcessSettings.ScreenPercentage / 100.0f;
*/

    
#if ENGINE_MAJOR_VERSION >= 5 
#else
    PrimaryResolutionFraction = FMath::Clamp(
        PrimaryResolutionFraction,
        FSceneViewScreenPercentageConfig::kMinResolutionFraction,
        FSceneViewScreenPercentageConfig::kMaxResolutionFraction);
#endif

    float ResolutionFraction = PrimaryResolutionFraction * ViewFamily.SecondaryViewFraction;

    FIntPoint ViewSize;

    // CeilToInt so tha view size is at least 1x1 if ResolutionFraction == FSceneViewScreenPercentageConfig::kMinResolutionFraction.
    ViewSize.X = FMath::CeilToInt( ( view->UnscaledViewRect.Max.X + view->UnscaledViewRect.Min.X) * ResolutionFraction);
    ViewSize.Y = FMath::CeilToInt( ( view->UnscaledViewRect.Max.Y + view->UnscaledViewRect.Min.Y) * ResolutionFraction);

    if (!IVentuzPlugin::IsAvailable())
        return;
    FModuleManager::LoadModuleChecked<FVentuzPlugin>("VentuzPlugin").SetViewports(view->UnscaledViewRect.Min.X, view->UnscaledViewRect.Min.Y, view->UnscaledViewRect.Max.X, view->UnscaledViewRect.Max.Y,ViewSize.X,ViewSize.Y);

    static const auto AlphaPropagation = IConsoleManager::Get().FindTConsoleVariableDataInt( TEXT( "r.PostProcessing.PropagateAlpha" ) );
    FModuleManager::LoadModuleChecked<FVentuzPlugin>( "VentuzPlugin" ).SetAlphaPropagation( AlphaPropagation->GetValueOnAnyThread() );
}

void FVentuzViewExtension::PreRenderView_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView)
{
    TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::PreRenderView_RT"));

    if (!IVentuzPlugin::IsAvailable())
        return;
    FModuleManager::LoadModuleChecked<FVentuzPlugin>("VentuzPlugin").SetSyncEvent(Ventuz::UE4::SyncEvent::UnrealPreRenderViewRT);
}

void FVentuzViewExtension::PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily)
{
    TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::PreRenderViewFamily_RenderThread"));

    if (!IVentuzPlugin::IsAvailable())
        return;
    FModuleManager::LoadModuleChecked<FVentuzPlugin>("VentuzPlugin").SetSyncEvent(Ventuz::UE4::SyncEvent::UnrealPreRenderViewFamilyRT);
}

void FVentuzViewExtension::PostRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily)
{
    TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FVentuzPlugin::PostRenderViewFamily_RenderThread"));

    if (!IVentuzPlugin::IsAvailable())
        return;
    FModuleManager::LoadModuleChecked<FVentuzPlugin>("VentuzPlugin").SetSyncEvent(Ventuz::UE4::SyncEvent::UnrealPostRenderViewFamilyRT);

    // wait for ventuz endframe

    //FModuleManager::LoadModuleChecked<FVentuzPlugin>("VentuzPlugin").WaitForEvent(Ventuz::UE4::SyncEvent::VentuzBeginFrame, true);
}

bool FVentuzViewExtension::IsActiveThisFrame( const FSceneViewExtensionContext& Context ) const
{
    return ( LinkedViewport == Context.Viewport );
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FVentuzPlugin, VentuzPlugin)
