#pragma once

#include "GasaCommon.h"
#include "GasaPlayerState.h"
#include "Networking/GasaNetLibrary.h"
#include "Engine/Engine.h"
#include "GameFramework/PlayerController.h"

#include "GasaPlayerController.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGasaPlayerControllerSig, AGasaPlayerController*, PC);

UCLASS(Blueprintable)
class GASA_API AGasaPlayerController : public APlayerController
{
	GENERATED_BODY()
protected:
	friend void AGasaPlayerState::ClientInitialize(AController* NewOwner);
public:

	UFUNCTION()
	void OnSeamlessTravelStart();
	
#pragma region Camera
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TObjectPtr<ACameraMount> Cam;
#pragma endregion Camera

		// This will be implemented in the base until it needs to be lifted into an abstraction.
#if 0
#pragma region Highlighting
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Highlighting")
	EHighlight HighlightState;
	
	UFUNCTION(BlueprintCallable, Category="Highlighting")
	void SetHighlight( EHighlight Desired );

	UFUNCTION(BlueprintCallable, Category="Highlighting")
	FORCEINLINE void Highlight() { SetHighlight(EHighlight::Enabled); };
	
	UFUNCTION(BlueprintCallable, Category="Highlighting")
	FORCEINLINE void Dehighlight() { SetHighlight(EHighlight::Disabled); };
#pragma endregion Highlighting
#endif

#pragma region GameFramework
	UPROPERTY(BlueprintAssignable)
	FOnGasaPlayerControllerSig Event_NetOwner_OnGameFrameworkInitialized;

	UPROPERTY(BlueprintAssignable)
	FOnGasaPlayerControllerSig Event_NetOwner_OnReady;

	UPROPERTY(BlueprintAssignable)
	FOnPawnSig Event_OnPawnPossessed;

	UPROPERTY(Replicated, VisibleAnywhere)
	bool bNetOwner_GameFrameworkInitialized = false;

	UPROPERTY(VisibleAnywhere, Category = "Game Framework")
	bool bNetOwnerReady = false;
	
	void Client_CheckIfOwnerReady();

	UFUNCTION()
	void NetOwner_OnReady();

	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, meta = (DisplayName = "NetOwner: On Ready"))
	void BP_NetOwner_OnReady();

	UFUNCTION()
	void OnGameFrameworkInitialized();

	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, meta=(DisplayName = "On Game Framework Initialized"))
	void BP_OnGameFrameworkInitialized();
	
	UFUNCTION()
	void OnPawnReady();

	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, meta=(DisplayName = "On Pawn Ready"))
	void BP_OnPawnReady();
	
	UFUNCTION()
	void Server_SetupOnPawnReadyBinds(APawn* PawnToBindTo);

	UFUNCTION()
	void Server_SetNetOwner_GameFrameworkInitialized();
	
	UFUNCTION(Server, Reliable)
	void ServerRPC_R_NotifyOwningClientReady();

	UFUNCTION(Server, Reliable)
	void ServerRPC_R_SetNetOwner_GameFrameworkInitialized();
#pragma endregion GameFramework
	
#pragma region Input
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	TObjectPtr<AGasaCharacter> HoverPrev;
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	TObjectPtr<AGasaCharacter> HoverCurr;
	
	UPROPERTY(EditAnywhere, Category="Input")
	TObjectPtr<UInputMappingContext> IMC;

	UPROPERTY(EditAnywhere, Category="Input")
	TObjectPtr<UInputAction> IA_Move;
	
	void Move(FInputActionValue const& ActionValue);
#pragma endregion Input
	
#pragma region NetSlime
	// NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp
	FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); }
	FORCEINLINE bool IsClient()               const { return Gasa::IsClient( this ); }
	FORCEINLINE bool IsListenServer()         const { return Gasa::IsListenServer( this ); }
	FORCEINLINE bool IsNetOwner()             const { return Gasa::IsNetOwner( this ); }
	FORCEINLINE bool IsServer()               const { return Gasa::IsServer( this ); }
	FORCEINLINE bool IsSimulatedProxy()       const { return Gasa::IsSimulatedProxy( this ); }
	FORCEINLINE void NetLog(
		FString           Message,
		EGasaVerbosity    Verbosity = EGasaVerbosity::Log,
		FLogCategoryBase& Category  = LogGasaNet,
		bool              DumpStack = false,
		int32             Line      = __builtin_LINE(),
		ANSICHAR const*   File      = __builtin_FILE(),
		ANSICHAR const*   Func      = __builtin_FUNCTION()
	)
	{
		Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func );
	}
#pragma endregion NetSlime
	
	AGasaPlayerController();

	inline AGasaPlayerState* GetPlayerState();
	
#pragma region PlayerController
	bool CanRestartPlayer() override;
	void ClientSetHUD_Implementation(TSubclassOf<AHUD> NewHUDClass) override;
	void ClientUpdateLevelStreamingStatus_Implementation(FName PackageName, bool bNewShouldBeLoaded, bool bNewShouldBeVisible, bool bNewShouldBlockOnLoad, int32 LODIndex, FNetLevelVisibilityTransactionId TransactionId, bool bNewShouldBlockOnUnload) override;
	void PlayerTick(float DeltaTime) override;
	void PostSeamlessTravel() override;
	void SetupInputComponent() override;
	void SpawnDefaultHUD() override;
#pragma endregion PlayerController

#pragma region Controller
	void OnPossess(APawn* InPawn) override;
	void OnRep_Pawn() override;
	void OnUnPossess() override;
#pragma endregion Controller

#pragma region Actor
	void BeginPlay() override;
	void PostInitializeComponents() override;
	void Tick(float DeltaSeconds) override;
#pragma endregion Actor

#pragma region UObject
	void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
#pragma endregion UObject
};

namespace Gasa
{
	inline
	AGasaPlayerController* GetPrimaryPlayerController(UObject* Context)
	{
		UWorld* World = GEngine->GetWorldFromContextObject(Context, EGetWorldErrorMode::LogAndReturnNull);
		if (World == nullptr)
		{
			Log("World is null... are you running in a proper context?", ELogV::Error);
			return nullptr;
		}
		return Cast<AGasaPlayerController>(World->GetFirstPlayerController());
	}
}
