using System;
using System.Collections;
using System.Collections.Generic;
using Chernobyl.Collections.Generic;

namespace Chernobyl.StateMachine
{
    /// <summary>
    /// A default implementation of the <see cref="IState"/> interface. This 
    /// class is used to provide a basic state changing mechanism for states and 
    /// make it easier to create new <see cref="IState"/> types.
    /// </summary>
    public class State : IState
    {
        /// <summary>
        /// This event is raised when this state has become the active state.
        /// This can happen if this instances' <see cref="ParentState"/> has been
        /// set and the <see cref="ParentState"/> <see cref="IState"/> has had
        /// its <see cref="ChildState"/> set to this instance. It can also
        /// happen if this instance has decided to activate itself. This event
        /// will only be raised once when the state has become active. It will not be
        /// invoked a second time unless it is deactivated first.
        /// </summary>
        public event EventHandler Entered
        {
            add { _entered += value; }
            remove { _entered -= value; }
        }

        /// <summary>
        /// This event is raised when this state is no longer an active state.
        /// This can happen if this instances' <see cref="ParentState"/> has
        /// been set and the <see cref="ParentState"/> <see cref="IState"/> has
        /// had its <see cref="ChildState"/> set to this instance. It can also
        /// happen if this instance has decided to deactivate itself. This event
        /// will only be raised once when the state has been deactivated. It will
        /// not be raised a second time unless it was previously in an active
        /// state.
        /// </summary>
        public event EventHandler Left
        {
            add { _left += value; }
            remove { _left -= value; }
        }

        /// <summary>
        /// The <see cref="IState"/> that this <see cref="IState"/> is a
        /// sub-state of or null if this <see cref="IState"/> is not sub-state
        /// of another state. When set this property will set the incoming
        /// <see cref="IState"/> instances <see cref="ChildState"/> property
        /// to this instance (unless the value set is null of course). If this
        /// property is set to null (or in other words, this state is
        /// deactivated), two things will occur: (1) the <see cref="Left"/> event
        /// will be raised and (2), immediately prior to raising the
        /// <see cref="Left"/> event, this state will deactivate its child state
        /// (or sub-state) which will deactivate its child states and so and so
        /// forth. Deactivating sub-states is done prior to invoking the
        /// <see cref="Left"/> event so that sub-states will be deactivated from
        /// the lowest sub-state to the highest sub-state. For example, in a
        /// state machine setup like so: state1 -&gt; state2 -&gt; state3 -&gt; state4
        /// If state2 was deactivated, then state4 would be deactivated first
        /// (i.e., its <see cref="IState.Left"/> event would be raised first),
        /// then state3, and finally state2.
        /// </summary>
        public IState ParentState
        {
            get { return _parentState; }
            set { SetParent(ref _parentState, value, this, _entered, _left); }
        }

        /// <summary>
        /// The currently active sub-state of this <see cref="IState"/> or null
        /// if this <see cref="IState"/> doesn't currently have an active
        /// sub-state. When set this property will set the incoming
        /// <see cref="IState"/> instances <see cref="ParentState"/> property
        /// to this instance (unless the value set is null, in which case, the
        /// <see cref="IState.ChildState"/> this instances
        /// <see cref="ParentState"/> is set to null).
        /// </summary>
        public IState ChildState
        {
            get { return _childState; }
            set { SetChild(ref _childState, value, this); }
        }

