#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 "Runtime/Launch/Resources/Version.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"

uint32 GetTypeHash( const GUID& k );
class UTextureShareObject;


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 ISceneViewExtension
{
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 int32 GetPriority() const override { return -1; }
  virtual bool IsActiveThisFrame( const FSceneViewExtensionContext& Context ) 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;

  int currentTextureShareFrameIndex = 0;

  bool isInitialized = false;
  bool textureSharesInSync = false;

  void OnPostWorldCreation( UWorld* world );

  void ProcessVentuzInputs();
  void SendTextureShareList();

  TMap<GUID, UTextureShareObject*> textureShares;

  GUID ventuzLaunchID{};
  bool isLiveLink = true;

  bool isGameRunning = false;
  void SetGameRunning( UWorld* world );
  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();

  void CreateVentuzTextureShare( UWorld* world );
  void DestroyVentuzTextureShare( UWorld* world );

public:

  FVentuzPlugin();
  void Tick( float deltaTime ) override;

  void OnBeginFrame();
  void OnEndFrame();
  void OnEnginePreExit();

  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 GUID& textureShareName, UTextureShareObject* textureShareObject );
  void UnRegisterTextureShare( const GUID& 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 SetAlphaPropagation( int propagation );

private:

  UTextureShareObject* GetRegisteredTextureShare( const GUID& guid );
  TSharedPtr<FVentuzViewExtension, ESPMode::ThreadSafe> FindOrAddDisplayConfiguration( FViewport* InViewport );
  TArray<TSharedPtr<FVentuzViewExtension, ESPMode::ThreadSafe>> DisplayExtensions;
};

