using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Chernobyl.Utility;

namespace Chernobyl
{
    /// <summary>
    /// Combines the invocation of two methods into one method. This type will
    /// invoke the method passed to its constructor when both <see cref="First(T1)"/>
    /// and <see cref="Second(T2)"/> have been invoked.
    /// </summary>
    /// <typeparam name="T1">The argument type of <see cref="First(T1)"/>.</typeparam>
    /// <typeparam name="T2">The argument type of <see cref="Second(T2)"/>.</typeparam>
    public class ActionCombiner<T1, T2>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ActionCombiner{T1, T2}"/> class.
        /// </summary>
        /// <param name="action">The method that is to be invoked once 
        /// <see cref="First(T1)"/> and <see cref="Second(T2)"/> have both been 
        /// invoked.</param>
        public ActionCombiner(Action<T1, T2> action)
        {
            action.ThrowIfNull("action");
            _action = action;
        }

        /// <summary>
        /// An <see cref="Action{T1}"/> method that must be invoked for the main
        /// <see cref="Action{T1, T2}"/> to be invoked.
        /// </summary>
        /// <param name="first">The argument that is to be passed to the
        /// <see cref="Action{T1, T2}"/> method.</param>
        public void First(T1 first)
        {
            _first = first;
            _methodsInvoked[(int)Method.First] = true;

            // If the second method has been invoked, invoke _action.
            if (_methodsInvoked[(int)Method.Second])
                _action(_first, _second);
        }

        /// <summary>
        /// An <see cref="Action{T1}"/> method that must be invoked for the main
        /// <see cref="Action{T1, T2}"/> to be invoked.
        /// </summary>
        /// <param name="second">The argument that is to be passed to the
        /// <see cref="Action{T1, T2}"/> method.</param>
        public void Second(T2 second)
        {
            _second = second;
            _methodsInvoked[(int)Method.Second] = true;

            // If the first method has been invoked, invoke _action.
            if (_methodsInvoked[(int)Method.First])
                _action(_first, _second);
        }

        /// <summary>
        /// The method that is to be invoked once <see cref="First(T1)"/> and
        /// <see cref="Second(T2)"/> have both been invoked.
        /// </summary>
        readonly Action<T1, T2> _action;

        /// <summary>
        /// The argument that was passed to <see cref="First(T1)"/> when it was
        /// invoked. <c>default(T1)</c> if <see cref="First(T1)"/> has not yet
        /// been invoked.
        /// </summary>
        T1 _first;

        /// <summary>
        /// The argument that was passed to <see cref="Second(T2)"/> when it was
        /// invoked. <c>default(T1)</c> if <see cref="Second(T2)"/> has not yet
        /// been invoked.
        /// </summary>
        T2 _second;

        /// <summary>
        /// Flags for which methods have been invoked. The <see cref="First(T1)"/>
        /// is the first index, <see cref="Second(T2)"/> is the second index. If
        /// a value is true then that method has been invoked, false if otherwise.
        /// </summary>
        readonly bool[] _methodsInvoked = new bool[(int)Method.Count];

        /// <summary>
        /// The indices of the method flags in <see cref="_methodsInvoked"/>.
        /// </summary>
        enum Method
        {
            /// <summary>
            /// The index of <see cref="ActionCombiner{T1, T2}.First(T1)"/>.
            /// </summary>
            First,

            /// <summary>
            /// The index of <see cref="ActionCombiner{T1, T2}.Second(T2)"/>.
            /// </summary>
            Second,

