// Fill out your copyright notice in the Description page of Project Settings.


#include "Pawn/BasicVRInteractionComponent.h"


#include "Interaction/Clickable.h"
#include "Interaction/Grabable.h"
#include "Interaction/Targetable.h"
#include "Interaction/GrabbingBehaviorComponent.h"
#include "Misc/Optional.h"
#include "DrawDebugHelpers.h"
#include "Components/WidgetComponent.h"

DEFINE_LOG_CATEGORY(LogVRInteractionComponent);

// Sets default values for this component's properties
UBasicVRInteractionComponent::UBasicVRInteractionComponent()
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	PrimaryComponentTick.bCanEverTick = true;

	// Setup the interaction ray.
	InteractionRay = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Interaction Ray"));
	//this ray model has an inlayed cross with flipped normals so it can be seen as a cross in desktop mode where the right hand is attached to the head
	ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/RWTHVRToolkit/PointingRay/Ray_Mesh"));
	if (MeshAsset.Object != nullptr)
	{
		InteractionRay->SetStaticMesh(MeshAsset.Object);
	}
	// turns off collisions as the InteractionRay is only meant to visualize the ray
	InteractionRay->SetCollisionProfileName(TEXT("NoCollision"));
	bShowDebug = false; //otherwise the WidgetInteractionComponent debug vis is shown
	InteractionSource = EWidgetInteractionSource::Custom; //can also be kept at default (World), this way, however, we efficiently reuse the line traces
	
}

void UBasicVRInteractionComponent::BeginPlay()
{
	Super::BeginPlay();
	
	//WidgetInteractionComponent
	InteractionDistance = MaxClickDistance;
	SetInteractionRayVisibility(InteractionRayVisibility);
}

void UBasicVRInteractionComponent::BeginInteraction()
{
	if(!InteractionRayEmitter) return;
	
	// start and end point for raytracing
	const FTwoVectors StartEnd = GetHandRay(MaxClickDistance);
	TOptional<FHitResult> Hit = RaytraceForFirstHit(StartEnd);
	if (!Hit.IsSet())
		return;

	AActor* HitActor = Hit->GetActor();

	//trigger interaction of WidgetInteractionComponent
	SetCustomHitResult(Hit.GetValue());
	//if !bCanRaytraceEveryTick, you have to click twice, since the first tick it only highlights and can't directly click
	PressPointerKey(EKeys::LeftMouseButton);
	
	
	if (HitActor && HitActor->Implements<UGrabable>() && Hit->Distance < MaxGrabDistance)
	{
		// call grabable actors function so he reacts to our grab
		IGrabable::Execute_OnBeginGrab(HitActor);
		
		// save it for later, is needed every tick
		Behavior = HitActor->FindComponentByClass<UGrabbingBehaviorComponent>();
		if ( Behavior == nullptr)
			HandlePhysicsAndAttachActor(HitActor);

		// we save the grabbedActor in a general form to access all of AActors functions easily later
		GrabbedActor = HitActor;
	}
	else if (HitActor && HitActor->Implements<UClickable>() && Hit->Distance < MaxClickDistance)
	{
		IClickable::Execute_OnClick(HitActor, Hit->Location);
	}
}

void UBasicVRInteractionComponent::EndInteraction()
{
	if(!InteractionRayEmitter) return;

	//end interaction of WidgetInteractionComponent
	ReleasePointerKey(EKeys::LeftMouseButton);
	
	// if we didnt grab anyone there is no need to release
	if (GrabbedActor == nullptr)
		return;

	// let the grabbed object react to release
	IGrabable::Execute_OnEndGrab(GrabbedActor);

	// Detach the Actor
	if (GrabbedActor->FindComponentByClass<UGrabbingBehaviorComponent>() == nullptr)
	{
		if (ComponentSimulatingPhysics) {
			ComponentSimulatingPhysics->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
			ComponentSimulatingPhysics->SetSimulatePhysics(true);
		}
		else {
			GrabbedActor->GetRootComponent()->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
		}
	}

	// forget about the actor
	GrabbedActor = nullptr;
	ComponentSimulatingPhysics = nullptr;
	Behavior = nullptr;
}

// Called every frame
void UBasicVRInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (!InteractionRayEmitter) return;
	
	// if our Grabable Actor is not constrained we need to calculate the position dynamically
	if (Behavior != nullptr)
	{	
		// specifies the hand in space
		const FVector HandPos = InteractionRayEmitter->GetComponentLocation();
		const FQuat HandQuat = InteractionRayEmitter->GetComponentQuat();

		Behavior->HandleNewPositionAndDirection(HandPos, HandQuat); 
	}

	// only raytrace for targetable objects if bool user wants to enable this feature
	if (!bCanRaytraceEveryTick)
		return;

	const FTwoVectors StartEnd = GetHandRay(MaxClickDistance);
	TOptional<FHitResult> Hit = RaytraceForFirstHit(StartEnd);

	if (!Hit.IsSet())
	{
		if(InteractionRayVisibility==EInteractionRayVisibility::VisibleOnHoverOnly)
		{
			InteractionRay->SetVisibility(false);
		}

		// Execute leave event on the actor that lost the focus if there was one
		if (LastActorHit && LastActorHit->Implements<UTargetable>())
		{
				ITargetable::Execute_OnTargetedLeave(LastActorHit);
		}

		LastActorHit = nullptr;
		return;
	}
	
	AActor* HitActor = Hit->GetActor();

	// Execute Leave and enter events when the focused actor changed
	if (HitActor != LastActorHit)
	{
		//We can always execute the enter event as we are sure that a hit occured
		if (HitActor->Implements<UTargetable>())
		{
			ITargetable::Execute_OnTargetedEnter(HitActor);
		}

		//Only execute the Leave Event if there was an actor that was focused previously
		if (LastActorHit != nullptr && LastActorHit->Implements<UTargetable>())
		{
			ITargetable::Execute_OnTargetedLeave(LastActorHit);
		}
	}

	// for now uses the same distance as clicking
	if (HitActor->Implements<UTargetable>() && Hit->Distance < MaxClickDistance)
	{
		ITargetable::Execute_OnTargeted(HitActor, Hit->Location);
	}

	// widget interaction
	SetCustomHitResult(Hit.GetValue());
	if(InteractionRayVisibility==EInteractionRayVisibility::VisibleOnHoverOnly)
	{
		if(HitActor->Implements<UTargetable>() || HitActor->Implements<UClickable>() || IsOverInteractableWidget())
		{
			InteractionRay->SetVisibility(true);
		}
		else
		{
			InteractionRay->SetVisibility(false);
		}
	}
	LastActorHit = HitActor; // Store the actor that was hit to have access to it in the next frame as well
}

