﻿using System;
using System.Collections;
using JLGames.RocketDriver.CSharp;
using UnityEngine;

namespace JLGames.RocketDriver.Actions.Wait
{
    /// <summary>
    /// Nesting in coroutines is not recommended
    /// 不建议在协程中嵌套使用
    /// There is a limit on the number of coroutines after nesting, max 16?
    /// 协程嵌套后有数量限制,最大16层？
    /// </summary>
    public class WaitBehaviour : MonoBehaviour
    {
        private readonly WaitCalls m_EndOfFrame = new WaitCalls();
        private readonly WaitCalls m_FixedUpdate = new WaitCalls();

        public Coroutine InvokdEndOfFrame(Callback call, int weight = 0)
        {
            if (null == call) return null;

            var waitCall = WaitCall.Instantiate(call, weight);
            m_EndOfFrame.AddWaitCall(waitCall);
            m_EndOfFrame.Sort();

            if (m_EndOfFrame.Coroutine == null)
            {
                m_EndOfFrame.Coroutine = StartCoroutine(WaitForEndOfFrame_Coroutine());
            }

            return m_EndOfFrame.Coroutine;
        }

        public Coroutine InvokdEndOfFrame(Action call, int weight = 0)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(); }, null);
            return InvokdEndOfFrame(callback, weight);
        }

        public Coroutine InvokdEndOfFrame<T>(Action<T> call, T arg, int weight = 0)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(arg); }, null);
            return InvokdEndOfFrame(callback, weight);
        }

        public Coroutine InvokdFixedUpdate(Callback call, int weight = 0)
        {
            if (null == call) return null;

            m_FixedUpdate.AddWaitCall(WaitCall.Instantiate(call, weight));
            m_FixedUpdate.Sort();

            if (m_FixedUpdate.Coroutine == null)
            {
                m_FixedUpdate.Coroutine = StartCoroutine(WaitForFixedUpdate_Coroutine());
            }

            return m_FixedUpdate.Coroutine;
        }

        public Coroutine InvokdFixedUpdate(Action call, int weight = 0)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(); }, null);
            return InvokdFixedUpdate(callback, weight);
        }

        public Coroutine InvokdFixedUpdate<T>(Action<T> call, T arg, int weight = 0)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(arg); }, null);
            return InvokdFixedUpdate(callback, weight);
        }

        public Coroutine InvokdSeconds(Callback call, float second)
        {
            if (null == call || second < 0) return null;
            if (Mathf.Approximately(second, 0))
            {
                call.Invoke();
                return null;
            }

            return StartCoroutine(WaitForSeconds_Coroutine(second, call));
        }

        public Coroutine InvokdSeconds(Action call, float second)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(); }, null);
            return InvokdSeconds(callback, second);
        }

        public Coroutine InvokdSeconds<T>(Action<T> call, T arg, float second)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(arg); }, null);
            return InvokdSeconds(callback, second);
        }

        public Coroutine InvokdSecondsRealtime(Callback call, float second)
        {
            if (null == call || second < 0) return null;
            if (Mathf.Approximately(second, 0))
            {
                call.Invoke();
                return null;
            }

            return StartCoroutine(WaitForSecondsRealtime_Coroutine(second, call));
        }

        public Coroutine InvokdSecondsRealtime(Action call, float second)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(); }, null);
            return InvokdSecondsRealtime(callback, second);
        }

        public Coroutine InvokdSecondsRealtime<T>(Action<T> call, T arg, float second)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(arg); }, null);
            return InvokdSecondsRealtime(callback, second);
        }

        public Coroutine InvokdUntil(Callback call, Func<bool> predicate)
        {
            if (null == call || null == predicate) return null;
            return StartCoroutine(WaitForUntil_Coroutine(predicate, call));
        }

        public Coroutine InvokdUntil(Action call, Func<bool> predicate)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(); }, null);
            return InvokdUntil(callback, predicate);
        }

        public Coroutine InvokdUntil<T>(Action<T> call, T arg, Func<bool> predicate)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(arg); }, null);
            return InvokdUntil(callback, predicate);
        }

        public Coroutine InvokdWhile(Callback call, Func<bool> predicate)
        {
            if (null == call || null == predicate) return null;
            return StartCoroutine(WaitForWhile_Coroutine(predicate, call));
        }

        public Coroutine InvokdWhile(Action call, Func<bool> predicate)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(); }, null);
            return InvokdWhile(callback, predicate);
        }

        public Coroutine InvokdWhile<T>(Action<T> call, T arg, Func<bool> predicate)
        {
            if (null == call) return null;

            var callback = new Callback(delegate { call.Invoke(arg); }, null);
            return InvokdWhile(callback, predicate);
        }

        //-------------

        protected IEnumerator WaitForEndOfFrame_Coroutine()
        {
            yield return new WaitForEndOfFrame();
            m_EndOfFrame.Invoke();
        }

        protected IEnumerator WaitForFixedUpdate_Coroutine()
        {
            yield return new WaitForFixedUpdate();
            m_FixedUpdate.Invoke();
        }

        protected IEnumerator WaitForSeconds_Coroutine(float seconds, Callback call)
        {
            yield return new WaitForSeconds(seconds);
            call.Invoke();
        }

        protected IEnumerator WaitForSecondsRealtime_Coroutine(float seconds, Callback call)
        {
            yield return new WaitForSecondsRealtime(seconds);
            call.Invoke();
        }

        protected IEnumerator WaitForUntil_Coroutine(Func<bool> predicate, Callback call)
        {
            yield return new WaitUntil(predicate);
            call.Invoke();
        }

        protected IEnumerator WaitForWhile_Coroutine(Func<bool> predicate, Callback call)
        {
            yield return new WaitWhile(predicate);
            call.Invoke();
        }
    }
}