/**
 * 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.Unmanaged;
using System.Threading;
namespace Live2D.Cubism.Core
{
    /// 
    /// 'Atomic'  update task.
    /// 
    internal sealed class CubismTaskableModel : ICubismTask
    {
        #region Factory Methods
        /// 
        /// Creates a  from a .
        /// 
        /// Moc source.
        /// Instance.
        public static CubismTaskableModel CreateTaskableModel(CubismMoc moc)
        {
            return new CubismTaskableModel(moc);
        }
        #endregion
        /// 
        /// Handle to unmanaged model.
        /// 
        public CubismUnmanagedModel UnmanagedModel { get; private set; }
        /// 
        ///  the model was instantiated from.
        /// 
        public CubismMoc Moc { get; private set; }
        private CubismDynamicDrawableData[] _dynamicDrawableData;
        /// 
        /// Buffer to write dynamic data to.
        /// 
        public CubismDynamicDrawableData[] DynamicDrawableData
        {
            get
            {
                CubismDynamicDrawableData[] dynamicDrawableData = null;
                if (Monitor.TryEnter(Lock))
                {
                    dynamicDrawableData = _dynamicDrawableData;
                    Monitor.Exit(Lock);
                }
                return dynamicDrawableData;
            }
            private set
            {
                _dynamicDrawableData = value;
            }
        }
        /// 
        /// True if task is currently executing.
        /// 
        public bool IsExecuting
        {
            get
            {
                var isExecuting = false;
                if (Monitor.TryEnter(Lock))
                {
                    isExecuting = (State == TaskState.Enqueued || State == TaskState.Executing);
                    Monitor.Exit(Lock);
                }
                return isExecuting;
            }
        }
        /// 
        /// True if did run to completion at least once.
        /// 
        public bool DidExecute
        {
            get
            {
                var didExecute = false;
                if (Monitor.TryEnter(Lock))
                {
                    didExecute = (State == TaskState.Executed);
                    Monitor.Exit(Lock);
                }
                return didExecute;
            }
        }
        /// 
        /// True if unmanaged model and moc should be released.
        /// 
        private bool ShouldReleaseUnmanaged { get; set; }
        #region Constructor
        /// 
        /// Initializes instance.
        /// 
        /// Moc unmanaged model was instantiated from.
        public CubismTaskableModel(CubismMoc moc)
        {
            Moc = moc;
            // Instantiate unmanaged model.
            var unmanagedMoc = moc.AcquireUnmanagedMoc();
            UnmanagedModel = CubismUnmanagedModel.FromMoc(unmanagedMoc);
            Lock = new object();
            State = TaskState.Idle;
            DynamicDrawableData = CubismDynamicDrawableData.CreateData(UnmanagedModel);
            ShouldReleaseUnmanaged = false;
        }
        #endregion
        /// 
        /// Tries to read parameters into a buffer.
        /// 
        /// Buffer to write to.
        ///  on success;  otherwise.
        public bool TryReadParameters(CubismParameter[] parameters)
        {
            var didRead = false;
            if (Monitor.TryEnter(Lock))
            {
                try
                {
                    if (State == TaskState.Executed)
                    {
                        parameters.ReadFrom(UnmanagedModel);
                        didRead = true;
                    }
                }
                finally
                {
                    Monitor.Exit(Lock);
                }
            }
            return didRead;
        }
        /// 
        /// Tries to write parameters to a buffer.
        /// 
        /// Buffer to read from.
        /// Buffer to read from.
        ///  on success;  otherwise.
        public bool TryWriteParametersAndParts(CubismParameter[] parameters, CubismPart[] parts)
        {
            var didWrite = false;
            if (Monitor.TryEnter(Lock))
            {
                try
                {
                    if (State != TaskState.Executing)
                    {
                        parameters.WriteTo(UnmanagedModel);
                        parts.WriteTo(UnmanagedModel);
                        didWrite = true;
                    }
                }
                finally
                {
                    Monitor.Exit(Lock);
                }
            }
            return didWrite;
        }
        /// 
        /// Dispatches the task for (maybe async) execution.
        /// 
        public void Update()
        {
            // Validate state.
            lock (Lock)
            {
                if (State == TaskState.Enqueued || State == TaskState.Executing)
                {
                    return;
                }
                // Update state.
                State = TaskState.Enqueued;
            }
            CubismTaskQueue.Enqueue(this);
        }
        /// 
        /// Forces the task to run now to completion.
        /// 
        public bool UpdateNow()
        {
            // Validate state.
            lock (Lock)
            {
                if (State == TaskState.Enqueued || State == TaskState.Executing)
                {
                    return false;
                }
                // Update state.
                State = TaskState.Enqueued;
            }
            // Run execution directly.
            Execute();
            return true;
        }
        /// 
        /// Releases unmanaged resource.
        /// 
        public void ReleaseUnmanaged()
        {
            ShouldReleaseUnmanaged = true;
            // Return if task is ongoing.
            lock (Lock)
            {
                if (State == TaskState.Enqueued || State == TaskState.Executing)
                {
                    return;
                }
            }
            OnReleaseUnmanaged();
            ShouldReleaseUnmanaged = false;
        }
        /// 
        /// Runs the task.
        /// 
        private void Execute()
        {
            // Validate state.
            lock (Lock)
            {
                State = TaskState.Executing;
            }
            // Update native backend.
            UnmanagedModel.Update();
            // Get results.
            DynamicDrawableData.ReadFrom(UnmanagedModel);
            // Update state.
            lock (Lock)
            {
                State = TaskState.Executed;
                // Release native if requested.
                if (ShouldReleaseUnmanaged)
                {
                    OnReleaseUnmanaged();
                }
            }
        }
        /// 
        /// Actually releases native resource(s).
        /// 
        private void OnReleaseUnmanaged()
        {
            UnmanagedModel.Release();
            Moc.ReleaseUnmanagedMoc();
            UnmanagedModel = null;
        }
        #region Implementation of ICubismTask
        void ICubismTask.Execute()
        {
            Execute();
        }
        #endregion
        #region Threading
        /// 
        /// Task states.
        /// 
        private enum TaskState
        {
            /// 
            /// Idle state.
            /// 
            Idle,
            /// 
            /// Waiting-for-execution state.
            /// 
            Enqueued,
            /// 
            /// Executing state.
            /// 
            Executing,
            /// 
            /// Executed state.
            /// 
            Executed
        }
        /// 
        /// Lock.
        /// 
        private object Lock { get; set; }
        /// 
        /// Internal state.
        /// 
        private TaskState State { get; set; }
        #endregion
    }
}