// == LICENSE INFORMATION ==
/*
 * First author tiritomato 2013.
 * This program is distributed under the GNU Lesser General Public License(LGPL ver3).
 * support blog (Japanese only) http://d.hatena.ne.jp/tiri_tomato/
 */
// == LICENSE INFORMATION ==

namespace DotNetEx
{
    //! @addtogroup DotNetEx名前空間
    //! @{

    //! @brief ロッククラスの生成数をカウントする鍵クラス。
    //! @details CounterKeyインスタンスから生成されたCounterKey.Lockクラスは生成時にCounterKeyのロックカウントをインクリメントし、Lock.Dispose()によってCounterKeyのロックカウントを解放します。
    public class CounterKey
    {
        //! @brief Lockクラスの挙動（異常報知オプション）を指定します。
        //! @details Dispose()処理時に想定外の挙動がある場合の対応を指定します。
        //! 広義には例外処理に相当するケースですが、Dispose()中の例外スローはあまり一般的でない事も多く、例外に代わりCounterKeyの静的イベントが生じます。
        //! さらにこれらのイベントを有効にするには、Lockオブジェクト生成時にも個別の検知設定を行ってください。
        [System.Flags]
        public enum LockMode
        {
            //! @brief 規定値。なんのオプションも指定されません。
            None = 0x0,
            //! @brief 指定すると解放しすぎが生じた時にCounterKey.OverPopイベントを生じます（通常ありえない事態なので、明示的にこのフラグを指定しない限り、解放しすぎイベントはデフォルトで起動します）。 
            NotRaiseOverpopEvent = 0x1,
            //! @brief Lockクラスのファイナライザが作動すると「明示的なDispose忘れ」としてCounterKey.FinalizedWithoutExplicitDisposeイベントを生じます。
            //! using文を使用するか明示的Dispose()を呼んでおけばこのイベントは起こりません。
            RaiseFinalizedWithoutExplicitDisposeEvent = 0x2, 
            //! @brief 規定値。なんのオプションも指定されません(常にNoneの別名として定義されます)。
            Default = None,
        }

        //! @brief CounterKeyの参照カウント管理クラス
        //! @details CounterKeyからLockクラスを作成すると、LockクラスはCounterKeyの参照カウントをインクリメントして、Lock.Dispose(またはファイナライザ)でCounterKeyの参照カウントをデクリメントします。
        //! GC時に異常報知したい時ファイナライザを禁止したい場合はUsingLock()を参照してください。(通常、明示的Dispose()がされるかusing文であればGCは起きない設計)
        public class Lock : System.IDisposable
        {
            //! @brief コンストラクタで渡されたCounterKeyインスタンスとの関係がDispose済み、あるいはnullでコンストラクトされているか確認します。
            public bool IsDisposed { get { lock (m_lock) return m_target != null; } }

            //! @brief コンストラクタ。
            public Lock(CounterKey target)
            {
                m_lock = new System.Object();
                m_disposeLock = new System.Object();
                if ((m_target = target) != null) lock (target) target.m_ct++;
            }

            //! @brief ファイナライザ
            ~Lock() { Dispose(false); }

            //! @brief 登録されたCounterKeyのカウントを下げて、CounterKeyとの関係を消去します。usingまたは明示的なDispose()呼び出しを推奨します。
            public void Dispose()
            {
                lock (m_disposeLock)
                {
                    Dispose(true);
                    System.GC.SuppressFinalize(this);
                }
            }

            //! @brief Disposeの実装メソッド
            //! @details @param [in] ExplicitDispose 明示的なDisposeであればtrue、ファイナライザが作動している場合はfalse。
            //! （なお、一度でも明示的Dispose()が行われるとファイナライザは作動しなくなります）
            protected virtual void Dispose(bool ExplicitDispose)
            {
                lock (m_lock)
                {
                    CounterKey lock_target = m_target;
                    if (m_target != null)
                    {
                        lock (lock_target)
                        {
                            LockMode mode = m_target.Mode;
                            if ((ExplicitDispose == false) && ((mode | LockMode.RaiseFinalizedWithoutExplicitDisposeEvent) == LockMode.RaiseFinalizedWithoutExplicitDisposeEvent))
                            {
                                CounterKey.OnFinalizedWithoutExplicitDispose(this);
                            }
                            if (m_target.m_ct == 0)
                            {
                                if ((mode | LockMode.NotRaiseOverpopEvent) != LockMode.NotRaiseOverpopEvent) CounterKey.OnOverPop(this);
                            }
                            else m_target.m_ct--;
                            m_target = null;
                        }
                    }
                }
            }

            //! @brief コンストラクタに渡されたCounterKeyインスタンスを取得します。nullで初期化あるいはDispose済みであればこの値はnullです。
            protected CounterKey Target { get { lock (m_lock) return m_target; } }

            private CounterKey m_target;
            private readonly System.Object m_lock;
            private readonly System.Object m_disposeLock;
        }

        //! @brief デフォルトコンストラクター
        public CounterKey() : this(LockMode.Default) { }

        //! @brief コンストラクター
        public CounterKey(LockMode mode)
        {
            m_mode = mode;
            m_ct = 0;
        }

        //! @brief Lockクラスを生成します。
        public Lock GenerateLock() { return new Lock(this); }

        //! @brief CounterKeyクラスを生成します。
        //! @details LockMode.RaiseFinalizedWithoutExplicitDisposeEventで、usingステートメントなどを使用せずDisposeを忘れると異常報知するCounterKeyを生成します。
        public static CounterKey UsingKey() { return new CounterKey(LockMode.RaiseFinalizedWithoutExplicitDisposeEvent); }

        //! @brief 現在のキー参照カウントを取得します。
        public System.UInt64 KeyCount { get { return m_ct; } }

        //! @brief 初期化時に設定された異常報知オプションの取得
        public LockMode Mode { get { return m_mode; } }

        //! @brief 解放しすぎが生じた際に起こります。
        public static event System.EventHandler OverPop;

        //! @brief ファイナライズ（usingなど明示的Dispose()以外の、暗黙の自動ガベージコレクト）が生じた時に起こります。
        public static event System.EventHandler FinalizedWithoutExplicitDispose;

        //! @brief Lockオブジェクトを指定してOverPopイベントを起こします。
        protected static void OnOverPop(Lock sender)
        {
            if (OverPop != null) OverPop(sender, System.EventArgs.Empty);
        }

        //! @brief Lockオブジェクトを指定してFinalizedWithoutExplicitDisposeイベントを起こします。
        protected static void OnFinalizedWithoutExplicitDispose(Lock sender)
        {
            if (FinalizedWithoutExplicitDispose != null) FinalizedWithoutExplicitDispose(sender, System.EventArgs.Empty);
        }

        private System.UInt64 m_ct;
        private LockMode m_mode;
    }

    //! @}
}
