// WS-POS version 1.3 参照実装
// 
// 実装 version 0.6 

using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.Win32.SafeHandles;

namespace WSPOSInitiative.Common
{
    /// <summary>
    /// イベント待ち用・同期用の実装
    /// イベント待ち・同期のコードは、言語処理系の実装に依存するため
    /// 他の処理系・言語系への移植のために隠ぺいします。
    /// 
    /// Implementations for waiting event and synchronization.
    /// Since the code for event waiting and synchronization depends on the implementation language, 
    /// hides for transplantation into the other processing systems and languages.
    /// </summary>
    public class EventWait : IEventWait
    {
        /// <summary>
        /// イベント待ちハンドルの実体
        /// Event wait handles entities
        /// </summary>
        private IntPtr _waitHandle = IntPtr.Zero;
        //private EventWaitHandle _waitHandle = new AutoResetEvent(false);


        /// <summary>
        /// Win32 API メッセージループをしながらイベント待ちを行う
        /// It waits for an event, processing a Win32 API message loop. 
        /// </summary>
        [DllImport("user32.dll")]
        static extern uint MsgWaitForMultipleObjects(uint nCount, IntPtr[] pHandles,
           bool bWaitAll, int dwMilliseconds, uint dwWakeMask);

        const uint QS_KEY = 0x1;
        const uint QS_MOUSEMOVE = 0x2;
        const uint QS_MOUSEBUTTON = 0x4;
        const uint QS_MOUSE = (QS_MOUSEMOVE | QS_MOUSEBUTTON);
        const uint QS_INPUT = (QS_MOUSE | QS_KEY);
        const uint QS_POSTMESSAGE = 0x8;
        const uint QS_TIMER = 0x10;
        const uint QS_PAINT = 0x20;
        const uint QS_SENDMESSAGE = 0x40;
        const uint QS_HOTKEY = 0x80;
        const uint QS_REFRESH = (QS_HOTKEY | QS_KEY | QS_MOUSEBUTTON | QS_PAINT);
        const uint QS_ALLEVENTS = (QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY);
        const uint QS_ALLINPUT = (QS_SENDMESSAGE | QS_PAINT | QS_TIMER | QS_POSTMESSAGE | QS_MOUSEBUTTON | QS_MOUSEMOVE | QS_HOTKEY | QS_KEY);
        const uint QS_ALLPOSTMESSAGE = 0x100;
        const uint QS_RAWINPUT = 0x400;
        const uint WAIT_OBJECT_0 = 0;
        const uint WAIT_ABANDONED_0 = 0x80;
        const uint WAIT_FAILED = 0xFFFFFFFF;
        const uint WAIT_TIMEOUT = 0x102;

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, IntPtr lpName);
        [DllImport("kernel32.dll")]
        static extern bool SetEvent(IntPtr hEvent);
        [DllImport("kernel32.dll")]
        static extern bool ResetEvent(IntPtr hEvent);
        [DllImport("kernel32.dll")]
        static extern bool CloseHandle(IntPtr hObject);

        public EventWait()
        {
            _waitHandle = CreateEvent(IntPtr.Zero, false, false, IntPtr.Zero);
        }

        /// <summary>
        /// 同期リソースのシグナル化を待つ
        /// Wait for the signal of sync resources
        /// </summary>
        /// <param name="millisecondsTimeout">タイムアウトを指定(ミリ秒) | Specifies the time-out (in milliseconds)</param>
        /// <returns>同期リソースがシグナル化したか否か | Did the synchronous resource signal-ize or not? </returns>
        public bool Wait(int millisecondsTimeout)
        {
            var loopCore = Thread.GetData(Thread.GetNamedDataSlot(WorkerThreadHolder.LoopCoreThreadSlotName)) as WorkerThreadHolder.WorkerThreadCore;

            if (loopCore == null)
            {
                IntPtr[] handles = new IntPtr[1];
                handles[0] = _waitHandle;

                while (true)
                {
                    Application.DoEvents();
                    uint waitResult = MsgWaitForMultipleObjects(1, handles, false, millisecondsTimeout, QS_ALLINPUT);
                    switch (waitResult)
                    {
                        case WAIT_ABANDONED_0:
                        case WAIT_OBJECT_0:
                            return true;
                        case WAIT_TIMEOUT:
                        case WAIT_FAILED:
                            return false;
                    }
                }
            }
            else
            {
                IntPtr[] handles = new IntPtr[1];
                handles[0] = _waitHandle;

                DateTime beforeDateTime = DateTime.Now;
                
                while (true)
                {
                    bool loopRet = loopCore();
                    if (loopRet == false)
                    {
                        return false;
                    }

                    uint waitResult = MsgWaitForMultipleObjects(1, handles, false, 1, QS_ALLINPUT);
                    switch (waitResult)
                    {
                        case WAIT_ABANDONED_0:
                        case WAIT_OBJECT_0:
                            return true;
                        case WAIT_TIMEOUT:
                            if (millisecondsTimeout == Timeout.Infinite || 
                                (DateTime.Now - beforeDateTime).TotalMilliseconds < millisecondsTimeout)
                            {
                                continue;
                            }
                            return false;
                        case WAIT_FAILED:
                            return false;
                    }
                }

            }


            //メッセージループを行いながらイベント待ちをしたいため、下記ロジックはコメントアウト
            //bool waitRet = _waitHandle.WaitOne(millisecondsTimeout);
            //return waitRet;
        }

