using GCloud.UQM;
using System;
using System.Collections;
using System.Threading;
using UnityEngine;

public class CrashSightAnrMonitor
{
    private static CrashSightMonoBehaviour monoBehaviour = CrashSightMonoBehaviour.Instance;
    private static int anrTimeoutMs = 5000;
    private static int detectionTimeoutMs = 1000;
    protected static bool Paused { get; private set; } = false;
    private static int ticksSinceUiUpdate = 0;
    private static bool reported = false;
    private static bool running = false;
    private static Thread thread;

    public static void Start(int timeoutMs = 5000)
    {
        if (running)
        {
            return;
        }
        else
        {
            running = true;
        }
        anrTimeoutMs = timeoutMs;
        detectionTimeoutMs = Math.Max(1, anrTimeoutMs / 5);

        monoBehaviour.ApplicationPausing += () => Paused = true;
        monoBehaviour.ApplicationResuming += () => Paused = false;
        monoBehaviour.ApplicationQuitting += () => Stop();

        thread = new Thread(Run)
        {
            Name = "CrashSight-ANR-Monitor",
            IsBackground = true,
            Priority = System.Threading.ThreadPriority.BelowNormal,
        };
        thread.Start();
        
        monoBehaviour.StartCoroutine(UpdateUiStatus());
    }
    public static void Stop()
    {
        running = false;
    }

    private static IEnumerator UpdateUiStatus()
    {
        UQMCrash.SetEngineMainThread();
        var waitForSeconds = new WaitForSecondsRealtime((float)detectionTimeoutMs / 1000);

        yield return waitForSeconds;
        while (running)
        {
            ticksSinceUiUpdate = 0;
            reported = false;
            yield return waitForSeconds;
        }
    }

    private static void Run()
    {
        try
        {
            while (running)
            {
                ticksSinceUiUpdate += detectionTimeoutMs;
                Thread.Sleep(detectionTimeoutMs);

                if (Paused)
                {
                    ticksSinceUiUpdate = 0;
                }
                else if (ticksSinceUiUpdate >= anrTimeoutMs && !reported)
                {
                    Report();
                    reported = true;
                }
            }
        }
        catch (Exception ex)
        {
            UQMLog.LogError("CrashSightAnrMonitor meets an unknown error = \n" + ex.Message + "\n" + ex.StackTrace);
        }
    }

    private static void Report()
    {
        string message = $"Application not responding for at least {anrTimeoutMs} ms.";
        UQMLog.Log(message);
        UQMCrash.ProcessEngineAnr(1);
    }
}

public class UnityAnrException : Exception
{
    internal UnityAnrException() : base() { }
    internal UnityAnrException(string message) : base(message) { }
    internal UnityAnrException(string message, Exception innerException) : base(message, innerException) { }
}