// Copyright Epic Games, Inc. All Rights Reserved.

#include "CorruptedMemoryCharacter.h"

#include "CMGameInstance.h"
#include "CM_GameState.h"
#include "CM_PlayerController.h"
#include "CM_Shelving.h"
#include "CorruptedMemoryProjectile.h"
#include "Animation/AnimInstance.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"


//////////////////////////////////////////////////////////////////////////
// ACorruptedMemoryCharacter

ACorruptedMemoryCharacter::ACorruptedMemoryCharacter()
{
	// Character doesnt have a rifle at start
	bHasRifle = false;

	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(55.f, 96.0f);

	// Create a CameraComponent	
	FirstPersonCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
	FirstPersonCameraComponent->SetupAttachment(GetCapsuleComponent());
	FirstPersonCameraComponent->SetRelativeLocation(FVector(-10.f, 0.f, 60.f)); // Position the camera
	FirstPersonCameraComponent->bUsePawnControlRotation = true;
	
	bReplicates = true;
	PrimaryActorTick.bCanEverTick = true;
}

void ACorruptedMemoryCharacter::BeginPlay()
{
	// Call the base class  
	Super::BeginPlay();

	//Add Input Mapping Context
	if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
		{
			Subsystem->AddMappingContext(DefaultMappingContext, 0);
		}
	}

	if (GetWorld()->GetAuthGameMode()) return;
	if (!IsValid(GetPlayerState<ACM_PlayerState>())) return;
	if (GetPlayerState<ACM_PlayerState>()->bServerRequestUserInformation)
	{
		GetPlayerState<ACM_PlayerState>()->bServerRequestUserInformation = false;
		const UCMGameInstance* GameInstance = Cast<UCMGameInstance>(GetWorld()->GetGameInstance());
		ServerPlayerJoin(GameInstance->AccountUsername, GameInstance->PlayerIndex, GetPlayerState<ACM_PlayerState>()->GetUniqueId());
	}
}

//////////////////////////////////////////////////////////////////////////// Input

void ACorruptedMemoryCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	// Set up action bindings
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		//Jumping
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);

		//Moving
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ACorruptedMemoryCharacter::Move);

		//Looking
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ACorruptedMemoryCharacter::Look);

		//Scanning
		EnhancedInputComponent->BindAction(ScanAction, ETriggerEvent::Started, this, &ACorruptedMemoryCharacter::ScanStart);
		EnhancedInputComponent->BindAction(ScanAction, ETriggerEvent::Completed, this, &ACorruptedMemoryCharacter::ScanCancel);
	}
}

void ACorruptedMemoryCharacter::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);
	ScanPercentage = GetWorldTimerManager().GetTimerElapsed(ScanTimerHandle) / ScanTime;
}


void ACorruptedMemoryCharacter::LogServerMessage_Implementation(const FString& Message)
{
	if (GetWorld()->GetAuthGameMode()) return;
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, Message);
}

void ACorruptedMemoryCharacter::Client_EnableUserInput_Implementation()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("EnableUserInput_Implementation"));
	EnableInput(GetWorld()->GetFirstPlayerController());
	GetWorld()->GetFirstPlayerController()->SetInputMode(FInputModeGameOnly());
}

void ACorruptedMemoryCharacter::Client_DisableUserInput_Implementation()
{
	DisableInput(GetWorld()->GetFirstPlayerController());
	GetWorld()->GetFirstPlayerController()->SetInputMode(FInputModeUIOnly());
}

void ACorruptedMemoryCharacter::Client_GameStart_Implementation()
{
	OnGameStart.Broadcast();
}

void ACorruptedMemoryCharacter::Client_GameEnd_Implementation()
{
	OnGameEnd.Broadcast();
	FTimerHandle TimerHandle;
	GetWorld()->GetTimerManager().SetTimer(TimerHandle, [this]()
	{
		Cast<UCMGameInstance>(GetWorld()->GetGameInstance())->CloseServer(Cast<UCMGameInstance>(GetWorld()->GetGameInstance())->JoinedLobbyID);
	}, 10, false);
}

