using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace ChillConnect
{
    public class TimerTask
    {
        private Timer timer;

        public int repeatCount;
        public float interval;
        private Action<TimerTask> onCallBack;
        public object[] args;
        public float time;
        public bool isActive = true;
        public bool isFrameUpdate = false;

        public TimerTask(Timer timer, float interval, Action<TimerTask> onCallBack, params object[] args)
        {
            this.timer = timer;

            repeatCount = 1;
            this.interval = interval;
            this.onCallBack = onCallBack;
            this.args = args;
            time = timer.GetTriggerTime(interval);
        }

        public TimerTask(Timer timer, int repeatCount, float interval, Action<TimerTask> onCallBack,
            params object[] args)
        {
            this.timer = timer;

            this.repeatCount = repeatCount;
            this.interval = interval;
            this.onCallBack = onCallBack;
            this.args = args;
            time = timer.GetTriggerTime(interval);
        }

        public TimerTask(Timer timer, Action<TimerTask> onCallBack, params object[] args)
        {
            this.timer = timer;

            this.onCallBack = onCallBack;
            this.args = args;
        }

        public void CallFunc()
        {
            onCallBack?.Invoke(this);
        }

        public static implicit operator bool(TimerTask timerTask)
        {
            return null != timerTask;
        }
    }

    public class Timer : MonoBehaviour
    {
        public const int INFINITE_LOOP = -1;

        private List<TimerTask> timerTaskList = new();
        private List<TimerTask> timerTaskTriggers = new();

        [SerializeField] private string timerName;
        private TimerTimeType type = TimerTimeType.Null;

        public void SetTimer(string name, TimerTimeType type)
        {
            timerName = name + "_" + type;
            this.type = type;
        }

        private float GetTime()
        {
            return type switch
            {
                TimerTimeType.Time => Time.time,
                TimerTimeType.UnscaledTime => Time.unscaledTime,
                TimerTimeType.RealtimeSinceStartup => Time.realtimeSinceStartup,
                _ => 0
            };
        }

        private void Update()
        {
            if (timerTaskList.Count > 0)
            {
                timerTaskTriggers.Clear();
                foreach (var timerTask in timerTaskList.Where(timerTask => timerTask.isActive))
                {
                    if (timerTask.isFrameUpdate)
                    {
                        timerTaskTriggers.Add(timerTask);
                    }
                    else
                    {
                        if (timerTask.time <= GetTime())
                        {
                            timerTaskTriggers.Add(timerTask);
                        }
                    }
                }

                foreach (var triggerTimerTask in timerTaskTriggers)
                {
                    if (triggerTimerTask.repeatCount != INFINITE_LOOP)
                    {
                        triggerTimerTask.repeatCount--;
                    }

                    if (triggerTimerTask.repeatCount == 0)
                    {
                        timerTaskList.Remove(triggerTimerTask);
                    }
                    else
                    {
                        if (!triggerTimerTask.isFrameUpdate)
                        {
                            triggerTimerTask.time = GetTriggerTime(triggerTimerTask.interval);
                        }
                    }

                    triggerTimerTask.CallFunc();
                }
            }
        }

        public TimerTask AddLoopTimer(float interval, Action<TimerTask> onCallBack, params object[] args)
        {
            var timeTask = new TimerTask(this, INFINITE_LOOP, interval, onCallBack, args);
            AddTimer(timeTask);
            return timeTask;
        }

        public void AddTimer(TimerTask timerTask)
        {
            if (null != timerTask)
            {
                if (!timerTask.isFrameUpdate)
                {
                    if (timerTask.interval <= 0)
                    {
                        timerTask.CallFunc();
                        return;
                    }
                }

                timerTaskList.Add(timerTask);
            }
        }

        public void ClearAll()
        {
            timerTaskList.Clear();
            timerTaskTriggers.Clear();
        }

        public float GetTriggerTime(float interval)
        {
            return GetTime() + interval;
        }

        private void OnDestroy()
        {
            ClearAll();
            timerTaskList = null;
            timerTaskTriggers = null;
        }
    }
}