            /// <summary>
            /// Number of items in this enum.
            /// </summary>
            Count
        }
    }

    /// <summary>
    /// Combines the invocation of two methods into one method. This type will
    /// invoke the method passed to its constructor when <see cref="First(T1)"/>,
    /// <see cref="Second(T2)"/>, and <see cref="Third(T3)"/> have been invoked.
    /// </summary>
    /// <typeparam name="T1">The argument type of <see cref="First(T1)"/>.</typeparam>
    /// <typeparam name="T2">The argument type of <see cref="Second(T2)"/>.</typeparam>
    /// <typeparam name="T3">The argument type of <see cref="Third(T3)"/>.</typeparam>
    public class ActionCombiner<T1, T2, T3>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ActionCombiner{T1, T2}"/> class.
        /// </summary>
        /// <param name="action">The method that is to be invoked once 
        /// <see cref="First(T1)"/>, <see cref="Second(T2)"/>, and
        /// <see cref="Third(T3)"/> have both been invoked.</param>
        public ActionCombiner(Action<T1, T2, T3> action)
        {
            action.ThrowIfNull("action");
            _action = action;
        }

        /// <summary>
        /// An <see cref="Action{T1}"/> method that must be invoked for the main
        /// <see cref="Action{T1, T2, T3}"/> to be invoked.
        /// </summary>
        /// <param name="first">The argument that is to be passed to the
        /// <see cref="Action{T1, T2, T3}"/> method.</param>
        public void First(T1 first)
        {
            _first = first;
            _methodsInvoked[(int)Method.First] = true;

            // If the second method has been invoked, invoke _action.
            if (_methodsInvoked[(int)Method.Second] && _methodsInvoked[(int)Method.Third])
                _action(_first, _second, _third);
        }

        /// <summary>
        /// An <see cref="Action{T1}"/> method that must be invoked for the main
        /// <see cref="Action{T1, T2, T3}"/> to be invoked.
        /// </summary>
        /// <param name="second">The argument that is to be passed to the
        /// <see cref="Action{T1, T2, T3}"/> method.</param>
        public void Second(T2 second)
        {
            _second = second;
            _methodsInvoked[(int)Method.Second] = true;

            // If the first method has been invoked, invoke _action.
            if (_methodsInvoked[(int)Method.First] && _methodsInvoked[(int)Method.Third])
                _action(_first, _second, _third);
        }

        /// <summary>
        /// An <see cref="Action{T1}"/> method that must be invoked for the main
        /// <see cref="Action{T1, T2, T3}"/> to be invoked.
        /// </summary>
        /// <param name="third">The argument that is to be passed to the
        /// <see cref="Action{T1, T2, T3}"/> method.</param>
        public void Third(T3 third)
        {
            _third = third;
            _methodsInvoked[(int)Method.Third] = true;

            // If the first method has been invoked, invoke _action.
            if (_methodsInvoked[(int)Method.First] && _methodsInvoked[(int)Method.Second])
                _action(_first, _second, _third);
        }

        /// <summary>
        /// The method that is to be invoked once <see cref="First(T1)"/> and
        /// <see cref="Second(T2)"/> have both been invoked.
        /// </summary>
        readonly Action<T1, T2, T3> _action;

        /// <summary>
        /// The argument that was passed to <see cref="First(T1)"/> when it was
        /// invoked. <c>default(T1)</c> if <see cref="First(T1)"/> has not yet
        /// been invoked.
        /// </summary>
        T1 _first;

        /// <summary>
        /// The argument that was passed to <see cref="Second(T2)"/> when it was
        /// invoked. <c>default(T1)</c> if <see cref="Second(T2)"/> has not yet
        /// been invoked.
        /// </summary>
        T2 _second;

        /// <summary>
        /// The argument that was passed to <see cref="Third(T3)"/> when it was
        /// invoked. <c>default(T1)</c> if <see cref="Third(T3)"/> has not yet
        /// been invoked.
        /// </summary>
        T3 _third;

        /// <summary>
        /// Flags for which methods have been invoked. The <see cref="First(T1)"/>
        /// is the first index, <see cref="Second(T2)"/> is the second index. If
        /// a value is true then that method has been invoked, false if otherwise.
        /// </summary>
        readonly bool[] _methodsInvoked = new bool[(int)Method.Count];

        /// <summary>
        /// The indices of the method flags in <see cref="_methodsInvoked"/>.
        /// </summary>
        enum Method
        {
            /// <summary>
            /// The index of <see cref="ActionCombiner{T1, T2, T3}.First(T1)"/>.
            /// </summary>
            First,

            /// <summary>
            /// The index of <see cref="ActionCombiner{T1, T2, T3}.Second(T2)"/>.
            /// </summary>
            Second,

            /// <summary>
            /// The index of <see cref="ActionCombiner{T1, T2, T3}.Third(T3)"/>.
            /// </summary>
            Third,

            /// <summary>
            /// Number of items in this enum.
            /// </summary>
            Count
        }
    }
}