// Oleg Petruny proprietary.

#include "AgeOfWarUnit.h"

#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"

#include "AgeOfWarManager.h"

namespace
{
    constexpr auto PlayerCollision = TEXT("AgeOfWarUnitPlayer");
    constexpr auto ComputerCollision = TEXT("AgeOfWarUnitComputer");
    constexpr auto TagNoAuto = TEXT("NoAuto");
}

const FAgeOfWarUnitStats& AAgeOfWarUnit::GetStats()
{
    if(!stats)
        stats = &AAgeOfWarManager::UnitGetStats(GetClass());

    return *stats;
}

void AAgeOfWarUnit::UpdateStats()
{
    health = GetStats().health;
    attackStartRange = GetStats().attackStartRange;

    switch(team)
    {
        case UnitTeam::AI:
            root->SetCollisionProfileName(ComputerCollision);
            allyblocker->SetCollisionProfileName(PlayerCollision);
            break;
        case UnitTeam::Player:
            root->SetCollisionProfileName(PlayerCollision);
            allyblocker->SetCollisionProfileName(ComputerCollision);
            break;
        default:
            break;
    }
    allyblocker->SetBoxExtent(root->GetUnscaledBoxExtent(), false);
    allyblocker->SetCollisionEnabled(ECollisionEnabled::NoCollision);

    OnUpdateStats();
}

void AAgeOfWarUnit::BeginPlay()
{
    if(manager)
    {
        UpdateStats();
    }

    Super::BeginPlay();

    world = GetWorld();
}

void AAgeOfWarUnit::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);
}

AAgeOfWarUnit::AAgeOfWarUnit()
{
    root = CreateDefaultSubobject<UBoxComponent>(TEXT("Collision"));
    allyblocker = CreateDefaultSubobject<UBoxComponent>(TEXT("AllyBlocker"));
    allyblocker->SetupAttachment(root);
    traceStart = CreateDefaultSubobject<USceneComponent>(TEXT("TraceStart"));
    traceStart->SetupAttachment(root);
    traceStart->AddRelativeLocation(FVector(0, 0, 200));

    PrimaryActorTick.bCanEverTick = true;
    PrimaryActorTick.bStartWithTickEnabled = true;
}

void AAgeOfWarUnit::Tick(float deltaTime)
{
    Super::Tick(deltaTime);

    if(!manager || GetStats().moveSpeed <= 0)
        return;

    // try move
    FHitResult moveHit;
    auto moveStep = GetActorForwardVector() * GetStats().moveSpeed;
    this->AddActorWorldOffset(moveStep, true, &moveHit, ETeleportType::None);
    if(moveHit.bBlockingHit)
        allyblocker->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
    else
        allyblocker->SetCollisionEnabled(ECollisionEnabled::NoCollision);

    // look for units forward
    FHitResult hit;
    auto startLocation = traceStart->GetComponentLocation();
    auto endLocation = startLocation + (GetActorRotation().Vector() * attackStartRange * (moveHit.bBlockingHit ? 5 : 1));
    world->LineTraceSingleByChannel(
        hit,
        startLocation,
        endLocation,
        ECollisionChannel::ECC_GameTraceChannel1
    );
    DrawDebugLine(GetWorld(), startLocation, endLocation, FColor::Red, false, deltaTime * 10, 0, 4.0f);

    // check if blocked
    if(!hit.bBlockingHit)
        return;

    forwardUnit = Cast<AAgeOfWarUnit>(hit.GetActor());
    if(!forwardUnit) // something is wrong, only block with units
    {
        Destroy();
        return;
    }

    // iterate to attackable unit
    for(int i = 1; forwardUnit->team == team && i < GetStats().attackUnitDistance; i += forwardUnit->GetStats().unitSize)
        forwardUnit = forwardUnit->forwardUnit;

    // if forward unit is in the same team skip attack
    if(forwardUnit->team == team)
        return;

    // attack the forward unit
    if(lastAttackTimestamp + GetStats().attackRate - FPlatformTime::Seconds() < 0)
    {
        lastAttackTimestamp = FPlatformTime::Seconds();
        forwardUnit->Damage(GetStats().attackDamage);
    }
    GEngine->AddOnScreenDebugMessage(4, 5.0f, FColor::Yellow, FString::Printf(TEXT("Time %f"), lastAttackTimestamp + GetStats().attackRate - FPlatformTime::Seconds()));
}

void AAgeOfWarUnit::Damage(const int32 damage)
{
    if(killed)
        return;

    GEngine->AddOnScreenDebugMessage((int32)GetClass()->GetUniqueID(), 5.0f, FColor::Yellow, FString::Printf(TEXT("%d"), health));

    health -= damage;

    if(health > 0)
        return;

    killed = true;

    if(IsValid(manager))
    {
        manager->UnitKill(this);
    }
    else
    {
        Destroy();
    }
}
