#pragma once

#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
#endif
#include <dxgi1_2.h>
#include <d3d11.h>
#if PLATFORM_WINDOWS
#include "Windows/HideWindowsPlatformTypes.h"
#endif

#include "SceneViewExtension.h"

#include "RHI.h"
#include "Tickable.h"
#include "Widgets/SWindow.h"
#include "GenericPlatform/GenericApplication.h"
#include "GenericPlatform/ICursor.h"
#include "IInputDeviceModule.h"
#include "UE4ComClient.h"
#include "Scalability.h"

#include "VentuzBPEvent.h"

class IVentuzNodeFieldSync
{

public:

    virtual void UpdateNodeFields( const std::vector<Ventuz::UE4::FieldDescriptor>& fieldList ) = 0;
};

// we don't know what to derive from so we need to wrap the Application interface
class FVentuzCursor : public ICursor
{
public:
    FVentuzCursor() { Position = FVector2D(0, 0); }
    virtual ~FVentuzCursor() = default;
    virtual FVector2D GetPosition() const override { return Position; }
    virtual void SetPosition(const int32 X, const int32 Y) override { Position = FVector2D(X, Y); };
    virtual void SetType(const EMouseCursor::Type InNewCursor) override {};
    virtual EMouseCursor::Type GetType() const override { return EMouseCursor::Type::Default; };
    virtual void GetSize(int32& Width, int32& Height) const override {};
    virtual void Show(bool bShow) override {};
    virtual void Lock(const RECT* const Bounds) override {};
    virtual void SetTypeShape(EMouseCursor::Type InCursorType, void* CursorHandle) override {};

private:
    /** The cursor position sent across with mouse events. */
    FVector2D Position;
};

class FVentuzApplicationWrapper : public GenericApplication
{
public:
    FVentuzApplicationWrapper(TSharedPtr<GenericApplication> InWrappedApplication)
        : GenericApplication(MakeShareable(new FVentuzCursor()))
        , WrappedApplication(InWrappedApplication)
    {
    }

    /**
     * Functions passed directly to the wrapped application.
     */

    virtual void SetMessageHandler(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler) { WrappedApplication->SetMessageHandler(InMessageHandler); }
    virtual void PollGameDeviceState(const float TimeDelta) { WrappedApplication->PollGameDeviceState(TimeDelta); }
    virtual void PumpMessages(const float TimeDelta) { WrappedApplication->PumpMessages(TimeDelta); }
    virtual void ProcessDeferredEvents(const float TimeDelta) { WrappedApplication->ProcessDeferredEvents(TimeDelta); }
    virtual void Tick(const float TimeDelta) { WrappedApplication->Tick(TimeDelta); }
    virtual TSharedRef< FGenericWindow > MakeWindow() { return WrappedApplication->MakeWindow(); }
    virtual void InitializeWindow(const TSharedRef< FGenericWindow >& Window, const TSharedRef< FGenericWindowDefinition >& InDefinition, const TSharedPtr< FGenericWindow >& InParent, const bool bShowImmediately) { WrappedApplication->InitializeWindow(Window, InDefinition, InParent, bShowImmediately); }
    virtual void SetCapture(const TSharedPtr< FGenericWindow >& InWindow) { WrappedApplication->SetCapture(InWindow); }
    virtual void* GetCapture(void) const { return WrappedApplication->GetCapture(); }
    virtual FModifierKeysState GetModifierKeys() const { return WrappedApplication->GetModifierKeys(); }
    virtual TSharedPtr< FGenericWindow > GetWindowUnderCursor() { return WrappedApplication->GetWindowUnderCursor(); }
    virtual void SetHighPrecisionMouseMode(const bool Enable, const TSharedPtr< FGenericWindow >& InWindow) { WrappedApplication->SetHighPrecisionMouseMode(Enable, InWindow); };
    virtual bool IsUsingHighPrecisionMouseMode() const { return WrappedApplication->IsUsingHighPrecisionMouseMode(); }
    virtual bool IsUsingTrackpad() const { return WrappedApplication->IsUsingTrackpad(); }
    virtual bool IsMouseAttached() const { return WrappedApplication->IsMouseAttached(); }
    virtual bool IsGamepadAttached() const { return WrappedApplication->IsGamepadAttached(); }
    virtual void RegisterConsoleCommandListener(const FOnConsoleCommandListener& InListener) { WrappedApplication->RegisterConsoleCommandListener(InListener); }
    virtual void AddPendingConsoleCommand(const FString& InCommand) { WrappedApplication->AddPendingConsoleCommand(InCommand); }
    virtual FPlatformRect GetWorkArea(const FPlatformRect& CurrentWindow) const { return WrappedApplication->GetWorkArea(CurrentWindow); }
    virtual bool TryCalculatePopupWindowPosition(const FPlatformRect& InAnchor, const FVector2D& InSize, const FVector2D& ProposedPlacement, const EPopUpOrientation::Type Orientation, /*OUT*/ FVector2D* const CalculatedPopUpPosition) const { return WrappedApplication->TryCalculatePopupWindowPosition(InAnchor, InSize, ProposedPlacement, Orientation, CalculatedPopUpPosition); }
    virtual void GetInitialDisplayMetrics(FDisplayMetrics& OutDisplayMetrics) const { WrappedApplication->GetInitialDisplayMetrics(OutDisplayMetrics); }
    virtual EWindowTitleAlignment::Type GetWindowTitleAlignment() const { return WrappedApplication->GetWindowTitleAlignment(); }
    virtual EWindowTransparency GetWindowTransparencySupport() const { return WrappedApplication->GetWindowTransparencySupport(); }
    virtual void DestroyApplication() { WrappedApplication->DestroyApplication(); }
    virtual IInputInterface* GetInputInterface() { return WrappedApplication->GetInputInterface(); }
    virtual ITextInputMethodSystem* GetTextInputMethodSystem() { return WrappedApplication->GetTextInputMethodSystem(); }
    virtual void SendAnalytics(IAnalyticsProvider* Provider) { WrappedApplication->SendAnalytics(Provider); }
    virtual bool SupportsSystemHelp() const { return WrappedApplication->SupportsSystemHelp(); }
    virtual void ShowSystemHelp() { WrappedApplication->ShowSystemHelp(); }
    virtual bool ApplicationLicenseValid(FPlatformUserId PlatformUser = PLATFORMUSERID_NONE) { return WrappedApplication->ApplicationLicenseValid(PlatformUser); }

