context: using System.Collections.Generic;
  using System.Linq;
  using System.Threading;
  using Antlr4.Runtime;
  using NLog;
  using Rubberduck.Parsing.Annotations;
  using Rubberduck.Parsing.Binding;
  using Rubberduck.Parsing.Grammar;
  using Rubberduck.Parsing.Symbols;
  using Rubberduck.Parsing.VBA.DeclarationCaching;
  using Rubberduck.VBEditor;
  
  namespace Rubberduck.Parsing.VBA.ReferenceManagement
  {
      public sealed class IdentifierReferenceResolver
      {
          private readonly DeclarationFinder _declarationFinder;
          private readonly QualifiedModuleName _qualifiedModuleName;
          private readonly Stack<IBoundExpression> _withBlockExpressions;
          private readonly Declaration _moduleDeclaration;
          private Declaration _currentScope;
          private Declaration _currentParent;
          private readonly BindingService _bindingService;
          private readonly BoundExpressionVisitor _boundExpressionVisitor;
          private readonly FailedResolutionVisitor _failedResolutionVisitor;
          private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
  
          public IdentifierReferenceResolver(QualifiedModuleName qualifiedModuleName, DeclarationFinder finder)
          {
              _declarationFinder = finder;
              _qualifiedModuleName = qualifiedModuleName;
              _withBlockExpressions = new Stack<IBoundExpression>();
              _moduleDeclaration = finder.MatchName(_qualifiedModuleName.ComponentName)
                  .SingleOrDefault(item =>
                      (item.DeclarationType.HasFlag(DeclarationType.ClassModule) ||
                       item.DeclarationType == DeclarationType.ProceduralModule)
                      && item.QualifiedName.QualifiedModuleName.Equals(_qualifiedModuleName));
              SetCurrentScope();
              var typeBindingContext = new TypeBindingContext(_declarationFinder);
              var procedurePointerBindingContext = new ProcedurePointerBindingContext(_declarationFinder);
              _bindingService = new BindingService(
                  _declarationFinder,
                  new DefaultBindingContext(_declarationFinder, typeBindingContext, procedurePointerBindingContext),
                  typeBindingContext,
                  procedurePointerBindingContext);
              _boundExpressionVisitor = new BoundExpressionVisitor(finder);
              _failedResolutionVisitor = new FailedResolutionVisitor(finder);
          }
  
          public void SetCurrentScope()
          {
              _currentScope = _moduleDeclaration;
              _currentParent = _moduleDeclaration;
          }
  
          public void SetCurrentScope(string memberName, DeclarationType type)
          {
              Logger.Trace("Setting current scope: {0} ({1}) in thread {2}", memberName, type,
                  Thread.CurrentThread.ManagedThreadId);
  
              _currentParent = _declarationFinder.MatchName(memberName).SingleOrDefault(item =>
                  item.QualifiedName.QualifiedModuleName == _qualifiedModuleName && item.DeclarationType == type);
  
              _currentScope = _declarationFinder.MatchName(memberName).SingleOrDefault(item =>
                  item.QualifiedName.QualifiedModuleName == _qualifiedModuleName && item.DeclarationType == type) ??
                              _moduleDeclaration;
  
              Logger.Trace("Current scope is now {0} in thread {1}",
                  _currentScope == null ? "null" : _currentScope.IdentifierName, Thread.CurrentThread.ManagedThreadId);
          }
  
          public void EnterWithBlock(VBAParser.WithStmtContext context)
          {
              var withExpression = GetInnerMostWithExpression();
              var boundExpression = _bindingService.ResolveDefault(
                  _moduleDeclaration,
                  _currentParent,
                  context.expression(),
                  withExpression,
                  StatementResolutionContext.Undefined,
                  false);
              _failedResolutionVisitor.CollectUnresolved(boundExpression, _currentParent, withExpression);
              _boundExpressionVisitor.AddIdentifierReferences(boundExpression, _qualifiedModuleName, _currentScope, _currentParent);
              _withBlockExpressions.Push(boundExpression);
          }
  
          private IBoundExpression GetInnerMostWithExpression()
          {
              if (_withBlockExpressions.Any())
              {
                  return _withBlockExpressions.Peek();
              }
              return null;
          }
  
          public void ExitWithBlock()
          {
              _withBlockExpressions.Pop();
          }
  
          public void Resolve(VBAParser.ArgDefaultValueContext context)
          {
              var expression = context.expression();
              if (expression == null)
              {
                  return;
              }
              ResolveDefault(expression, false);
          }
  
          public void Resolve(VBAParser.ArrayDimContext context)
          {
              if (context.boundsList() == null)
              {
                  return;
              }
              foreach (var dimSpec in context.boundsList().dimSpec())
              {
                  if (dimSpec.lowerBound() != null)
                  {
                      ResolveDefault(dimSpec.lowerBound().constantExpression().expression(), true);
                  }
                  ResolveDefault(dimSpec.upperBound().constantExpression().expression(), true);
              }
          }
  
          public void Resolve(VBAParser.OnErrorStmtContext context)
          {
              if (context.expression() == null)
              {
                  return;
              }
              ResolveLabel(context.expression(), context.expression().GetText());
          }
  
          public void Resolve(VBAParser.ErrorStmtContext context)
          {
              ResolveDefault(context.expression(), true);
          }
  
          private void ResolveLabel(ParserRuleContext context, string label)
          {
              var labelDeclaration = _bindingService.ResolveGoTo(_currentParent, label);
              if (labelDeclaration != null)
              {
                  var callSiteContext = context;
                  var identifier = context.GetText();
                  var callee = labelDeclaration;
                  labelDeclaration.AddReference(
                      _qualifiedModuleName,
                      _currentScope,
                      _currentParent,
                      callSiteContext,
                      identifier,
                      callee,
                      callSiteContext.GetSelection(),
                      FindIdentifierAnnotations(_qualifiedModuleName, callSiteContext.GetSelection().StartLine));
              }
          }
  
          private IEnumerable<IParseTreeAnnotation> FindIdentifierAnnotations(QualifiedModuleName module, int line)
          {
              return _declarationFinder.FindAnnotations(module, line, AnnotationTarget.Identifier);
          }
  
          private void ResolveDefault(
              ParserRuleContext expression,
              bool requiresLetCoercion = false,
              StatementResolutionContext statementContext = StatementResolutionContext.Undefined,
              bool isAssignmentTarget = false,
              bool hasExplicitLetStatement = false,
              bool isSetAssignment = false,
              bool isReDim = false)
          {
              var withExpression = GetInnerMostWithExpression();
              var boundExpression = _bindingService.ResolveDefault(
                  _moduleDeclaration,
                  _currentParent,
                  expression,
                  withExpression,
                  statementContext,
                  requiresLetCoercion,
                  isAssignmentTarget);
  
              _failedResolutionVisitor.CollectUnresolved(boundExpression, _currentParent, withExpression);
  
              _boundExpressionVisitor.AddIdentifierReferences(
                  boundExpression, 
                  _qualifiedModuleName, 
                  _currentScope,
                  _currentParent,
                  isAssignmentTarget,
                  hasExplicitLetStatement, 
                  isSetAssignment,
                  isReDim);
          }
  
          private void ResolveType(ParserRuleContext expression)
          {
              var boundExpression = _bindingService.ResolveType(_moduleDeclaration, _currentParent, expression);
              if (boundExpression.Classification == ExpressionClassification.ResolutionFailed)
              {
                  Logger.Warn($"Type Context: Failed to resolve {expression.GetText()}. Binding as much as we can.");
              }
  
              _failedResolutionVisitor.CollectUnresolved(boundExpression, _currentParent, GetInnerMostWithExpression());
              _boundExpressionVisitor.AddIdentifierReferences(boundExpression, _qualifiedModuleName, _currentScope, _currentParent);
          }
  
          public void Resolve(VBAParser.GoToStmtContext context)
          {
              ResolveLabel(context.expression(), context.expression().GetText());
          }
  
          public void Resolve(VBAParser.OnGoToStmtContext context)
          {
              ResolveDefault(context.expression()[0], true);
              for (int labelIndex = 1; labelIndex < context.expression().Length; labelIndex++)
              {
                  ResolveLabel(context.expression()[labelIndex], context.expression()[labelIndex].GetText());
              }
          }
  
          public void Resolve(VBAParser.GoSubStmtContext context)
          {
              ResolveLabel(context.expression(), context.expression().GetText());
          }
  
          public void Resolve(VBAParser.OnGoSubStmtContext context)
          {
              ResolveDefault(context.expression()[0], true);
              for (int labelIndex = 1; labelIndex < context.expression().Length; labelIndex++)
              {
                  ResolveLabel(context.expression()[labelIndex], context.expression()[labelIndex].GetText());
              }
          }
  
          public void Resolve(VBAParser.RedimStmtContext context)
          {
              // TODO: Create local variable if no match for ReDim variable declaration.
              foreach (var redimVariableDeclaration in context.redimDeclarationList().redimVariableDeclaration())
              {
                  // We treat ReDim statements as index expressions to make it SLL.
                  var lExpr = ((VBAParser.LExprContext)redimVariableDeclaration.expression()).lExpression();
  
                  VBAParser.LExpressionContext indexedExpression;
                  VBAParser.ArgumentListContext argumentList;
                  if (lExpr is VBAParser.IndexExprContext indexExpr)
                  {
                      indexedExpression = indexExpr.lExpression();
  
gt:                     argumentList = indexExpr.argumentList();