        /// <summary>
        /// 同期リソースをシグナル化する
        /// A synchronous resource is signal-ized. 
        /// </summary>
        /// <returns>正常にシグナル化できたか否か | If successfully managed signaling or not</returns>
        public bool Signal()
        {
            bool ret = SetEvent(_waitHandle);
            return ret;
            //return _waitHandle.Set();
        }

        /// <summary>
        /// 同期リソースを非シグナル化する
        /// Non-signaling synchronization resources
        /// </summary>
        /// <returns>正常に非シグナル化できたか否か | Successfully able to non-signal or not?</returns>
        public bool NonSignal()
        {
            return ResetEvent(_waitHandle);
            //return _waitHandle.Reset();
        }

        /// <summary>
        /// 同期リソースを破棄する
        /// Discard the synchronization resources
        /// </summary>
        public void Dispose()
        {
            CloseHandle(_waitHandle);
            _waitHandle = IntPtr.Zero;
            //_waitHandle.Close();
            //_waitHandle.Dispose(); .NET FW 3ではDisposeを呼べない
        }

        /// <summary>
        /// 渡された複数の同期リソースのいずれかのシグナル化を待つ
        /// Wait for the signaling of one of multiple synchronous resource passed.
        /// </summary>
        /// <param name="waits">同期リソースのリスト | List of synchronous resource</param>
        /// <param name="millisecondsTimeout">タイムアウト指定(ミリ秒) | timeout (millisecondsTimeout)</param>
        /// <returns>シグナル化した同期リソースwaitsのインデックス | index of waits which signalized.</returns>
        public int WaitForAny(IEventWait[] waits, int millisecondsTimeout)
        {
            IntPtr[] safeHandles = new IntPtr[waits.Length];
            for (int i = 0; i < waits.Length; i++)
            {
                safeHandles[i] = ((EventWait)waits[i])._waitHandle;
            }
            uint handleCount = (uint)safeHandles.Length;

            while (true)
            {
                uint waitResult = MsgWaitForMultipleObjects(handleCount, safeHandles, false, millisecondsTimeout, QS_ALLINPUT);

                if (WAIT_OBJECT_0 <= waitResult && waitResult < handleCount)
                {
                    return (int)(waitResult - WAIT_OBJECT_0);
                }
                if (WAIT_ABANDONED_0 <= waitResult && waitResult < (WAIT_ABANDONED_0 + handleCount))
                {
                    return (int)(waitResult - WAIT_ABANDONED_0);
                }
                if (waitResult == WAIT_TIMEOUT || waitResult == WAIT_FAILED)
                {
                    return WaitHandle.WaitTimeout;
                }
                Application.DoEvents();
            }


            //WaitHandle[] waitHandles = new WaitHandle[waits.Length];
            //for (int i = 0; i < waits.Length; i++)
            //{
            //    if ((waits[i] is EventWait) == false)
            //    {
            //        throw new InvalidOperationException("It is only the instance array of the same implement class which can be passed to EventWait.WaitForAny.");
            //    }
            //    waitHandles[i] = ((EventWait)waits[i])._waitHandle;
            //}

            //return WaitHandle.WaitAny(waitHandles, millisecondsTimeout);
        }
    }
}