    /**
     * Functions with overridden behavior.
     */
    virtual bool IsCursorDirectlyOverSlateWindow() const { return true; }

    TSharedPtr<GenericApplication> WrappedApplication;
};

class FVentuzInputDevice : public IInputDevice
{
    int touchIndexMap[10];

    TSharedPtr<class FVentuzApplicationWrapper> appWrapper;
    TSharedRef<FGenericApplicationMessageHandler> MessageHandler;

public:
    FVentuzInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler/*, TArray<UPixelStreamerInputComponent*>& InInputComponents*/);

    void ParseOSCBundle(std::string data);
    void ResetOriginalApplication();

    virtual void Tick(float deltaTime) override;

    virtual void SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override;

    // inputdevice overrides
    virtual void SendControllerEvents() override {}
    bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { return true;}

    void SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) override {}
    void SetChannelValues(int32 ControllerId, const FForceFeedbackValues& values) override {}
};

class IVentuzPlugin : public IInputDeviceModule
{
public:

	static inline IVentuzPlugin& Get()
	{
		return FModuleManager::LoadModuleChecked<IVentuzPlugin>("VentuzPlugin");
	}
	static inline bool IsAvailable()
	{
		return FModuleManager::Get().IsModuleLoaded("VentuzPlugin");
	}
};

class FVentuzViewExtension : public FSceneViewExtensionBase
{
public:
    FVentuzViewExtension(const FAutoRegister& AutoRegister, FViewport* AssociatedViewport);

    //~ Begin ISceneViewExtension interface	
    virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override;
    virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) override;

    virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) override;
    virtual void PreRenderView_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView) override;

    virtual void PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) override;
    virtual void PostRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) override;

    virtual int32 GetPriority() const override { return -1; }
    virtual bool IsActiveThisFrame(class FViewport* InViewport) const override;
    //~End ISceneVIewExtension interface

    /** Viewport to which we are attached */
    FViewport* GetAssociatedViewport() { return LinkedViewport; }

private:
    FViewport* LinkedViewport = nullptr;
};


class VENTUZPLUGIN_API FVentuzPlugin : public IVentuzPlugin, public FTickableGameObject
{
 	Ventuz::UE4::UE4ComClient client;

