using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace Microsoft.Dafny;

public class IfStmt : Statement, ICloneable<IfStmt>, ICanFormat {
  public readonly bool IsBindingGuard;
  public readonly Expression Guard;
  public readonly BlockStmt Thn;
  public readonly Statement Els;
  [ContractInvariantMethod]
  void ObjectInvariant() {
    Contract.Invariant(!IsBindingGuard || (Guard is ExistsExpr && ((ExistsExpr)Guard).Range == null));
    Contract.Invariant(Thn != null);
    Contract.Invariant(Els == null || Els is BlockStmt || Els is IfStmt || Els is SkeletonStatement);
  }

  public IfStmt Clone(Cloner cloner) {
    return new IfStmt(cloner, this);
  }

  public IfStmt(Cloner cloner, IfStmt original) : base(cloner, original) {
    IsBindingGuard = original.IsBindingGuard;
    Guard = cloner.CloneExpr(original.Guard);
    Thn = cloner.CloneBlockStmt(original.Thn);
    Els = cloner.CloneStmt(original.Els, false);
  }

  public IfStmt(RangeToken rangeToken, bool isBindingGuard, Expression guard, BlockStmt thn, Statement els)
    : base(rangeToken) {
    Contract.Requires(rangeToken != null);
    Contract.Requires(!isBindingGuard || (guard is ExistsExpr && ((ExistsExpr)guard).Range == null));
    Contract.Requires(thn != null);
    Contract.Requires(els == null || els is BlockStmt || els is IfStmt || els is SkeletonStatement);
    this.IsBindingGuard = isBindingGuard;
    this.Guard = guard;
    this.Thn = thn;
    this.Els = els;
  }
  public IfStmt(RangeToken rangeToken, bool isBindingGuard, Expression guard, BlockStmt thn, Statement els, Attributes attrs)
    : base(rangeToken, attrs) {
    Contract.Requires(rangeToken != null);
    Contract.Requires(!isBindingGuard || (guard is ExistsExpr && ((ExistsExpr)guard).Range == null));
    Contract.Requires(thn != null);
    Contract.Requires(els == null || els is BlockStmt || els is IfStmt || els is SkeletonStatement);
    this.IsBindingGuard = isBindingGuard;
    this.Guard = guard;
    this.Thn = thn;
    this.Els = els;
  }
  public override IEnumerable<Statement> SubStatements {
    get {
      yield return Thn;
      if (Els != null) {
        yield return Els;
      }
    }
  }
  public override IEnumerable<Expression> NonSpecificationSubExpressions {
    get {
      foreach (var e in base.NonSpecificationSubExpressions) { yield return e; }
      if (Guard != null) {
        yield return Guard;
      }
    }
  }

  public bool SetIndent(int indentBefore, TokenNewIndentCollector formatter) {
    foreach (var token in OwnedTokens) {
      if (formatter.SetIndentLabelTokens(token, indentBefore)) {
        continue;
      }
      switch (token.val) {
        case "if": {
            formatter.SetOpeningIndentedRegion(token, indentBefore);
            formatter.Visit(Guard, indentBefore);
            formatter.SetIndentBody(Thn, indentBefore);
            break;
          }
        case "else": {
            formatter.SetKeywordWithoutSurroundingIndentation(token, indentBefore);
            formatter.SetIndentBody(Els, indentBefore);
            break;
          }
      }
    }
    return false;
  }

  public void Resolve(INewOrOldResolver resolver, ResolutionContext resolutionContext) {
    if (Guard != null) {
      if (!resolutionContext.IsGhost && IsBindingGuard && resolver.Options.ForbidNondeterminism) {
        resolver.Reporter.Error(MessageSource.Resolver, GeneratorErrors.ErrorId.c_binding_if_forbidden, Tok, "binding if statement forbidden by the --enforce-determinism option");
      }
      resolver.ResolveExpression(Guard, resolutionContext);
      resolver.ConstrainTypeExprBool(Guard, "condition is expected to be of type bool, but is {0}");
    } else {
      if (!resolutionContext.IsGhost && resolver.Options.ForbidNondeterminism) {
        resolver.Reporter.Error(MessageSource.Resolver, GeneratorErrors.ErrorId.c_nondeterministic_if_forbidden, Tok, "nondeterministic if statement forbidden by the --enforce-determinism option");
      }
    }

    resolver.Scope.PushMarker();
    if (IsBindingGuard) {
      var exists = (ExistsExpr)Guard;
      foreach (var v in exists.BoundVars) {
        resolver.ScopePushAndReport(resolver.Scope, v.Name, v, v.Tok, "bound-variable");
      }
    }
    resolver.DominatingStatementLabels.PushMarker();
    resolver.ResolveBlockStatement(Thn, resolutionContext);
    resolver.DominatingStatementLabels.PopMarker();
    resolver.Scope.PopMarker();

    if (Els != null) {
      resolver.DominatingStatementLabels.PushMarker();
      resolver.ResolveStatement(Els, resolutionContext);
      resolver.DominatingStatementLabels.PopMarker();
    }
  }
}