context: using System;
  using System.Collections.Concurrent;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Threading;
  using System.Threading.Tasks;
  using System.Windows.Media;
  using Microsoft.VisualStudio.Text;
  using NuGet;
  using System.Globalization;
  
  namespace NuGetConsole.Implementation.Console
  {
  
      internal interface IPrivateConsoleDispatcher : IConsoleDispatcher, IDisposable
      {
          event EventHandler<EventArgs<Tuple<SnapshotSpan, bool>>> ExecuteInputLine;
          void PostInputLine(InputLine inputLine);
          void PostKey(VsKeyInfo key);
          void CancelWaitKey();
      }
  
  
      /// <summary>
      /// This class handles input line posting and command line dispatching/execution.
      /// </summary>
      internal class ConsoleDispatcher : IPrivateConsoleDispatcher
      {
  
          private readonly BlockingCollection<VsKeyInfo> _keyBuffer = new BlockingCollection<VsKeyInfo>();
          private CancellationTokenSource _cancelWaitKeySource;
          private bool _isExecutingReadKey;
  
          /// <summary>
          /// The IPrivateWpfConsole instance this dispatcher works with.
          /// </summary>
          private IPrivateWpfConsole WpfConsole { get; set; }
  
          /// <summary>
          /// Child dispatcher based on host type. Its creation is postponed to Start(), so that
          /// a WpfConsole's dispatcher can be accessed while inside a host construction.
          /// </summary>
          private Dispatcher _dispatcher;
  
          private readonly object _lockObj = new object();
  
          public event EventHandler StartCompleted;
  
          public event EventHandler StartWaitingKey;
  
          public ConsoleDispatcher(IPrivateWpfConsole wpfConsole)
          {
              UtilityMethods.ThrowIfArgumentNull(wpfConsole);
  
              this.WpfConsole = wpfConsole;
          }
  
          public bool IsExecutingCommand
          {
              get
              {
                  return (_dispatcher != null) && _dispatcher.IsExecuting;
              }
          }
  
          public void PostKey(VsKeyInfo key)
          {
              if (key == null)
              {
                  throw new ArgumentNullException("key");
              }
              _keyBuffer.Add(key);
          }
  
          public bool IsExecutingReadKey
          {
              get { return _isExecutingReadKey; }
          }
  
          public bool IsKeyAvailable
          {
              get
              {
                  // In our BlockingCollection<T> producer/consumer this is
                  // not critical so no need for locking. 
                  return _keyBuffer.Count > 0;
              }
          }
  
          public void CancelWaitKey()
          {
              if (_isExecutingReadKey && !_cancelWaitKeySource.IsCancellationRequested)
              {
                  _cancelWaitKeySource.Cancel();
              }
          }
  
          public void AcceptKeyInput()
          {
              Debug.Assert(_dispatcher != null);
  
              if (_dispatcher != null && WpfConsole != null)
              {
                  WpfConsole.BeginInputLine();
              }
          }
  
          public VsKeyInfo WaitKey()
          {
              try
              {
                  // raise the StartWaitingKey event on main thread
                  RaiseEventSafe(StartWaitingKey);
  
                  // set/reset the cancellation token
                  _cancelWaitKeySource = new CancellationTokenSource();
                  _isExecutingReadKey = true;
  
                  // blocking call
                  VsKeyInfo key = _keyBuffer.Take(_cancelWaitKeySource.Token);
  
                  return key;
              }
              catch (OperationCanceledException)
              {
                  return null;
              }
              finally
              {
                  _isExecutingReadKey = false;
              }
          }
  
          private void RaiseEventSafe(EventHandler handler)
          {
              if (handler != null)
              {
                  Microsoft.VisualStudio.Shell.ThreadHelper.Generic.Invoke(() => handler(this, EventArgs.Empty));
              }
          }
  
          public bool IsStartCompleted { get; private set; }
  
          #region IConsoleDispatcher
  
          public void Start()
          {
              // Only Start once
              lock (_lockObj)
              {
                  if (_dispatcher == null)
                  {
                      IHost host = WpfConsole.Host;
  
                      if (host == null)
                      {
                          throw new InvalidOperationException("Can't start Console dispatcher. Host is null.");
                      }
  
                      if (host is IAsyncHost)
                      {
                          _dispatcher = new AsyncHostConsoleDispatcher(this);
                      }
                      else
                      {
                          _dispatcher = new SyncHostConsoleDispatcher(this);
                      }
  
                      // capture the cultures to assign to the worker thread below
                      CultureInfo currentCulture = CultureInfo.CurrentCulture;
                      CultureInfo currentUICulture = CultureInfo.CurrentUICulture;
  
                      Task.Factory.StartNew(
                          // gives the host a chance to do initialization works before the console starts accepting user inputs
                          () => 
                              {
                                  // apply the culture of the main thread to this thread so that the PowerShell engine
                                  // will have the same culture as Visual Studio.
                                  System.Threading.Thread.CurrentThread.CurrentCulture = currentCulture;
                                  System.Threading.Thread.CurrentThread.CurrentUICulture = currentUICulture;
  
                                  host.Initialize(WpfConsole);
                              }
                      ).ContinueWith(
                          task =>
                          {
                              if (task.IsFaulted)
                              {
                                  var exception = ExceptionUtility.Unwrap(task.Exception);
                                  WriteError(exception.Message);
                              }
  
                              if (host.IsCommandEnabled)
                              {
                                  Microsoft.VisualStudio.Shell.ThreadHelper.Generic.Invoke(_dispatcher.Start);
                              }
  
                              RaiseEventSafe(StartCompleted);
                              IsStartCompleted = true;
                          },
                          TaskContinuationOptions.NotOnCanceled
                      );
                  }
              }
          }
  
          private void WriteError(string message)
          {
              if (WpfConsole != null)
              {
                  WpfConsole.Write(message + Environment.NewLine, Colors.Red, null);
              }
          }
  
          public void ClearConsole()
          {
              Debug.Assert(_dispatcher != null);
              if (_dispatcher != null)
              {
                  _dispatcher.ClearConsole();
              }
          }
          #endregion
  
          #region IPrivateConsoleDispatcher
          public event EventHandler<EventArgs<Tuple<SnapshotSpan, bool>>> ExecuteInputLine;
  
          void OnExecute(SnapshotSpan inputLineSpan, bool isComplete)
          {
              ExecuteInputLine.Raise(this, Tuple.Create(inputLineSpan, isComplete));
          }
  
          public void PostInputLine(InputLine inputLine)
          {
              Debug.Assert(_dispatcher != null);
              if (_dispatcher != null)
              {
                  _dispatcher.PostInputLine(inputLine);
              }
          }
          #endregion
  
          private abstract class Dispatcher
          {
              protected ConsoleDispatcher ParentDispatcher { get; private set; }
              protected IPrivateWpfConsole WpfConsole { get; private set; }
  
              private bool _isExecuting;
  
              public bool IsExecuting
              {
                  get
                  {
                      return _isExecuting;
                  }
                  protected set
                  {
                      _isExecuting = value;
                      WpfConsole.SetExecutionMode(_isExecuting);
                  }
              }
  
              protected Dispatcher(ConsoleDispatcher parentDispatcher)
              {
                  ParentDispatcher = parentDispatcher;
                  WpfConsole = parentDispatcher.WpfConsole;
              }
  
              /// <summary>
              /// Process a input line.
              /// </summary>
              /// <param name="inputLine"></param>
              protected Tuple<bool, bool> Process(InputLine inputLine)
              {
                  SnapshotSpan inputSpan = inputLine.SnapshotSpan;
  
                  if (inputLine.Flags.HasFlag(InputLineFlag.Echo))
                  {
                      WpfConsole.BeginInputLine();
  
                      if (inputLine.Flags.HasFlag(InputLineFlag.Execute))
                      {
                          WpfConsole.WriteLine(inputLine.Text);
                          inputSpan = WpfConsole.EndInputLine(true).Value;
                      }
                      else
                      {
                          WpfConsole.Write(inputLine.Text);
                      }
                  }
  
                  if (inputLine.Flags.HasFlag(InputLineFlag.Execute))
                  {
                      string command = inputLine.Text;
                      bool isExecuted = WpfConsole.Host.Execute(WpfConsole, command, null);
                      WpfConsole.InputHistory.Add(command);
                      ParentDispatcher.OnExecute(inputSpan, isExecuted);
                      return Tuple.Create(true, isExecuted);
                  }
                  return Tuple.Create(false, false);
              }
  
              protected void PromptNewLine()
              {
                  WpfConsole.Write(WpfConsole.Host.Prompt + (char)32);    // 32 is the space
                  WpfConsole.BeginInputLine();
              }
  
              public void ClearConsole()
              {
                  // When inputting commands
  
gt:                 if (WpfConsole.InputLineStart != null)