using System.Collections.Generic;
using Microsoft.Boogie;

namespace Core
{
  /// <summary>
  /// Traverses an expression, replacing all holes with bound variables taken from the <see cref="_replDummies"/>
  /// queue.
  /// </summary>
  class LambdaLiftingMaxHolesFiller : StandardVisitor
  {
    private readonly List<Absy> _holes;
    private readonly QKeyValue _lambdaAttrs;
    private readonly Queue<Variable> _replDummies;

    private LambdaLiftingMaxHolesFiller(List<Absy> holes, IEnumerable<Variable> replDummies, QKeyValue lambdaAttrs)
    {
      _holes = holes;
      _lambdaAttrs = lambdaAttrs;
      _replDummies = new Queue<Variable>(replDummies);
    }

    public static Expr Fill(List<Absy> holes,
      List<Variable> replDummies,
      Expr expr, QKeyValue lambdaAttrs)
    {
      return new LambdaLiftingMaxHolesFiller(holes, replDummies, lambdaAttrs).VisitExpr(expr);
    }

    private bool ShouldBeReplaced(Absy node)
    {
      return _holes.Contains(node);
    }

    private bool GetReplacementVariable(Absy node, out Variable variable)
    {
      if (_replDummies.Count > 0 && ShouldBeReplaced(node))
      {
        variable = _replDummies.Dequeue();
        return true;
      }

      variable = null;
      return false;
    }

    public override Expr VisitLambdaExpr(LambdaExpr node)
    {
      var attributes = _lambdaAttrs == null ? null : VisitQKeyValue(_lambdaAttrs);
      var body = VisitExpr(node.Body);
      return new LambdaExpr(node.tok, node.TypeParameters, node.Dummies, attributes, body);
    }

    public override Variable VisitVariable(Variable node)
    {
      if (GetReplacementVariable(new IdentifierExpr(node.tok, node), out var variable))
      {
        return variable;
      }

      return base.VisitVariable(node);
    }

    public override Expr VisitExistsExpr(ExistsExpr node)
    {
      if (GetReplacementVariable(node, out var variable))
      {
        return new IdentifierExpr(variable.tok, variable);
      }

      return base.VisitExistsExpr(node);
    }

    public override Expr VisitForallExpr(ForallExpr node)
    {
      if (GetReplacementVariable(node, out var variable))
      {
        return new IdentifierExpr(variable.tok, variable);
      }

      return base.VisitForallExpr(node);
    }

    public override Expr VisitIdentifierExpr(IdentifierExpr node)
    {
      if (GetReplacementVariable(node, out var variable))
      {
        return new IdentifierExpr(variable.tok, variable);
      }

      return base.VisitIdentifierExpr(node);
    }

    public override Expr VisitLetExpr(LetExpr node)
    {
      if (GetReplacementVariable(node, out var variable))
      {
        return new IdentifierExpr(variable.tok, variable);
      }

      return base.VisitLetExpr(node);
    }

    public override Expr VisitLiteralExpr(LiteralExpr node)
    {
      if (GetReplacementVariable(node, out var variable))
      {
        return new IdentifierExpr(variable.tok, variable);
      }

      return base.VisitLiteralExpr(node);
    }

    public override Expr VisitNAryExpr(NAryExpr node)
    {
      if (GetReplacementVariable(node, out var variable))
      {
        return new IdentifierExpr(variable.tok, variable);
      }

      return base.VisitNAryExpr(node);
    }

    public override Expr VisitOldExpr(OldExpr node)
    {
      if (GetReplacementVariable(node, out var variable))
      {
        return new IdentifierExpr(variable.tok, variable);
      }

      return base.VisitOldExpr(node);
    }
  }
}