/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */
using Live2D.Cubism.Core;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
namespace Live2D.Cubism.Framework.Tasking
{
    /// 
    /// Built-in task handler, works async.
    /// 
    public static class CubismBuiltinAsyncTaskHandler
    {
        #region Workers
        /// 
        /// s waiting for execution.
        /// 
        private static Queue Tasks { get; set; }
        /// 
        /// Background worker threads.
        /// 
        private static Thread Worker { get; set; }
        /// 
        /// Lock for syncing access to  and .
        /// 
        private static object Lock { get; set; }
        /// 
        /// Signal for waking up workers.
        /// 
        private static ManualResetEvent Signal { get; set; }
        /// 
        ///  backing field. ALWAYS ACCESS THROUGH PROPERTY!
        /// 
        private static bool _callItADay;
        /// 
        /// True if workers should exit.
        /// 
        private static bool CallItADay
        {
            get
            {
                lock (Lock)
                {
                    return _callItADay;
                }
            }
            set
            {
                lock (Lock)
                {
                    _callItADay = value;
                }
            }
        }
        /// 
        /// Initializes async task handling.
        /// 
        public static void Activate()
        {
            // Check if it is already set.
            if (CubismTaskQueue.OnTask != null && CubismTaskQueue.OnTask != EnqueueTask)
            {
                Debug.LogWarning("\"CubismTaskQueue.OnTask\" already set.");
                return;
            }
            // Initialize fields.
            Tasks = new Queue();
            Worker = new Thread(Work);
            Lock = new object();
            Signal = new ManualResetEvent(false);
            CallItADay = false;
            // Become handler.
            CubismTaskQueue.OnTask = EnqueueTask;
            // Start worker.
            Worker.Start();
        }
        /// 
        /// Cleanup workers.
        /// 
        public static void Deactivate()
        {
            // Return early if self isn' handler.
            if (CubismTaskQueue.OnTask != EnqueueTask)
            {
                return;
            }
            // Unbecome handler.
            CubismTaskQueue.OnTask = null;
            // Stop worker.
            CallItADay = true;
            if (Worker != null)
            {
                Signal.Set();
                Worker.Join();
            }
            // Reset fields
            Tasks = null;
            Worker = null;
            Lock = null;
            Signal = null;
        }
        /// 
        /// Enqueues a new task.
        /// 
        /// Task to enqueue.
        private static void EnqueueTask(ICubismTask task)
        {
            lock (Lock)
            {
                Tasks.Enqueue(task);
                Signal.Set();
            }
        }
        /// 
        /// Dequeues a task.
        /// 
        /// A valid task on success;  otherwise.
        private static ICubismTask DequeueTask()
        {
            lock (Lock)
            {
                return (Tasks.Count > 0)
                    ? Tasks.Dequeue()
                    : null;
            }
        }
        /// 
        /// Entry point for workers.
        /// 
        private static void Work()
        {
            while (!CallItADay)
            {
                // Try to dequeue a task.
                var task = DequeueTask();
                // Execute task if available.
                if (task != null)
                {
                    task.Execute();
                }
                // Wait for a task to become available.
                else
                {
                    Signal.WaitOne();
                    Signal.Reset();
                }
            }
        }
        #endregion
    }
}