using System;
using System.Collections.Generic;

namespace Mix.Tool.ModelEditor.Project
{
    /// <summary>
    /// バックグラウンドタスクインターフェース
    /// </summary>
    public interface IBackgroundTask
    {
        void OnExecute();
        void OnFinish();
    }

    /// <summary>
    /// バックグラウンドワーカークラス
    /// </summary>
    public class BackgroundWorker : System.IDisposable
    {
        private class InnerTask
        {
            private IBackgroundTask task = null;

            public IBackgroundTask Task
            {
                set { this.task = value; }
            }

            public System.EventHandler Finished = null;

            public virtual void Execute()
            {
                this.task.OnExecute();
            }

            public virtual void Finish()
            {
                this.task.OnFinish();

                if (this.Finished != null)
                {
                    this.Finished(this.task, new System.EventArgs());
                }
            }
        }

        private class TaskQueue
        {
            private System.Collections.Generic.Queue<InnerTask> queue = new Queue<InnerTask>();
            private System.Threading.Semaphore emptySema = null;
            private System.Threading.Semaphore countSema = null;

            public bool IsEmpty
            {
                get
                {
                    try
                    {
                        bool ret = false;

                        lock (this.queue)
                        {
                            ret = this.queue.Count == 0;
                        }

                        return ret;
                    }
                    catch
                    {
                        throw;
                    }
                }
            }

            public TaskQueue(int maxTask)
            {
                if (maxTask <= 0)
                {
                    throw new System.ArgumentOutOfRangeException("maxTask は 1 以上でなければなりません");
                }

                this.emptySema = new System.Threading.Semaphore(maxTask, maxTask);
                this.countSema = new System.Threading.Semaphore(0, maxTask);
            }

            public void Enqueue(InnerTask task)
            {
                try
                {
                    this.emptySema.WaitOne();

                    lock (this.queue)
                    {
                        this.queue.Enqueue(task);
                    }

                    this.countSema.Release();
                }
                catch
                {
                    throw;
                }
            }

            public InnerTask Dequeue()
            {
                try
                {
                    InnerTask task = null;

                    this.countSema.WaitOne();

                    lock (this.queue)
                    {
                        task = this.queue.Dequeue();
                    }

                    this.emptySema.Release();

                    return task;
                }
                catch
                {
                    throw;
                }
            }

            public InnerTask DequeueNoWait()
            {
                InnerTask task = null;

                if (this.countSema.WaitOne(0) == true)
                {
                    lock (this.queue)
                    {
                        task = this.queue.Dequeue();
                    }

                    this.emptySema.Release();
                }

                return task;
            }
        }

        private class TaskQueueNoWait
        {
            private System.Collections.Generic.Queue<InnerTask> queue = new Queue<InnerTask>();

            public bool IsEmpty
            {
                get
                {
                    try
                    {
                        bool ret = false;

                        lock (this.queue)
                        {
                            ret = this.queue.Count == 0;
                        }

                        return ret;
                    }
                    catch
                    {
                        throw;
                    }
                }
            }

            public void Enqueue(InnerTask task)
            {
                try
                {
                    lock (this.queue)
                    {
                        this.queue.Enqueue(task);
                    }
                }
                catch
                {
                    throw;
                }
            }

            public InnerTask Dequeue()
            {
                try
                {
                    lock (this.queue)
                    {
                        if (this.queue.Count > 0)
                        {
                            return this.queue.Dequeue();
                        }
                        else
                        {
                            return null;
                        }
                    }
                }
                catch
                {
                    throw;
                }
            }
        }

        private TaskQueue executeQueue = null;
        private TaskQueueNoWait finishQueue = null;
        private System.Threading.Thread thread = null;
        private System.Threading.EventWaitHandle joinEvent = null;
        private bool isDisposed = false;

        /// <summary>
        /// 破棄されているかどうかの取得
        /// </summary>
        public bool IsDispoed
        {
            get { return this.isDisposed; }
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="maxTask"></param>
        public BackgroundWorker(int maxTask)
        {
            if (maxTask <= 0)
            {
                throw new System.ArgumentOutOfRangeException("maxTask は 1 以上でなければなりません");
            }

            this.executeQueue = new TaskQueue(maxTask);
            this.finishQueue = new TaskQueueNoWait();

            this.joinEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.ManualReset);

            this.thread = new System.Threading.Thread(ExecuteThread);
            this.thread.Start(this);
        }

        /// <summary>
        /// すべてのタスクが完了次第、破棄します
        /// </summary>
        public void Dispose()
        {
            if (this.isDisposed == true)
            {
                throw new System.InvalidOperationException();
            }

            //終了を通知しジョインイベントを待機
            this.executeQueue.Enqueue(null);
            this.joinEvent.WaitOne();

            //フィニッシュキューが空になるまでループ
            while (this.finishQueue.IsEmpty == false)
            {
                this.Poll();
                System.Threading.Thread.Sleep(0);
            }
            
            this.isDisposed = true;
        }

        /// <summary>
        /// タスクを追加します
        /// </summary>
        /// <param name="task"></param>
        /// <param name="executedEvent"></param>
        public void AddTask(IBackgroundTask task, System.EventHandler finishedEvent)
        {
            if (this.isDisposed == true)
            {
                throw new System.InvalidOperationException();
            }

            if (task == null)
            {
                throw new System.ArgumentNullException("task");
            }

            InnerTask innerTask = new InnerTask();

            innerTask.Task = task;
            innerTask.Finished = finishedEvent;

            this.executeQueue.Enqueue(innerTask);
        }

        /// <summary>
        /// 更新します
        /// </summary>
        public void Poll()
        {
            InnerTask task = this.finishQueue.Dequeue();

            if (task != null)
            {
                task.Finish();
            }
        }

        private void ExecuteThread(object obj)
        {
            InnerTask task = null;

            do
            {
                task = this.executeQueue.Dequeue();

                if (task != null)
                {
                    task.Execute();
                    this.finishQueue.Enqueue(task);
                }
            }
            while (task != null);

            this.joinEvent.Set();
        }
    }
}
