using System;
using System.Collections.Generic;
using Wayne.Lib.Log;

namespace Wayne.Lib.StateEngine
{
    //[System.Diagnostics.DebuggerNonUserCode()]
    abstract class EventBufferingRootStateMachine : RootStateMachine
    {
        #region Fields

        private readonly List<StateEngineEvent> eventList = new List<StateEngineEvent>();
        private readonly object eventQueueSyncObj = new object();
        private readonly Queue<StateEngineEvent> resendQueue = new Queue<StateEngineEvent>();

        private uint nextSequenceNumber;
        private readonly object nextSequenceNumberLock = new object();

        private bool stateChanged;

        #endregion

        #region Constants

        private const uint sequenceNumberWindowSize = 200;
        private const uint sequenceNumberWindowStart = uint.MaxValue - sequenceNumberWindowSize / 2;
        private const uint sequenceNumberWindowEnd = uint.MinValue + sequenceNumberWindowSize / 2;

        #endregion

        #region Construction

        protected EventBufferingRootStateMachine(string name, IDebugLogger debugLogger, object logCategory)
            : base(name, debugLogger, logCategory)
        {

        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Recursively hook on the OnStateChanged for the state machine and all sub composite states.
        /// </summary>
        /// <param name="stateMachine"></param>
        protected internal void CreateOnStateChangeEventHandler(StateMachine stateMachine)
        {
            stateMachine.OnStateChanged += new EventHandler<StateChangedEventArgs>(StateChangedNotification);
            foreach (State state in stateMachine.CreatedStates)
            {
                CompositeState compositeState = state as CompositeState;
                if (compositeState != null)
                {
                    CreateOnStateChangeEventHandler(compositeState.StateMachine);
                }
            }
        }

        /// <summary>
        /// Processes all queued events.
        /// </summary>
        protected internal void HandleQueuedEvents(ReentrancyMutex mutexToReleaseWhenEventProcessingIsDone)
        {
            int eventCount;
            lock (eventQueueSyncObj)
            {
                eventCount = eventList.Count;

                if ((eventCount == 0) && (mutexToReleaseWhenEventProcessingIsDone != null))
                    mutexToReleaseWhenEventProcessingIsDone.Release();
            }

            if (eventCount > 0)
            {
                do
                {
                    //Pick out the new event.
                    StateEngineEvent stateEngineEvent;
                    lock (eventQueueSyncObj)
                    {
                        stateEngineEvent = eventList[0];
                        eventList.RemoveAt(0);
                    }

                    //Reset the state changed flag. This flag is set in the OnStateChange event handler                     
                    stateChanged = false;

                    if (disposed)
                        return;

                    //Handle the event
                    HandleEventInCurrentState(stateEngineEvent);

                    //The state changed during the event handling.
                    if (stateChanged)
                    {
                        lock (eventQueueSyncObj)
                        {
                            //Move all the events waiting for resend to the normal event queue.
                            while (resendQueue.Count > 0)
                                eventList.Add(resendQueue.Dequeue());

                            SortEventList();
                        }
                    }

                    lock (eventQueueSyncObj)
                    {
                        eventCount = eventList.Count;

                        if ((eventCount == 0) && (mutexToReleaseWhenEventProcessingIsDone != null))
                            mutexToReleaseWhenEventProcessingIsDone.Release();
                    }

                } while ((eventCount > 0) && !disposed);
            }
        }

        /// <summary>
        /// Handle the event in the current state, and if the event is returned unhandled, it 
        /// is put on the resend queue, to be sent to the next state we enter.
        /// </summary>
        /// <param name="stateEngineEvent"></param>
        protected override void HandleEventInCurrentState(StateEngineEvent stateEngineEvent)
        {
            base.HandleEventInCurrentState(stateEngineEvent);

            //If the event was not handled, it should be queued to be re-sent in the next state.
            if (!stateEngineEvent.Handled)
            {
                lock (eventQueueSyncObj)
                    resendQueue.Enqueue(stateEngineEvent);
            }
        }

        #endregion

        /// <summary>
        /// Sorts the event list.
        /// </summary>
        private void SortEventList()
        {
            eventList.Sort(CompareEvents);
        }

        /// <summary>
        /// Compares two state engine event objects.
        /// </summary>
        /// <param name="ev1"></param>
        /// <param name="ev2"></param>
        /// <returns></returns>        
        private static int CompareEvents(StateEngineEvent ev1, StateEngineEvent ev2)
        {
            if (ev1.Priority != ev2.Priority)
                return ev1.Priority.CompareTo(ev2.Priority);
            else
            {
                //Get the result through a normal comparison.
                int result = ev1.SequenceNumber.CompareTo(ev2.SequenceNumber);

                //Check, we leave a window for 200 events between uint.MaxValue and uint.MinValue
                if ((ev1.SequenceNumber >= sequenceNumberWindowStart) && (ev2.SequenceNumber <= sequenceNumberWindowEnd) ||
                    (ev2.SequenceNumber >= sequenceNumberWindowStart) && (ev1.SequenceNumber <= sequenceNumberWindowEnd))
                {
                    result *= -1;
                }

                return result;
            }
        }

        /// <summary>
        /// Generates a new sequence number for incoming events.
        /// </summary>
        /// <returns></returns>
        private uint GetNextEventSequenceNumber()
        {
            lock (nextSequenceNumberLock)
            {
                unchecked
                {
                    return nextSequenceNumber++;
                }
            }
        }

        /// <summary>
        /// Event hanler for state changes.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void StateChangedNotification(object sender, StateChangedEventArgs e)
        {
            stateChanged = true;
        }

        /// <summary>
        /// Entry point for incoming events. Stores the event in the event list and sorts the list. Nothing more happens, it is up to descendant classes
        /// to add functionality on how to handle this.
        /// </summary>
        /// <param name="stateEngineEvent"></param>
        public override void IncomingEvent(StateEngineEvent stateEngineEvent)
        {
            if (!disposed)
            {
                if (stateEngineEvent.ArrivalTime != DateTime.MinValue) //The arrival time is always set to minvalue when it is created. 
                    //If it has another value, it has been added to another statemachine.
                    throw new StateEngineException(string.Format("The event ({0}) has already been sent to another statemachine. It can not be sent to more than one.", stateEngineEvent.ToString()));

                stateEngineEvent.ArrivalTime = DateTime.Now;
                stateEngineEvent.SequenceNumber = GetNextEventSequenceNumber();

                lock (eventQueueSyncObj)
                {
                    //Add the event to the list.
                    eventList.Add(stateEngineEvent);

                    //Sort the event list.
                    SortEventList();
                }
            }
        }

        

        /// <summary>
        /// Removes all pending event that matches the supplied predicate.
        /// </summary>
        /// <typeparam name="TComparisonObject">Type of the comparison object</typeparam>
        /// <param name="predicate">The predicate that is used to match the event.</param>
        /// <param name="comparisonObject">The comparison object that is used in the StateEngineEventPredicate.</param>
        public override void RemovePendingEvents<TComparisonObject>(StateEngineEventPredicate<TComparisonObject> predicate, TComparisonObject comparisonObject)
        {
            lock (eventQueueSyncObj)
            {
                if ((debugLogger != null) && (debugLogger.IsActive(logCategory, DebugLogLevel.Maximized)))
                    debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, "ClearPendingEvents using predicate {0} using comparisonObject {1}", predicate, comparisonObject), logCategory, DebugLogLevel.Maximized);

                //Remove the matching events in the resend queue.
                Queue<StateEngineEvent> tempQueue = new Queue<StateEngineEvent>(resendQueue);
                resendQueue.Clear();

                while (tempQueue.Count > 0)
                {
                    StateEngineEvent tempEvent = tempQueue.Dequeue();
                    if (!predicate(tempEvent, comparisonObject)) //Call the predicate delegate to evaluate the event.
                    {
                        resendQueue.Enqueue(tempEvent); //Put back the event in the queue
                    }
                    else
                    {
                        if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
                            debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, "       Removed event [{0}] from resend queue", tempEvent), logCategory);
                    }
                }