void ACorruptedMemoryCharacter::Move(const FInputActionValue& Value)
{
	// input is a Vector2D
	FVector2D MovementVector = Value.Get<FVector2D>();

	if (Controller != nullptr)
	{
		// add movement 
		AddMovementInput(GetActorForwardVector(), MovementVector.Y);
		AddMovementInput(GetActorRightVector(), MovementVector.X);
	}
}

void ACorruptedMemoryCharacter::Look(const FInputActionValue& Value)
{
	// input is a Vector2D
	FVector2D LookAxisVector = Value.Get<FVector2D>();

	if (Controller != nullptr)
	{
		// add yaw and pitch input to controller
		AddControllerYawInput(LookAxisVector.X);
		AddControllerPitchInput(LookAxisVector.Y);
	}
}

void ACorruptedMemoryCharacter::ScanStart(const FInputActionValue& Value)
{
	//Line trace to see if there is a shelf in front of the player
	FHitResult HitResult;
	FVector StartLocation = FirstPersonCameraComponent->GetComponentLocation();
	FVector EndLocation = StartLocation + (FirstPersonCameraComponent->GetForwardVector() * 1000.f);
	FCollisionQueryParams TraceParams;
	TraceParams.AddIgnoredActor(this);
	TraceParams.AddIgnoredActor(GetOwner());
	TraceParams.bTraceComplex = true;
	TraceParams.bReturnPhysicalMaterial = false;
	GetWorld()->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, ECC_Visibility, TraceParams);

	if (!IsValid(HitResult.GetActor())) return;
	if (HitResult.GetActor()->Tags.Contains("StoreShelf") && Cast<ACM_Shelving>(HitResult.GetActor())->GameplayTags.Contains("Corrupted"))
	{
		FString AccountName = Cast<UCMGameInstance>(GetWorld()->GetGameInstance())->AccountUsername;
		if (GetWorldTimerManager().TimerExists(ScanTimerHandle))
		{
			GetWorldTimerManager().ClearTimer(ScanTimerHandle);
			ScanPercentage = 0.f;
		}
		GetWorld()->GetTimerManager().SetTimer(ScanTimerHandle, [this, AccountName, HitResult]()
		{
			if (Cast<ACM_Shelving>(HitResult.GetActor())->GameplayTags.Contains("Scanned")) return;
			Cast<ACM_PlayerController>(GetWorld()->GetFirstPlayerController())->ServerAddGameplayTagToShelf(HitResult.GetActor(), "Scanned");
			ServerSuccessfulScan(AccountName);
		}, 3.f, false);
	}
}

void ACorruptedMemoryCharacter::ScanCancel(const FInputActionValue& Value)
{
	const FString AccountName = Cast<UCMGameInstance>(GetWorld()->GetGameInstance())->AccountUsername;
	GetWorldTimerManager().ClearTimer(ScanTimerHandle);
	ScanPercentage = 0.f;
}

void ACorruptedMemoryCharacter::SetHasRifle(bool bNewHasRifle)
{
	bHasRifle = bNewHasRifle;
}

bool ACorruptedMemoryCharacter::GetHasRifle()
{
	return bHasRifle;
}

void ACorruptedMemoryCharacter::ServerPlayerJoin_Implementation(const FString& PlayerName, const int32& PlayerIndex, const FUniqueNetIdRepl& UNetID)
{
	if (!GetWorld()->GetAuthGameMode()) return;
	if (const auto PlayerStateObjPtr = Cast<ACM_GameState>(GetWorld()->GetGameState())->PlayerArray.FindByPredicate([&](const APlayerState* Player)
	{
		return Player->GetUniqueId() == UNetID;
	}))
	{
		PlayerStateObjPtr->Get()->SetPlayerName(PlayerName);
		PlayerStateObjPtr->Get()->SetPlayerId(PlayerIndex);
	}
}