    TSharedPtr<FVentuzInputDevice> InputDevice;

    TMultiMap<uint, FVentuzEventCallback> eventCallbacks;

    bool isInitialized = false;
    bool textureSharesInSync = false;

    void OnPostWorldCreation(UWorld* world);

    void ProcessVentuzInputs();
    void SendTextureShareList();

    TSet<FString> textureShares;

    GUID ventuzLaunchID{};
    bool isLiveLink = true;

    bool isGameRunning = false;
    void SetGameRunning( bool isRunning );
    void SendFieldListUpdate(const GUID& requestID, const std::string& nodeName );

    std::unordered_map<GUID, std::string> fromVentuzFieldListRequests;

    //unsigned int GetCurrentFrameViaHack();

    Scalability::FQualityLevels defaultQualitySettings;
    Ventuz::UE4::RenderQuality renderQuality = Ventuz::UE4::RenderQuality::Default;
    void SetRenderQuality(Ventuz::UE4::RenderQuality quality);
    void DisableEditorCPUThrottling();
    void ForceFXAA();

public:

	FVentuzPlugin();

	virtual void Tick(float deltaTime) override;
    void OnBeginFrame();
    void OnBeginFrameRT();
    void OnEnginePreExit();
    void OnSceneColorResolved(FRHICommandListImmediate& RHICmdList, class FSceneRenderTargets& SceneContext);

    virtual TStatId GetStatId() const override;
	virtual bool IsTickableWhenPaused() const override;
	virtual bool IsTickableInEditor() const override;

	/** IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;

    TSharedPtr<class IInputDevice> CreateInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler);

    void RegisterEvent( uint eventNameHash, const FVentuzEventCallback& event );
    void TriggerEvent( uint eventNameHash, int argument );

    void TickAnc();

    FTransform GetVentuzCameraTransform();
    float GetVentuzCameraFov();
    float GetVentuzCameraAspect();
    FMatrix GetVentuzProjectionMatrix();
    bool IsVentuzCameraOrtho();
    FVector2D GetVentuzProjectionOffset();
    int GetIntValue(uint hash, bool& isAvailable);
    float GetFloatValue(uint hash, bool& isAvailable);
    FString GetStringValue(uint hash, bool& isAvailable);
    bool GetBoolValue(uint hash, bool& isAvailable);

    TArray<int> GetIntArray( uint hash, bool& isAvailable );
    TArray<float> GetFloatArray( uint hash, bool& isAvailable );
    TArray<FString> GetStringArray( uint hash, bool& isAvailable );
    TArray<bool> GetBoolArray( uint hash, bool& isAvailable );

    FColor GetColorValue(uint hash, bool& isAvailable);
    FTransform GetTransformValue(uint hash, bool& isAvailable);

    void RegisterTextureShare( const FString& textureShareName );
    void UnRegisterTextureShare( const FString& textureShareName );

    void SendIntValue( uint hash, const int& value );
    void SendFloatValue( uint hash, const float& value );
    void SendStringValue( uint hash, const FString& value );
    void SendBoolValue( uint hash, const bool value );

    void SendIntArray( uint hash, const TArray<int>& array );
    void SendFloatArray( uint hash, const TArray<float>& array );
    void SendStringArray( uint hash, const TArray<FString>& array );
    void SendBoolArray( uint hash, const TArray<bool>& array );


    void SendTransformValue( uint hash, const FTransform& value );
    void SendFieldList(const GUID& requestID, const std::vector<Ventuz::UE4::FieldDescriptor>& fieldsToSend );

    void RequestFieldListUpdate(const FString& nodeName, IVentuzNodeFieldSync* node );

    std::unordered_map<GUID, std::string> GetFieldListRequests();

    bool IsConnected();
    Ventuz::UE4::FromVentuzClock GetClusterClock();

    GUID GetVentuzLaunchID();

    void SetViewports( int x1, int y1, int x2, int y2, int zWidth, int zHeight );

    void SetSyncEvent(Ventuz::UE4::SyncEvent event);
    void WaitForEvent(Ventuz::UE4::SyncEvent event, bool reset);

private:

    TSharedPtr<FVentuzViewExtension, ESPMode::ThreadSafe> FindOrAddDisplayConfiguration(FViewport* InViewport);
    TArray<TSharedPtr<FVentuzViewExtension, ESPMode::ThreadSafe>> DisplayExtensions;
};