                //Remove the matching events in the event queue.
                List<int> indicesToRemove = new List<int>();
                for (int i = 0; i < eventList.Count; i++)
                {
                    if (predicate(eventList[i], comparisonObject))
                    {
                        indicesToRemove.Add(i);
                    }
                }

                foreach (int i in indicesToRemove)
                {
                    if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
                        debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, "   Removed event [{0}] from event list", eventList[i]), logCategory);
                    eventList[i] = null;
                }

                while (eventList.Contains(null))
                    eventList.Remove(null); //Removes only one instance of null, we may have several. So continue remove until there is no more nulls.
            }
        }

        /// <summary>
        /// DO NOT USE for any isfs based event!
        /// Removes all pending events that matches the specified event type. .Equals method is 
        /// used to compare.
        /// </summary>
        /// <param name="eventType"></param>
        public override void RemovePendingEventsOfType(object eventType)
        {
            RemovePendingEvents(MatchEventOnType, eventType);
        }

        /// <summary>
        /// Gets the pending envents from the StateMachine.
        /// </summary>
        /// <param name="eventType"></param>
        /// <returns>null or an IEnumberable</returns>
        public override IEnumerable<StateEngineEvent> GetPendingEventsOfType(object eventType)
        {
            return GetPendingEvents(MatchEventOnType, eventType);
        }

        /// <summary>
        /// Gets the pending StateEngineEvents of the type
        /// </summary>
        /// <typeparam name="TComparisonObject">Type of the comparison object</typeparam>
        /// <param name="predicate">The predicate that is used to match the event.</param>
        /// <param name="comparisonObject">The comparison object that is used in the StateEngineEventPredicate.</param>
        /// <returns>null or an IEnumerable</returns>
        public override IEnumerable<StateEngineEvent> GetPendingEvents<TComparisonObject>(
            StateEngineEventPredicate<TComparisonObject> predicate, TComparisonObject comparisonObject)
        {
            var result = new List<StateEngineEvent>();
            lock (eventQueueSyncObj)
            {
                var tempQueue = new Queue<StateEngineEvent>(resendQueue);
                while (tempQueue.Count > 0)
                {
                    var tempEvent = tempQueue.Dequeue();
                    if (predicate(tempEvent, comparisonObject)) //Call the predicate delegate to evaluate the event.
                    {
                        result.Add(tempEvent); 
                    }
                }
            }
            return result;
        }


        /// <summary>
        /// Predicate method that is used to make the event type comparison in the RemovePendingEventsOfType method.
        /// </summary>
        /// <param name="stateEngineEvent"></param>
        /// <param name="eventType"></param>
        /// <returns></returns>
        private static bool MatchEventOnType(StateEngineEvent stateEngineEvent, object eventType)
        {
            if (stateEngineEvent != null)
                return stateEngineEvent.Type.Equals(eventType);
            else
                return false;
        }

        /// <summary>
        /// Clear all pending events.
        /// </summary>
        internal override void ClearPendingEvents()
        {
            lock (eventQueueSyncObj)
            {
                if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
                {
                    debugLogger.Add("ClearPendingEvents");
                    foreach (StateEngineEvent tempEvent in resendQueue)
                        debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, "   Removed event [{0}] from resend queue ", tempEvent), logCategory);
                }

                resendQueue.Clear();

                if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
                {
                    foreach (StateEngineEvent tempEvent in eventList)
                        debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, "   Removed event [{0}] from event list", tempEvent), logCategory);
                }
                eventList.Clear();
            }
        }
    }
}