// Oleg Petruny proprietary.

#include "MoveInteractableModificator.h"

#include "EnhancedInputComponent.h"
#include "InputMappingContext.h"

#include "Interactable/Interactable.h"
#include "MainGameModeBase.h"
#include "PlayerBase.h"
#include "Widgets/InteractableHintWidget.h"
#include "Widgets/WidgetsManager.h"

UMoveInteractableModificator::UMoveInteractableModificator(const FObjectInitializer& ObjectInitializer)
    : UInteractableModificator(ObjectInitializer)
{
    activatorTypes |= static_cast<uint8>(EActivatorType::Use);

    static ConstructorHelpers::FObjectFinder<UInputMappingContext> asset{ TEXT("/Script/EnhancedInput.InputMappingContext'/Game/Input/Interactables/IMC_InteractableMove.IMC_InteractableMove'") };
    inputMapping = asset.Object;

    actor = Cast<AInteractable>(GetOwner());

    PrimaryComponentTick.bCanEverTick = true;
    PrimaryComponentTick.bStartWithTickEnabled = false;
    PrimaryComponentTick.TickGroup = ETickingGroup::TG_PostUpdateWork;
}

void UMoveInteractableModificator::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    UInteractableModificator::TickComponent(DeltaTime, TickType, ThisTickFunction);

    UpdatePosition();
}

void UMoveInteractableModificator::Bind_Implementation(class UEnhancedInputComponent* input)
{
    if(!input && !inputMapping)
        return;

    if(holding || rotating)
        return;

    for(auto& mapping : inputMapping->GetMappings())
    {
        if(mapping.Key == EKeys::LeftMouseButton)
        {
            auto handle = input->BindAction(mapping.Action, ETriggerEvent::Started, this, &UMoveInteractableModificator::TurnOnHolding).GetHandle();
            bindindingHandlers.Add(handle);
            handle = input->BindAction(mapping.Action, ETriggerEvent::Completed, this, &UMoveInteractableModificator::TurnOffHolding).GetHandle();
            bindindingHandlers.Add(handle);
        }
        else if(mapping.Key == EKeys::RightMouseButton)
        {
            auto handle = input->BindAction(mapping.Action, ETriggerEvent::Started, this, &UMoveInteractableModificator::TurnOnRotating).GetHandle();
            bindindingHandlers.Add(handle);
            handle = input->BindAction(mapping.Action, ETriggerEvent::Completed, this, &UMoveInteractableModificator::TurnOffRotating).GetHandle();
            bindindingHandlers.Add(handle);
        }
        else if(mapping.Key == EKeys::MouseWheelAxis)
        {
            auto handle = input->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &UMoveInteractableModificator::Zooming).GetHandle();
            bindindingHandlers.Add(handle);
        }
        else if(mapping.Key == EKeys::Mouse2D)
        {
            auto handle = input->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &UMoveInteractableModificator::Rotating).GetHandle();
            bindindingHandlers.Add(handle);
        }
    }

    lastInput = input;
    player = Cast<APlayerBase>(Cast<APlayerController>(lastInput->GetOwner())->GetPawn());
    SetComponentTickEnabled(true);

    OnMoveActivated.Broadcast();
}

void UMoveInteractableModificator::Unbind_Implementation()
{
    if(!lastInput || bindindingHandlers.IsEmpty())
        return;

    if(holding || rotating)
        return;

    for(auto handle : bindindingHandlers)
        lastInput->RemoveBindingByHandle(handle);
    bindindingHandlers.Empty();

    SetComponentTickEnabled(false);
    actor->Deactivate(GetActivatorTypes());

    OnMoveDeactivated.Broadcast();
}

void UMoveInteractableModificator::TurnOnHolding()
{
    FScopeLock lock1(&critical);
    holding = true;
    ProcessState();
    distance = (player->GetCameraLocation() - actor->GetActorLocation()).Length();
    AMainGameModeBase::GetWidgetsManager()->AnimateInteractionHint(this, 0, EInputAnimatedWidgetAnimation::Hold);
    OnHolding.Broadcast();
}
void UMoveInteractableModificator::TurnOffHolding()
{
    FScopeLock lock1(&critical);
    holding = false;
    ProcessState();
    AMainGameModeBase::GetWidgetsManager()->AnimateInteractionHint(this, 0, EInputAnimatedWidgetAnimation::Unhold);
}

void UMoveInteractableModificator::TurnOnRotating()
{
    FScopeLock lock1(&critical);
    rotating = true;
    player->LockPlayer({ .camera = true });
    ProcessState();
    AMainGameModeBase::GetWidgetsManager()->AnimateInteractionHint(this, 1, EInputAnimatedWidgetAnimation::Hold);
    OnRotating.Broadcast();
}
void UMoveInteractableModificator::TurnOffRotating()
{
    FScopeLock lock1(&critical);
    rotating = false;
    player->UnlockPlayer({ .camera = true });
    ProcessState();
    AMainGameModeBase::GetWidgetsManager()->AnimateInteractionHint(this, 1, EInputAnimatedWidgetAnimation::Unhold);
}

void UMoveInteractableModificator::ProcessState()
{
    if(holding || rotating)
    {
        if(!actor->activationLockers.Contains(this))
            actor->activationLockers.Add(this);
    }
    else
    {
        actor->activationLockers.Remove(this);
        if((actor->GetActivatedFlags() & static_cast<int32>(GetActivatorTypes())) == 0
            && !bindindingHandlers.IsEmpty())
        {
            Unbind_Implementation();
        }
    }
}

void UMoveInteractableModificator::UpdatePosition()
{
    if(!player || (!holding && !rotating))
        return;

    auto camLoc = player->GetCameraLocation();
    auto dir = player->GetCameraDirection();
    auto newLoc = camLoc + dir * distance;
    auto oldLoc = actor->GetActorLocation();
    //auto interpLoc = FMath::VInterpTo(oldLoc, newLoc, GetWorld()->GetDeltaSeconds(), 20.0f);
    //auto interpLoc = FMath::Lerp(oldLoc, newLoc, 0.5f);
    FHitResult hit;
    actor->SetActorLocation(newLoc, true, &hit, ETeleportType::None);

    static bool indicating = false;
    if(hit.bBlockingHit && !indicating)
    {
        indicating = true;
        AMainGameModeBase::GetWidgetsManager()->AnimateInteractionHint(this, 0, EInputAnimatedWidgetAnimation::TurnOnIndication);
    }
    else if(!hit.bBlockingHit && indicating)
    {
        indicating = false;
        AMainGameModeBase::GetWidgetsManager()->AnimateInteractionHint(this, 0, EInputAnimatedWidgetAnimation::TurnOffIndication);
    }

}

void UMoveInteractableModificator::Zooming(const FInputActionValue& value)
{
    if(!holding && !rotating)
        return;

    distance += value.Get<float>() * zoomSpeed;
    distance = FMath::Clamp<float>(distance, minDistance, maxDistance);
}

void UMoveInteractableModificator::Rotating(const FInputActionValue& axis)
{
    if(!rotating)
        return;

    auto v = axis.Get<FVector>();
    auto vec = player->GetCameraDirection();
    auto currentRot = actor->GetActorRotation();
    auto currentLoc = actor->GetActorLocation();
    auto newRot = FRotator{ v.Y * vec.X, v.X, v.Y * vec.Y * -1 };
    actor->AddActorWorldRotation(newRot, true, nullptr, ETeleportType::None); // sweep not supported from 4.xx+
}