/**
   * Removes the current variable declaration from the AST and inserts it into the lowest possible
   * scope where the variable is first used. Returns the modification to the AST within a Change
   * object.
   */
  private Change recordASTModifications() {
    AST ast = compilationUnit.getAST();
    ASTRewrite rewriter = ASTRewrite.create(ast);

    // duplicate the original variable declaration in lower scope before getting rid of it
    VariableDeclarationStatement oldDeclaration =
        (VariableDeclarationStatement) variableVisitor.getDeclaration();

    assert variableVisitor.getFirstAssignment() instanceof Assignment;
    Assignment variableAssignment = (Assignment) variableVisitor.getFirstAssignment();
    ASTNode newScopeParent = variableAssignment.getParent().getParent();

    String codeString = oldDeclaration.toString();
    ASTNode newDeclaration =
        rewriter.createStringPlaceholder(
            codeString.substring(0, codeString.length() - 1),
            ASTNode.VARIABLE_DECLARATION_STATEMENT);

    ListRewrite listRewrite = rewriter.getListRewrite(newScopeParent, Block.STATEMENTS_PROPERTY);
    listRewrite.insertAt(newDeclaration, 0, null);

    // get rid of the higher scope declaration
    rewriter.remove(oldDeclaration, null);

    ICompilationUnit iCompUnit = method.getCompilationUnit();

    final String description = "ReduceScopeOfVariable";
    final String comment = "ReduceScopeOfVariable does blah...";

    // record the change
    CompilationUnitChange change =
        new CompilationUnitChange("Reduce scope of variable", iCompUnit) {
          public ChangeDescriptor getDescriptor() {
            Map<Object, Object> arguments = new HashMap<Object, Object>();
            arguments.put("method", method);
            arguments.put("var", varName);
            return new RefactoringChangeDescriptor(
                new ReduceScopeOfVariableDescriptor(
                    method.getJavaProject().getElementName(), description, comment, arguments));
          }
        };
    try {
      change.setEdit(rewriter.rewriteAST());
    } catch (Exception e) {
    }

    return change;
  }
  private Example makeExample(
      MethodDeclaration node,
      Set<? extends Expression> envolvedInvocations,
      List<ApiMethod> envolvedApiMethods) {

    //  Visitor responsável por realizar o slicing de programas
    SlicingStatementVisitor visitor =
        new SlicingStatementVisitor(node, new HashSet<ASTNode>(envolvedInvocations));
    node.accept(visitor);

    Collection<Statement> relatedStatements = visitor.getSlicedStatements();

    ASTNode newAST =
        ASTUtil.copyStatements(node.getBody(), relatedStatements, AST.newAST(AST.JLS3));
    if (!relatedStatements.isEmpty()) {
      LOGGER.error("Some statements were not included!");
    }

    if (newAST == null) {
      LOGGER.error("Slicing process failed for node ");
      // TODO Se AST retornada for nula é porque faltou incluir statement(s)
      return null;
    } else if (((Block) newAST).statements().isEmpty()) {
      LOGGER.error("Slicing process failed for node ");
      // TODO Se o Block retornado for vazio é porque faltou incluir statement(s)
      return null;
    }

    ASTUtil.removeEmptyBlocks((Block) newAST);

    // Adiciona declarações de variáveis que não foram encontradas no escopo do método
    // Para facilitar, tipos iguais são declarados no mesmo Statement
    Set<String> additionalDeclarationLines = new HashSet<String>();
    Map<ITypeBinding, List<IVariableBinding>> typesMap =
        new HashMap<ITypeBinding, List<IVariableBinding>>();
    for (IVariableBinding ivb : visitor.getUndiscoveredDeclarations()) {
      if (!typesMap.containsKey(ivb.getType())) {
        typesMap.put(ivb.getType(), new ArrayList<IVariableBinding>(2));
      }
      typesMap.get(ivb.getType()).add(ivb);
    }

    for (ITypeBinding typeBinding : typesMap.keySet()) {
      List<IVariableBinding> variableBindings = typesMap.get(typeBinding);

      Stack<VariableDeclarationFragment> fragments = new Stack<VariableDeclarationFragment>();
      for (IVariableBinding ivb : variableBindings) {
        VariableDeclarationFragment declarationFragment =
            newAST.getAST().newVariableDeclarationFragment();
        declarationFragment.setName(newAST.getAST().newSimpleName(ivb.getName()));
        fragments.add(declarationFragment);
      }

      VariableDeclarationStatement statement =
          newAST.getAST().newVariableDeclarationStatement(fragments.pop());
      while (!fragments.isEmpty()) {
        statement.fragments().add(fragments.pop());
      }

      statement.setType(this.getType(typeBinding, newAST.getAST()));

      additionalDeclarationLines.add(statement.toString());
      ((Block) newAST).statements().add(0, statement);
    }

    Example example = new Example();
    example.setAttachment(this.attachmentMap.get(node.getRoot()));
    example.setApiMethods(new HashSet<ApiElement>(envolvedApiMethods));
    example.setImports(visitor.getImports());

    for (Expression seed : envolvedInvocations) {
      example.getSeeds().add(seed.toString());
    }

    example.setSourceMethod(node.toString());
    example.setAddedAt(new Date(System.currentTimeMillis()));

    try {
      IMethodBinding nodeBinding = node.resolveBinding();
      if (!this.methodMap.containsKey(nodeBinding)) {
        ApiClass newApiClass = new ApiClass(nodeBinding.getDeclaringClass().getQualifiedName());
        methodDeclarationHandler(node, newApiClass);
      }
      example.setSourceMethodCall(this.methodMap.get(nodeBinding).getFullName());
    } catch (Exception e) {
      LOGGER.error(e);
      if (example.getSourceMethodCall() == null) {
        example.setSourceMethodCall("?");
      }
    }

    String codeExample = newAST.toString();
    for (String line : additionalDeclarationLines) {
      codeExample =
          codeExample.replace(
              line,
              line.replace("\n", "").concat("  ").concat("//initialized previously").concat("\n"));
    }

    // FIXME
    codeExample = codeExample.replaceAll("(\\{\n)(\\s+)(\\})", "$1 //do something \n$3");

    try {
      example.setCodeExample(codeExample);
      example.setFormattedCodeExample(ASTUtil.codeFormatter(codeExample));
    } catch (Exception e) {
      LOGGER.error(e);
      if (example.getFormattedCodeExample() == null) {
        example.setFormattedCodeExample(codeExample);
      }
    }

    // TODO Obter métricas do exemplo
    example
        .getMetrics()
        .put(ExampleMetric.LOC.name(), example.getFormattedCodeExample().split("\n").length - 1);
    example.getMetrics().put(ExampleMetric.ARGUMENTS.name(), visitor.getNumberOfArguments());
    example
        .getMetrics()
        .put(ExampleMetric.DECISION_STATEMENTS.name(), visitor.getNumberOfDecisionStatements());
    example.getMetrics().put(ExampleMetric.INVOCATIONS.name(), visitor.getNumberOfInvocations());
    example
        .getMetrics()
        .put(ExampleMetric.NULL_ARGUMENTS.name(), visitor.getNumberOfNullArguments());
    example
        .getMetrics()
        .put(ExampleMetric.PRIMITIVE_ARGUMENTS.name(), visitor.getNumberOfPrimitiveArguments());
    example
        .getMetrics()
        .put(ExampleMetric.FIELD_ARGUMENTS.name(), visitor.getNumberOfFieldArguments());
    example
        .getMetrics()
        .put(
            ExampleMetric.UNDISCOVERED_DECLARATIONS.name(),
            visitor.getNumberOfUndiscoveredDeclarations());
    example
        .getMetrics()
        .put(ExampleMetric.UNHANDLED_EXCEPTIONS.name(), visitor.getNumberOfUnhandledExceptions());

    return example;
  }