        /// <summary>
        /// Returns an enumerator that iterates through a down the children of
        /// an <see cref="IState"/> system starting at this <see cref="IState"/>.
        /// </summary>
        /// <returns>
        /// The <see cref="IEnumerator{T}"/> that allows iteration over
        /// a set of linked instance.
        /// </returns>
        public IEnumerator<IState> GetLinkedEnumerator()
        {
            return new LinkedEnumerator<IState>(this, LinkedNextState);
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="IEnumerator{T}"/> that can be used to iterate through 
        /// the collection.
        /// </returns>
        public IEnumerator<IState> GetEnumerator()
        {
            return GetLinkedEnumerator();
        }

        /// <summary>
        /// A helper method for types deriving from <see cref="IState"/>. This
        /// method is used in the <see cref="ParentState"/> property to properly
        /// set the parent and handle any other tasks that are associated with
        /// setting that property. This method will ensure that the parent isn't
        /// set twice if the <paramref name="parent"/> is the same as the
        /// <paramref name="newParent"/>, ensures that the 
        /// <see cref="IState.ChildState"/> of the old parent is set to null (if
        /// applicable), ensures that the <see cref="IState.ChildState"/> of the 
        /// <paramref name="child"/> is set to null (if applicable), and ensures 
        /// the <see cref="IState.Entered"/> and <see cref="IState.Left"/> are
        /// invoked using <paramref name="entered"/> and <paramref name="left"/>
        /// respectively.
        /// </summary>
        /// <param name="parent">The previous value of the 
        /// <see cref="IState.ParentState"/> and the instance that is to take the
        /// new value (if applicable).</param>
        /// <param name="newParent">The value being set on
        /// <see cref="IState.ParentState"/>.</param>
        /// <param name="child">The instance that was the child of 
        /// <paramref name="parent"/> and will be the child of 
        /// <paramref name="newParent"/>. Typically this is the value of 'this'.</param>
        /// <param name="entered">The method that is to be invoked if the
        /// <see cref="IState.Entered"/> event is to be raised.</param>
        /// <param name="left">The method that is to be invoked if the
        /// <see cref="IState.Left"/> event is to be raised.</param>
        public static void SetParent(ref IState parent, IState newParent, IState child,
                                     EventHandler entered, EventHandler left)
        {
            // Make sure we don't enter or leave a state more than once.
            if (parent != newParent)
            {
                IState previousParentState = parent;
                parent = newParent;

                // The previous parent should no longer reference this
                // IState as it's child.
                if (previousParentState != null &&
                    previousParentState.ChildState == child)
                    previousParentState.ChildState = null;

                if (parent != null)
                {
                    if (entered != null)
                        entered(child, EventArgs.Empty);

                    parent.ChildState = child;
                }
                else
                {
                    // Because this state is being deactivated, that means
                    // that all of the sub-states of this state need to be 
                    // deactivated. We can do that by setting our child
                    // state to null which will deactivate our sub-state and
                    // it's sub-states and so on and so forth.
                    child.ChildState = null;

                    if (left != null)
                        left(child, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// A helper method for types deriving from <see cref="IState"/>. This
        /// method is used in the <see cref="ChildState"/> property to properly
        /// set the child and handle any other tasks that are associated with
        /// setting that property. This method will ensure that the parent isn't
        /// set twice if the <paramref name="child"/> is the same as the
        /// <paramref name="newChild"/>, and ensures that the 
        /// <see cref="IState.ParentState"/> of the old old child is set to null 
        /// (if applicable).
        /// </summary>
        /// <param name="child">The previous value of the 
        /// <see cref="IState.ChildState"/> and the instance that is to take the
        /// new value (if applicable).</param>
        /// <param name="newChild">The value being set on
        /// <see cref="IState.ChildState"/>.</param>
        /// <param name="parent">The instance that was the parent of 
        /// <paramref name="child"/> and will be the parent of 
        /// <paramref name="newChild"/>. Typically this is the value of 'this'.</param>
        public static void SetChild(ref IState child, IState newChild, IState parent)
        {
            if (child != newChild)
            {
                IState previousChildState = child;
                child = newChild;

                if (child != null)
                    child.ParentState = parent;

                if (previousChildState != null &&
                    previousChildState.ParentState == parent)
                {
                    // The previous child should no longer reference this
                    // IState as it's parent.
                    previousChildState.ParentState = null;
                }
            }
        }

        /// <summary>
        /// Retrieves the <see cref="ChildState"/> of the passed in 
        /// <paramref name="current"/> state if it has one. This method is
        /// passed to the <see cref="LinkedEnumerator{T}"/> to allow it to
        /// iterate over the linked <see cref="IState"/> machine.
        /// </summary>
        /// <param name="current">The <see cref="IState"/> whose 
        /// <see cref="IState.ChildState"/> is to be retrieved.</param>
        /// <param name="next">The next <see cref="IState"/> in the link or the
        /// <see cref="IState.ChildState"/>.</param>
        /// <returns>True if this <see cref="IState"/> has a child state and
        /// <paramref name="next"/> is valid upon exit of this method. False if
        /// otherwise.</returns>
        public static bool LinkedNextState(IState current, out IState next)
        {
            next = current.ChildState;
            return next != null;
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="IEnumerator"/> object that can be used to iterate 
        /// through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// The backing field to <see cref="Entered"/>.
        /// </summary>
        EventHandler _entered;

        /// <summary>
        /// The backing field to <see cref="Left"/>.
        /// </summary>
        EventHandler _left;

        /// <summary>
        /// The backing field to <see cref="ParentState"/>.
        /// </summary>
        IState _parentState;

        /// <summary>
        /// The backing field to <see cref="ChildState"/>.
        /// </summary>
        IState _childState;
    }
}