/****************************************************************************
* Copyright 2019 Nreal Techonology Limited. All rights reserved.
*                                                                                                                                                          
* This file is part of NRSDK.                                                                                                          
*                                                                                                                                                           
* https://www.nreal.ai/        
* 
*****************************************************************************/

namespace NRKernal
{
    using System;
    using System.Collections.Generic;
    using UnityEngine;

    /// <summary> A class used for monitoring the status of an asynchronous task. </summary>
    /// <typeparam name="T"> The resultant type of the task.</typeparam>
    public class AsyncTask<T>
    {
        /// <summary>
        /// A collection of actons to perform on the main Unity thread after the task is complete. </summary>
        private List<Action<T>> m_ActionsUponTaskCompletion;

        /// <summary> Constructor for AsyncTask. </summary>
        /// <param name="asyncOperationComplete"> [out] A callback that, when invoked, changes the status
        ///                                       of the task to complete and sets the result based on
        ///                                       the argument supplied.</param>
        public AsyncTask(out Action<T> asyncOperationComplete)
        {
            // Register for early update event.
            if (!AsyncTask.IsInitialized)
            {
                AsyncTask.Init();
            }

            IsComplete = false;
            asyncOperationComplete = delegate (T result)
            {
                Result = result;
                IsComplete = true;
                if (m_ActionsUponTaskCompletion != null)
                {
                    AsyncTask.PerformActionInUpdate(() =>
                    {
                        for (int i = 0; i < m_ActionsUponTaskCompletion.Count; i++)
                        {
                            m_ActionsUponTaskCompletion[i](result);
                        }
                    });
                }
            };
        }

        /// <summary> Constructor for AsyncTask that creates a completed task. </summary>
        /// <param name="result"> The result of the completed task.</param>
        internal AsyncTask(T result)
        {
            Result = result;
            IsComplete = true;
        }

        /// <summary> Gets or sets a value indicating whether the task is complete. </summary>
        /// <value> <c>true</c> if the task is complete, otherwise <c>false</c>. </value>
        public bool IsComplete { get; private set; }

        /// <summary> Gets or sets the result of a completed task. </summary>
        /// <value> The result of the completed task. </value>
        public T Result { get; private set; }

        /// <summary>
        /// Returns a yield instruction that monitors this task for completion within a coroutine. </summary>
        /// <returns> A yield instruction that monitors this task for completion. </returns>
        public CustomYieldInstruction WaitForCompletion()
        {
            return new WaitForTaskCompletionYieldInstruction<T>(this);
        }

        /// <summary>
        /// Performs an action (callback) in the first Unity Update() call after task completion. </summary>
        /// <param name="doAfterTaskComplete"> The action to invoke when task is complete.  The result
        ///                                    of the task will be passed as an argument to the action.</param>
        /// <returns> The invoking asynchronous task. </returns>
        public AsyncTask<T> ThenAction(Action<T> doAfterTaskComplete)
        {
            // Perform action now if task is already complete.
            if (IsComplete)
            {
                doAfterTaskComplete(Result);
                return this;
            }

            // Allocate list if needed (avoids allocation if then is not used).
            if (m_ActionsUponTaskCompletion == null)
            {
                m_ActionsUponTaskCompletion = new List<Action<T>>();
            }

            m_ActionsUponTaskCompletion.Add(doAfterTaskComplete);
            return this;
        }
    }

    /// <summary> Helper methods for dealing with asynchronous tasks. </summary>
    internal class AsyncTask
    {
        /// <summary> Queue of update actions. </summary>
        private static Queue<Action> m_UpdateActionQueue = new Queue<Action>();
        /// <summary> The lock object. </summary>
        private static object m_LockObject = new object();

        /// <summary> Gets or sets a value indicating whether this object is initialized. </summary>
        /// <value> True if this object is initialized, false if not. </value>
        public static bool IsInitialized { get; private set; }

        /// <summary>
        /// Queues an action to be performed on Unity thread in Update().  This method can be called by
        /// any thread. </summary>
        /// <param name="action"> The action to perform.</param>
        public static void PerformActionInUpdate(Action action)
        {
            lock (m_LockObject)
            {
                m_UpdateActionQueue.Enqueue(action);
            }
        }

        /// <summary> An Update handler called each frame. </summary>
        public static void OnUpdate()
        {
            if (m_UpdateActionQueue.Count == 0)
            {
                return;
            }

            lock (m_LockObject)
            {
                while (m_UpdateActionQueue.Count > 0)
                {
                    Action action = m_UpdateActionQueue.Dequeue();
                    action();
                }
            }
        }

        /// <summary> Initializes this object. </summary>
        public static void Init()
        {
            if (IsInitialized)
            {
                return;
            }

            NRKernalUpdater.OnUpdate += OnUpdate;
            IsInitialized = true;
        }
    }
}