void UBasicVRInteractionComponent::Initialize(USceneComponent* RayEmitter, float InMaxGrabDistance, float InMaxClickDistance)
{
	if(InteractionRayEmitter) return; /* Return if already initialized */

	InteractionRayEmitter = RayEmitter;
	MaxGrabDistance = InMaxGrabDistance;
	MaxClickDistance = InMaxClickDistance;

	InteractionRay->AttachToComponent(RayEmitter, FAttachmentTransformRules::KeepRelativeTransform);
	InteractionRay->SetRelativeScale3D(FVector(MaxClickDistance/100.0f, 1.0f, 1.0f)); //the ray model has a length of 100cm
	this->AttachToComponent(RayEmitter, FAttachmentTransformRules::KeepRelativeTransform);
}

void UBasicVRInteractionComponent::SetInteractionRayVisibility(EInteractionRayVisibility NewVisibility)
{
	InteractionRayVisibility = NewVisibility;
	if(InteractionRay)
	{
		switch (InteractionRayVisibility)
		{
		case Visible:
			InteractionRay->SetVisibility(true);
			break;
		case VisibleOnHoverOnly:
		case Invisible:
			InteractionRay->SetVisibility(false);
			break;
		}
	}

	if(InteractionRayVisibility==EInteractionRayVisibility::VisibleOnHoverOnly && !bCanRaytraceEveryTick)
	{
		UE_LOG(LogVRInteractionComponent, Warning, TEXT("VisibleOnHoverOnly needs bCanRaytraceEveryTick=true, so this is set!"));
		bCanRaytraceEveryTick=true;
	}
	if(InteractionRayVisibility==EInteractionRayVisibility::Visible && !bCanRaytraceEveryTick)
	{
		UE_LOG(LogVRInteractionComponent, Warning, TEXT("VisibleOnHoverOnly will need two clicks to interact with widgets if bCanRaytraceEveryTick is not set!"));
	}
}

void UBasicVRInteractionComponent::HandlePhysicsAndAttachActor(AActor* HitActor)
{
	UPrimitiveComponent* PhysicsSimulatingComp = GetFirstComponentSimulatingPhysics(HitActor);
	const FAttachmentTransformRules Rules = FAttachmentTransformRules(EAttachmentRule::KeepWorld, false);
	
	if (PhysicsSimulatingComp) {
		PhysicsSimulatingComp->SetSimulatePhysics(false);
		PhysicsSimulatingComp->AttachToComponent(InteractionRayEmitter, Rules);	
		ComponentSimulatingPhysics = PhysicsSimulatingComp;
	}
	else {
		HitActor->GetRootComponent()->AttachToComponent(InteractionRayEmitter, Rules);
	}
}

FTwoVectors UBasicVRInteractionComponent::GetHandRay(const float Length) const
{
	const FVector Start = InteractionRayEmitter->GetComponentLocation();
	const FVector Direction = InteractionRayEmitter->GetForwardVector();
	const FVector End = Start + Length * Direction;

	return FTwoVectors(Start, End);
}

TOptional<FHitResult> UBasicVRInteractionComponent::RaytraceForFirstHit(const FTwoVectors& Ray) const
{
	const FVector Start = Ray.v1;
	const FVector End   = Ray.v2;	
	
	// will be filled by the Line Trace Function
	FHitResult Hit;

	FCollisionQueryParams Params; 
	Params.AddIgnoredActor(GetOwner()->GetUniqueID()); // prevents actor hitting itself
	if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECollisionChannel::ECC_Visibility,Params))
		return {Hit};
	else
		return {};
}

UPrimitiveComponent* GetFirstComponentSimulatingPhysics(const AActor* TargetActor)
{
	TArray<UPrimitiveComponent*> PrimitiveComponents;
	TargetActor->GetComponents<UPrimitiveComponent>(PrimitiveComponents);	

	// find any component that simulates physics, then traverse the hierarchy
	for (const auto& Component : PrimitiveComponents) {
		if (Component->IsSimulatingPhysics()) {
			return GetHighestParentSimulatingPhysics(Component);
		}	
	}
	return nullptr;
}

// recursively goes up the hierarchy and returns the highest parent simulating physics
UPrimitiveComponent* GetHighestParentSimulatingPhysics(UPrimitiveComponent* Comp)
{	
	if (Cast<UPrimitiveComponent>(Comp->GetAttachParent()) && Comp->GetAttachParent()->IsSimulatingPhysics()) {
		return GetHighestParentSimulatingPhysics(Cast<UPrimitiveComponent>(Comp->GetAttachParent()));
	}
	else {
		return Comp;
	}
}