//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
// CommonHelpers satellite library | Author: Sergei Y.Kitáev
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System;
using System.Threading;

namespace CommonHelpers {

//**************************************************************************************************
partial class HConsole {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

public static ConsoleKey[] DigitalKeys
{
   get
   {
      lock (_StaticSyncRoot_)
         return HArray.Duplicate(_DigitalKeys_);
   }
}

public static ConsoleKey[] NumPadKeys
{
   get
   {
      lock (_StaticSyncRoot_)
         return HArray.Duplicate(_NumPadKeys_);
   }
}

//--------------------------------------------------------------------------------------------------

public HConsoleInputIdlePollingContext InputIdlePollingContext
{
   get
   {
      lock (_InstanceSyncRoot_)
         return _InputIdlePollingContext_;
   }
}

//==================================================================================================

/// <exception cref="InvalidOperationException" />
/// <exception cref="HAbortConsoleInputException" />
public int AcceptInputKey(params ConsoleKey[] keyArray)
{
   return AcceptInputKey(keyArray,null);
}

/// <exception cref="InvalidOperationException" />
/// <exception cref="HAbortConsoleInputException" />
public int AcceptInputKey( ConsoleKey[] keyArray,
                           [HOptional] ConsoleModifiers[] modifierArray )
{
   return AcceptInputKey(keyArray,modifierArray,true);
}

/// <exception cref="InvalidOperationException" />
/// <exception cref="HAbortConsoleInputException" />
public int AcceptInputKey( ConsoleKey[] keyArray,
                           [HOptional] ConsoleModifiers[] modifierArray,
                           bool treatNumPadAsDigital )
{
   HArgChecking.VerifyNotNull(keyArray);
   if ( modifierArray!=null && keyArray.Length!=modifierArray.Length ||
        keyArray.Length==0 )
      throw HArgChecking.ArgumentException_IncorrectArguments;
   VerifyInNativeVisualMode();
   int index = -1;
   AcceptInputKey
   (
      (HAcceptConsoleInputKeyCallback)delegate (ConsoleKeyInfo keyInfo)
      {
         for (int i=0; i<keyArray.Length; i++)
         {
            var key = HArray.GetAtZeroBasedIndex(keyArray,i);
            var modifiers = modifierArray!=null ?
               HArray.GetAtZeroBasedIndex(modifierArray,i) : 0;

            if ( keyInfo.Modifiers == modifiers &&
                 ( keyInfo.Key == key || IsKeyDigital( key ) &&
                   NumPadKeyAsDigital( keyInfo.Key ) == key ) )
            {
               index = i; return true;
            }
         }
         return false;
      }
   );
   return index;
}

/// <exception cref="InvalidOperationException" />
/// <exception cref="HAbortConsoleInputException" />
public ConsoleKeyInfo AcceptInputKey(HAcceptConsoleInputKeyCallback acceptKeyCallback)
{
   lock (_InstanceSyncRoot_)
   {
      VerifyInNativeVisualMode();
      for (;;)
      {
         ResetInput();
         var keyInfo=Console.ReadKey(true);
         if (acceptKeyCallback(keyInfo))
            return keyInfo;
         PlayDisabledSound();
      }
   }
}

//==================================================================================================

/// <exception cref="InvalidOperationException" />
public void ResetInput()
{
   lock (_InstanceSyncRoot_)
   {
      VerifyInNativeVisualMode();
      while (Console.KeyAvailable)
         Console.ReadKey(true);
   }
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
/// <exception cref="HAbortConsoleInputException" />
public void WaitInput()
{
   WaitInput( false );
}
/// <exception cref="InvalidOperationException" />
/// <exception cref="HAbortConsoleInputException" />
public void WaitInput(bool withPreliminaryInputReset=false)
{
   lock (_InstanceSyncRoot_)
   {
      if ( withPreliminaryInputReset )
         ResetInput();
      if (InputIdlePollingContext!=null)
      {
         DateTime? prevTime=null;

         while (!Console.KeyAvailable)
         {
            var currTime = DateTime.Now;

            if ( prevTime==null ||
                 HDateTime.ElapsedMilliseconds(prevTime.Value,currTime) >=
                 InputIdlePollingContext.Interval )
            {
               InputIdlePollingContext.Callback();
               prevTime = currTime;
            }

            Thread.Sleep( WaitInputPollingPause );
         }
      }
      else
         while (!Console.KeyAvailable)
            Thread.Sleep( WaitInputPollingPause );
   }
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
/// <exception cref="HAbortConsoleInputException" />
public void AbortConsoleInput()
{
   lock (_InstanceSyncRoot_)
   {
      VerifyInNativeVisualMode();
      if (InputIdlePollingContext==null)
         throw new InvalidOperationException(
            "Input idle polling context is not active." );
      throw new HAbortConsoleInputException();
   }
}

//==================================================================================================

/// <exception cref="InvalidOperationException" />
/// <exception cref="HAbortConsoleInputException" />
public ConsoleKeyInfo ReadKey()
{
   lock (_InstanceSyncRoot_)
   {
      VerifyInNativeVisualMode();
      if (InputIdlePollingContext!=null)
         WaitInput();
      return Console.ReadKey( true );
   }
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
public bool ReadKeyIfAvailable(ref ConsoleKeyInfo keyInfo)
{
   lock (_InstanceSyncRoot_)
   {
      VerifyInNativeVisualMode();
      if (!Console.KeyAvailable)
         return false;
      keyInfo = Console.ReadKey( true );
      return true;
   }
}

//==================================================================================================

public bool IsKeyDigital(ConsoleKey key)
{
   return HArray.Contains(_DigitalKeys_,key);
}

public bool IsKeyNumPad(ConsoleKey key)
{
   return HArray.Contains(_NumPadKeys_,key);
}

//--------------------------------------------------------------------------------------------------

public ConsoleKey NumPadKeyAsDigital(ConsoleKey key)
{
   return IsKeyNumPad( key ) ?
      (ConsoleKey)( (int)ConsoleKey.D0 + (int)key - (int)ConsoleKey.NumPad0 ) :
      key;
}

//--------------------------------------------------------------------------------------------------

public string KeyToString(ConsoleKey key)
{
   return KeyToString(key,0);
}

public string KeyToString(ConsoleKey key,[HOptional] ConsoleModifiers modifiers)
{
   if (KeyToStringCallback!=null)
      return KeyToStringCallback(key,modifiers);

   string keyString=key.ToString();

   if ( IsKeyDigital(key) || IsKeyNumPad(key) )
      keyString=keyString[ keyString.Length-1 ].ToString();

   if ((modifiers & ConsoleModifiers.Shift)!=0)
      keyString="Shift+"+keyString;
   if ((modifiers & ConsoleModifiers.Alt)!=0)
      keyString="Alt+"+keyString;
   if ((modifiers & ConsoleModifiers.Control)!=0)
      keyString="Ctrl+"+keyString;

   return string.Format("<{0}>",keyString);
}

//==================================================================================================

public void PlayDisabledSound()
{
   HSound.PlayDisabled( true );
}

//==================================================================================================

internal void SetInputIdlePollingContext(HConsoleInputIdlePollingContext context)
{
   lock (_InstanceSyncRoot_)
      _InputIdlePollingContext_ = context;
}

//==================================================================================================

static readonly ConsoleKey[]
   _DigitalKeys_=
   {
      ConsoleKey.D0,
      ConsoleKey.D1, ConsoleKey.D2, ConsoleKey.D3,
      ConsoleKey.D4, ConsoleKey.D5, ConsoleKey.D6,
      ConsoleKey.D7, ConsoleKey.D8, ConsoleKey.D9
   },
   _NumPadKeys_=
   {
      ConsoleKey.NumPad0,
      ConsoleKey.NumPad1, ConsoleKey.NumPad2, ConsoleKey.NumPad3,
      ConsoleKey.NumPad4, ConsoleKey.NumPad5, ConsoleKey.NumPad6,
      ConsoleKey.NumPad7, ConsoleKey.NumPad8, ConsoleKey.NumPad9
   };

HConsoleInputIdlePollingContext
   _InputIdlePollingContext_;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // HConsole
//**************************************************************************************************

} // CommonHelpers
