#include "CrashSightAnrMonitor.h"
#include "Misc/ScopeLock.h"
#include "HAL/PlatformProcess.h"
#include "Async/Async.h"
#include "Misc/CoreDelegates.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "CrashSightMobileAgent.h"

FCrashSightAnrMonitor* FCrashSightAnrMonitor::Instance = nullptr;

void FCrashSightAnrMonitor::Initialize()
{
    if (!Instance)
    {
        Instance = new FCrashSightAnrMonitor();
    }
}

void FCrashSightAnrMonitor::Shutdown()
{
    StopMonitor();
    delete Instance;
    Instance = nullptr;
}

void FCrashSightAnrMonitor::StartMonitor(int32 TimeoutMs)
{
    FScopeLock Lock(&Instance->CriticalSection);

    if (Instance->bRunning) return;

    Instance->AnrTimeoutMs = TimeoutMs;
    Instance->DetectionTimeoutMs = FMath::Max(1, TimeoutMs / 5);
    Instance->bRunning = true;

    FTimerDelegate InitDelegate = FTimerDelegate::CreateLambda([](){
#if PLATFORM_ANDROID
       GCloud::CrashSight::CrashSightMobileAgent::setEngineMainThread();
#endif
    });
    GWorld->GetTimerManager().SetTimerForNextTick(InitDelegate);

    // Setup GameThread update timer
    FTimerDelegate TimerDelegate = FTimerDelegate::CreateRaw(Instance, &FCrashSightAnrMonitor::UpdateUiStatus);
    GWorld->GetTimerManager().SetTimer(
        Instance->UiUpdateTimerHandle,
        TimerDelegate,
        Instance->DetectionTimeoutMs / 1000.0f,
        true
    );

    // Start monitoring thread
    Instance->Thread = FRunnableThread::Create(Instance, TEXT("CrashSight-ANR-Monitor"), 0, TPri_BelowNormal);

    // Bind application pause events
    FCoreDelegates::ApplicationWillEnterBackgroundDelegate.AddRaw(Instance, &FCrashSightAnrMonitor::HandleApplicationWillEnterBackground);
    FCoreDelegates::ApplicationHasEnteredForegroundDelegate.AddRaw(Instance, &FCrashSightAnrMonitor::HandleApplicationWillEnterForeground);
}

void FCrashSightAnrMonitor::StopMonitor()
{
    FScopeLock Lock(&Instance->CriticalSection);

    Instance->bRunning = false;
    GWorld->GetTimerManager().ClearTimer(Instance->UiUpdateTimerHandle);
}

FCrashSightAnrMonitor::FCrashSightAnrMonitor()
    : bRunning(false)
    , bPaused(false)
    , AnrTimeoutMs(5000)
    , DetectionTimeoutMs(1000)
{
}

FCrashSightAnrMonitor::~FCrashSightAnrMonitor()
{
    Stop();
    if (Thread)
    {
        Thread->WaitForCompletion();
        delete Thread;
    }
}

uint32 FCrashSightAnrMonitor::Run()
{
    while (bRunning)
    {
        FPlatformProcess::Sleep(DetectionTimeoutMs / 1000.0f);

        if (!bRunning) break;

        if (!bPaused)
        {
            TicksSinceUiUpdate.Add(DetectionTimeoutMs);

            if (TicksSinceUiUpdate.GetValue() >= AnrTimeoutMs && !bReported)
            {
                ReportANR();
                bReported = true;
            }
        }
        else
        {
            TicksSinceUiUpdate.Reset();
        }
    }
    return 0;
}

void FCrashSightAnrMonitor::ReportANR()
{
    const FString Message = FString::Printf(TEXT("Application not responding for at least %d ms"), Instance->AnrTimeoutMs);
    UE_LOG(LogTemp, Error, TEXT("%s"), *Message);
#if PLATFORM_ANDROID
    GCloud::CrashSight::CrashSightMobileAgent::processEngineAnr(2);
#endif
}

void FCrashSightAnrMonitor::UpdateUiStatus()
{
    TicksSinceUiUpdate.Reset();
    bReported = false;
}

void FCrashSightAnrMonitor::HandleApplicationPause(bool bIsPaused)
{
    bPaused = bIsPaused;
    if (!bIsPaused)
    {
        TicksSinceUiUpdate.Reset();
    }
}

void FCrashSightAnrMonitor::HandleApplicationWillEnterBackground()
{
    HandleApplicationPause(true);
}

void FCrashSightAnrMonitor::HandleApplicationWillEnterForeground()
{
    HandleApplicationPause(false);
}