/*-------------------------------------------------------------------------
//                           UNCLASSIFIED
/*-------------------------------------------------------------------------
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
COMPANY.

THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
GOVERNMENT.

UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
-------------------------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ProgramLib
{
   /// <summary>
   /// Class that maps event array index to any event enumeration and wraps the WaitHandle.WaitAny() function
   /// Use case: 
   ///    When calling WaitHandle.WaitAny(EventArray), it only returns the index of the event in the array that is being signalled.
   ///    Refering to an index of the array to know which event it is is not ideal.  We want to be able to refer to an enumeration instead
   /// Usage:
   ///    In class that uses this class, must define a enumeration
   ///         public enum Events
   ///         {
   ///            EVENT1,
   ///            EVENT2,
   ///            EVENT3,
   ///            EVENTN
   ///            
   ///            // DO NOT change the name
   ///            // This must be the last member in the enum
   ///            EVENT_TIMED_OUT
   ///         }
   ///         Dictionary<Events, EventWaitHandle> dict = new Dictionary<Events, EventWaitHandle>();
   ///         dict[Events.EVENT1] = new ManualResetEvent(false);
   ///         dict[Events.EVENT2] = new AutoResetEvent(false);
   ///         dict[Events.EVENT3] = new ManualResetEvent(false);
   ///         dict[Events.EVENTN] = new AutoResetEvent(false);
   ///         dict[Events.EVENT_TIMED_OUT] = null;
   ///         
   ///         EventGroup<Events, EventWaitHandle> eventGroup = new EventGroup<Events, EventWaitHandle>(dict);
   ///         
   ///         Events id = eventGroup.WaitAny([Optional_timeout]);
   ///         
   ///        if (id == Events.EVENT_TIMED_OUT){}   // only if timeout was specified
   ///        if (id == Events.EVENT1){}
   ///               
   /// </summary>
   internal class EventGroup<T1,T2>
   {
      Dictionary<T1, T2> _eventDict = null;
      T2[] _eventArray = null;

      /// <summary>
      /// Constructor
      /// </summary>
      private EventGroup()
      {
      }

      /// <summary>
      /// Constructor
      /// The event enum passed in must be defined in the following format:
      ///         public enum Events
      ///         {
      ///            EVENT1,
      ///            EVENT2,
      ///            EVENT3,
      ///            EVENTN
      ///            
      ///            // DO NOT change the name
      ///            // This must be the last member in the enum
      ///            EVENT_TIMED_OUT
      ///         }
      /// </summary>
      /// <param name="eventDict">A dictionary that contains event enum that is associated to an event object</param>
      public EventGroup(Dictionary<T1, T2> eventDict)
      {
         if (eventDict == null)
            throw new Exception($"{nameof(eventDict)} cannot be null");

         if (!eventDict.First().Key.GetType().IsEnum)
         {
            throw new Exception($"{nameof(eventDict)}'s key must be an enumerated type");
         }

         // get the last element in the dictionary
         var element = eventDict.ElementAt(eventDict.Count - 1);

         // check if the name of the last enum member is what we expect
         string actualNameOfLastEnumMember = ((Enum)Convert.ChangeType(element.Key, typeof(Enum))).ToString();

         string expectedNameOfLastEnumMember = "EVENT_TIMED_OUT";
         if (actualNameOfLastEnumMember != expectedNameOfLastEnumMember)
         {
            throw new Exception($"Enum {element.Key.GetType().Name} must have {expectedNameOfLastEnumMember} as its last member. {nameof(eventDict)} must have {expectedNameOfLastEnumMember} as the last key in the dictionary");
         }

         if (eventDict.First().Value.GetType().BaseType != typeof(EventWaitHandle))
         {
            throw new Exception($"{nameof(eventDict)}'s value must be ManualResetEvent or AutoResetEvent type");
         }
         _eventDict = eventDict;

         _eventArray = new T2[_eventDict.Count - 1];

         int index = 0;
         foreach (KeyValuePair<T1, T2> entry in _eventDict)
         {
            if (index < _eventDict.Count - 1)
               _eventArray[index++] = entry.Value;
         }
      }

      /// <summary>
      /// Return the enumerated event based on the index of the event in the event array that is being signalled
      /// </summary>
      /// <param name="timeoutMilliseconds">time out in milliseconds</param>
      public T1 WaitAny(int? timeoutMilliseconds = null)
      {
         int index = 0;
         T1 eventId = default;

         if (timeoutMilliseconds == null)
            index = WaitHandle.WaitAny((EventWaitHandle[])Convert.ChangeType(_eventArray, typeof(EventWaitHandle[])));
         else
            index = WaitHandle.WaitAny((EventWaitHandle[])Convert.ChangeType(_eventArray, typeof(EventWaitHandle[])), (int)timeoutMilliseconds);

         if (index == WaitHandle.WaitTimeout)
         {
            var element = _eventDict.ElementAt(_eventDict.Count - 1);
            eventId = element.Key;
         }
         else
         {
            var element = _eventDict.ElementAt(index);
            eventId = element.Key;
         }

         return eventId;
      }
   }
}
