context: using System;
  using System.Collections.Concurrent;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Diagnostics.CodeAnalysis;
  using System.Linq;
  using System.Runtime.InteropServices;
  using System.Threading;
  using Antlr4.Runtime;
  using Antlr4.Runtime.Tree;
  using Rubberduck.Parsing.Symbols;
  using Rubberduck.VBEditor;
  using Rubberduck.Parsing.Annotations;
  using NLog;
  using Rubberduck.Parsing.VBA.Parsing;
  using Rubberduck.VBEditor.ComManagement;
  using Rubberduck.VBEditor.Events;
  using Rubberduck.VBEditor.SafeComWrappers;
  using Rubberduck.VBEditor.SafeComWrappers.Abstract;
  using Rubberduck.Parsing.VBA.DeclarationCaching;
  using Rubberduck.Parsing.VBA.Parsing.ParsingExceptions;
  using Rubberduck.Parsing.VBA.ReferenceManagement;
  using Rubberduck.VBEditor.Extensions;
  
  // ReSharper disable LoopCanBeConvertedToQuery
  
  namespace Rubberduck.Parsing.VBA
  {
      public class ParseProgressEventArgs : EventArgs
      {
          public QualifiedModuleName Module { get; }
          public ParserState State { get; }
          public ParserState OldState { get; }
          public CancellationToken Token { get; }
  
          public ParseProgressEventArgs(QualifiedModuleName module, ParserState state, ParserState oldState, CancellationToken token)
          {
              Module = module;
              State = state;
              OldState = oldState;
              Token = token;
          }
      }
  
      public class ParserStateEventArgs : EventArgs
      {
          public ParserStateEventArgs(ParserState state, ParserState oldState, CancellationToken token)
          {
              State = state;
              OldState = oldState;
              Token = token;
          }
  
          public ParserState State { get; }
          public ParserState OldState { get; }
          public CancellationToken Token { get; }
  
          public bool IsError => (State == ParserState.ResolverError ||
                                  State == ParserState.Error ||
                                  State == ParserState.UnexpectedError);
      }
  
      public class RubberduckStatusSuspendParserEventArgs : EventArgs
      {
          public RubberduckStatusSuspendParserEventArgs(object requestor, IEnumerable<ParserState> allowedRunStates, Action busyAction, int millisecondsTimeout)
          {
              Requestor = requestor;
              AllowedRunStates = allowedRunStates;
              BusyAction = busyAction;
              MillisecondsTimeout = millisecondsTimeout;
              Result = SuspensionOutcome.Pending;
          }
  
          public object Requestor { get; }
          public IEnumerable<ParserState> AllowedRunStates { get; }
          public Action BusyAction { get; }
          public int MillisecondsTimeout { get; }
          public SuspensionOutcome Result { get; set; }
          public Exception EncounteredException { get; set; }
      }
  
      public class RubberduckStatusMessageEventArgs : EventArgs
      {
          public RubberduckStatusMessageEventArgs(string message)
          {
              Message = message;
          }
  
          public string Message { get; }
      }
  
      public sealed class RubberduckParserState : IDisposable, IDeclarationFinderProvider, IParseTreeProvider, IParseManager
      {
          public const int NoTimeout = -1;
  
          private readonly ConcurrentDictionary<QualifiedModuleName, ModuleState> _moduleStates =
              new ConcurrentDictionary<QualifiedModuleName, ModuleState>();
  
          public event EventHandler<EventArgs> ParseRequest;
          public event EventHandler<EventArgs> ParseCancellationRequested;
          public event EventHandler<RubberduckStatusSuspendParserEventArgs> SuspendRequest;
          public event EventHandler<RubberduckStatusMessageEventArgs> StatusMessageUpdate;
  
          private static readonly List<ParserState> States = new List<ParserState>();
  
          private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
  
          public bool IsEnabled { get; internal set; }
  
          public DeclarationFinder DeclarationFinder { get; private set; }
  
          private readonly IVBE _vbe;
          private readonly IProjectsRepository _projectRepository;
          private readonly IVbeEvents _vbeEvents;
          private readonly IHostApplication _hostApp;
          private readonly IDeclarationFinderFactory _declarationFinderFactory;
  
          /// <param name="vbeEvents">Provides event handling from the VBE. Static method <see cref="VbeEvents.Initialize"/> must be already called prior to constructing the method.</param>
          [SuppressMessage("ReSharper", "JoinNullCheckWithUsage")]
          public RubberduckParserState(IVBE vbe, IProjectsRepository projectRepository, IDeclarationFinderFactory declarationFinderFactory, IVbeEvents vbeEvents)
          {
              _vbe = vbe ?? throw new ArgumentNullException(nameof(vbe));
              _projectRepository = projectRepository ?? throw new ArgumentException(nameof(projectRepository));
              _declarationFinderFactory = declarationFinderFactory ?? throw new ArgumentNullException(nameof(declarationFinderFactory));
              _vbeEvents = vbeEvents ?? throw new ArgumentNullException(nameof(vbeEvents));
              
              var values = Enum.GetValues(typeof(ParserState));
              foreach (var value in values)
              {
                  States.Add((ParserState)value);
              }
  
              _hostApp = _vbe.HostApplication();
              AddEventHandlers();
              IsEnabled = true;
              RefreshFinder(_hostApp);
          }
  
          private void RefreshFinder(IHostApplication host)
          {
              var oldDeclarationFinder = DeclarationFinder;
              DeclarationFinder = _declarationFinderFactory.Create(
                  AllDeclarationsFromModuleStates, 
                  AllAnnotations,
                  AllFailedResolutionsFromModuleStates,
                  host);
              _declarationFinderFactory.Release(oldDeclarationFinder);
          }
  
          public void RefreshDeclarationFinder() => RefreshFinder(_hostApp);
  
          #region Event Handling
  
          private void AddEventHandlers()
          {
              _vbeEvents.ProjectAdded += Sinks_ProjectAdded;
              _vbeEvents.ProjectRemoved += Sinks_ProjectRemoved;
              _vbeEvents.ProjectRenamed += Sinks_ProjectRenamed;
              _vbeEvents.ComponentAdded += Sinks_ComponentAdded;
              _vbeEvents.ComponentRemoved += Sinks_ComponentRemoved;
              _vbeEvents.ComponentRenamed += Sinks_ComponentRenamed;
          }
  
          private void RemoveEventHandlers()
          {
              _vbeEvents.ProjectAdded -= Sinks_ProjectAdded;
              _vbeEvents.ProjectRemoved -= Sinks_ProjectRemoved;
              _vbeEvents.ProjectRenamed -= Sinks_ProjectRenamed;
              _vbeEvents.ComponentAdded -= Sinks_ComponentAdded;
              _vbeEvents.ComponentRemoved -= Sinks_ComponentRemoved;
              _vbeEvents.ComponentRenamed -= Sinks_ComponentRenamed;
          }
  
          private void Sinks_ProjectAdded(object sender, ProjectEventArgs e)
          {
              if (!_vbe.IsInDesignMode)
              {
                  return;
              }
  
              Logger.Debug("Project '{0}' was added.", e.ProjectId);
              OnParseRequested(sender);
          }
  
          private void Sinks_ProjectRemoved(object sender, ProjectEventArgs e)
          {
              if (!_vbe.IsInDesignMode)
              {
                  return;
              }
              
              Debug.Assert(e.ProjectId != null);
              DisposeProjectDeclarations(e.ProjectId);
  
              OnParseRequested(sender);
          }
  
          private void DisposeProjectDeclarations(string projectId)
          {
              var projectDeclarations = DeclarationFinder.UserDeclarations(DeclarationType.Project)
                  .Where(declaration => declaration.ProjectId == projectId)
                  .OfType<ProjectDeclaration>();
              foreach (var projectDeclaration in projectDeclarations)
              {
                  projectDeclaration.Dispose();
              }
          }
  
          private void Sinks_ProjectRenamed(object sender, ProjectRenamedEventArgs e)
          {
              if (!_vbe.IsInDesignMode || !ThereAreDeclarations())
              {
                  return;
              }
  
              Logger.Debug("Project {0} was renamed.", e.ProjectId);
  
              OnParseRequested(sender);
          }
  
          private void Sinks_ComponentAdded(object sender, ComponentEventArgs e)
          {
              if (!_vbe.IsInDesignMode || !ThereAreDeclarations())
              {
                  return;
              }
  
              Logger.Debug("Component '{0}' was added.", e.QualifiedModuleName.ComponentName);
              OnParseRequested(sender);
          }
  
          private void Sinks_ComponentRemoved(object sender, ComponentEventArgs e)
          {
              if (!_vbe.IsInDesignMode || !ThereAreDeclarations())
              {
                  return;
              }
  
              Logger.Debug("Component '{0}' was removed.", e.QualifiedModuleName.ComponentName);
              OnParseRequested(sender);
          }
  
          private void Sinks_ComponentRenamed(object sender, ComponentRenamedEventArgs e)
          {
              if (!_vbe.IsInDesignMode || !ThereAreDeclarations())
              {
                  return;
              }
  
              Logger.Debug("Component '{0}' was renamed to '{1}'.", e.OldName, e.QualifiedModuleName.ComponentName);
  
              //todo: Find out for which situation this drastic (and problematic) cache invalidation has been introduced.
              if (ComponentIsWorksheet(e))
              {
                  RefreshProject(e.ProjectId);
                  Logger.Debug("Project '{0}' was removed.", e.QualifiedModuleName.ComponentName);
              }
              OnParseRequested(sender);
          }
  
          private bool ComponentIsWorksheet(ComponentRenamedEventArgs e)
          {
              var componentIsWorksheet = false;
              foreach (var declaration in AllUserDeclarations)
              {
                  if (declaration.ProjectId == e.ProjectId &&
                      declaration is ClassModuleDeclaration classModule &&
                      declaration.IdentifierName == e.OldName)
                  {
                      foreach (var superType in classModule.Supertypes)
                      {
                          if (superType.IdentifierName == "Worksheet")
                          {
                              componentIsWorksheet = true;
                              break;
                          }
                      }
  
                      break;
                  }
              }
  
              return componentIsWorksheet;
          }
  
          public void OnStatusMessageUpdate(string message)
          {
              var handler = StatusMessageUpdate;
              if (handler != null)
              {
                  var args = new RubberduckStatusMessageEventArgs(message);
                  handler.Invoke(this, args);
              }
          }
  
          #endregion
  
          /// <summary>
          /// Refreshes our list of cached projects.
          /// Be sure to reparse after calling this in case there
          /// were projects with duplicate ID's to clear the old
          /// declarations referencing the project by the old ID.
          /// </summary>
          public void RefreshProjects() => _projectRepository.Refresh();
          
  
          private void RefreshProject(string projectId)
          {
              _projectRepository.Refresh(projectId);
              ClearStateCache(projectId);
          }
  
          public List<IVBProject> Projects => _projectRepository.Projects().Select(tpl => tpl.Project).ToList();
  
          public IProjectsProvider ProjectsProvider => _projectRepository;
  
          public IReadOnlyList<Tuple<QualifiedModuleName, SyntaxErrorException>> ModuleExceptions
          {
              get
              {
                  var exceptions = new List<Tuple<QualifiedModuleName, SyntaxErrorException>>();
                  foreach (var kvp in _moduleStates)
                  {
                      if (kvp.Value.ModuleException == null)
                      {
                          continue;
                      }
  
                      exceptions.Add(Tuple.Create(kvp.Key, kvp.Value.ModuleException));
                  }
  
                  return exceptions;
              }
          }
  
          public event EventHandler<ParserStateEventArgs> StateChangedHighPriority;
          public event EventHandler<ParserStateEventArgs> StateChanged;
  
          private int _stateChangedInvocations;
          private void OnStateChanged(object requestor, CancellationToken token, ParserState state, ParserState oldStatus)
          {
              Interlocked.Increment(ref _stateChangedInvocations);
  
              Logger.Info($"{nameof(RubberduckParserState)} ({_stateChangedInvocations}) is invoking {nameof(StateChanged)} ({Status})");
  
              var highPriorityHandler = StateChangedHighPriority;
              if (highPriorityHandler != null && !token.IsCancellationRequested)
              {
                  try
                  {
                      var args = new ParserStateEventArgs(state, oldStatus, token);
                      highPriorityHandler.Invoke(requestor, args);
                  }
                  catch (OperationCanceledException cancellation)
                  {
                      throw;
                  }
                  catch (Exception e)
                  {
                      // Error state, because this implies consumers are not exception-safe!
                      // this behaviour could leave us in a state where some consumers have correctly updated and some have not
                      Logger.Error(e, "An exception occurred when notifying consumers of updated parser state.");
                  }
              }
  
              var handler = StateChanged;
              if (handler != null && !token.IsCancellationRequested)
              {
                  try
                  {
                      var args = new ParserStateEventArgs(state, oldStatus, token);
                      handler.Invoke(requestor, args);
                  }
                  catch (OperationCanceledException cancellation)
                  {
                      throw;
                  }
                  catch (Exception e)
                  {
                      // Error state, because this implies consumers are not exception-safe!
                      // this behaviour could leave us in a state where some consumers have correctly updated and some have not
                      Logger.Error(e, "An exception occurred when notifying consumers of updated parser state.");
                  }
              }
          }
  
          public event EventHandler<ParseProgressEventArgs> ModuleStateChanged;
  
          //Never spawn new threads changing module states in the handler! This will cause deadlocks. 
          private void OnModuleStateChanged(QualifiedModuleName module, ParserState state, ParserState oldState, CancellationToken token)
          {
              var handler = ModuleStateChanged;
  
gt:             if (handler != null && !token.IsCancellationRequested)