using System;

namespace Chernobyl.Switch
{
    /// <summary>
    /// An <see cref="ISwitch"/> that fires an event when it's
    /// <see cref="Count"/> reaches zero. The <see cref="Count"/> is reduced by
    /// one every time the <see cref="Countdown(object, EventArgs)"/> event
    /// handler method is invoked.
    /// </summary>
    public class CountdownSwitch : ISwitch
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CountdownSwitch"/> class.
        /// </summary>
        /// <param name="count">The count of this <see cref="CountdownSwitch"/>.
        /// When this <see cref="Count"/> reaches zero, the 
        /// <see cref="SwitchedOn"/> event will be raised. This 
        /// <see cref="Count"/> is decremented every time 
        /// <see cref="Countdown(object, EventArgs)"/> is invoked. The value of
        /// this parameter must be greater than 0.</param>
        /// <exception cref="ArgumentException">Thrown if the countdown
        /// <paramref name="count"/> is 0; you cannot countdown from zero.</exception>
        public CountdownSwitch(uint count)
        {
            Count = StartingCount = count;
        }

        /// <summary>
        /// An event handler that can be assigned to an event. This method will
        /// decrement the <see cref="Count"/> by one every time it is invoked.
        /// When the <see cref="Count"/> reaches zero the 
        /// <see cref="SwitchedOn"/> event will be raised.
        /// </summary>
        /// <param name="sender">The instance that generated the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        public void Countdown(object sender, EventArgs e)
        {
            // if we are already at zero, ignore the countdown.
            if(Count != 0)
            {
                --Count;

                if (Count == 0 && SwitchedOn != null)
                    SwitchedOn(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// An event that is raised when the <see cref="Count"/> has reached 
        /// zero.
        /// </summary>
        public event EventHandler SwitchedOn;

        /// <summary>
        /// An event that is raised right after this switch has been reset using
        /// the <see cref="Reset()"/> or <see cref="Reset(object, EventArgs)"/>
        /// methods.
        /// </summary>
        public event EventHandler SwitchedOff;

        /// <summary>
        /// Resets the switch to its original state or the its state after right
        /// after its creation. This method will invoke the 
        /// <see cref="SwitchedOff"/> event.
        /// </summary>
        public void Reset()
        {
            Count = StartingCount;

            if(SwitchedOff != null)
                SwitchedOff(this, EventArgs.Empty);
        }

        /// <summary>
        /// An event handler that can be assigned to an event. This event handler
        /// will reset the switch to its original state. It is exactly the same
        /// as calling <see cref="Reset()"/>.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The arguments to the event.</param>
        public void Reset(object sender, EventArgs e)
        {
            Reset();
        }

        /// <summary>
        /// The current count of this <see cref="CountdownSwitch"/>. When this
        /// <see cref="Count"/> reaches zero, the <see cref="SwitchedOn"/> event
        /// will be invoked. This <see cref="Count"/> is decremented every time
        /// <see cref="Countdown(object, EventArgs)"/> is invoked.
        /// </summary>
        public uint Count { get; private set; }

        /// <summary>
        /// The starting value of the <see cref="Count"/>. This is the value
        /// that was received in the constructor.
        /// </summary>
        /// <exception cref="ArgumentException">Thrown if the starting count
        /// <paramref name="value"/> is set to 0; you cannot countdown from zero.</exception>
        public uint StartingCount
        {
            get { return _startingCount; }
            private set
            {
                if (value == 0)
                    throw new ArgumentException("The countdown starting count " +
                    "cannot be zero since you cannot countdown from zero.",
                    "value");

                _startingCount = value;
            }
        }

        /// <summary>
        /// The backing field to <see cref="StartingCount"/>.
        /// </summary>
        uint _startingCount;
    }
}