/**
  * Remove an statement
  *
  * @param statement
  */
 private void remove(CtStatement statement) {
   if (statement.getParent() instanceof CtBlock) {
     ((CtBlock) statement.getParent()).removeStatement(statement);
   } else {
     CtCodeSnippetStatementImpl comment = new CtCodeSnippetStatementImpl();
     comment.setValue("/*REMOVED*/");
     statement.replace(comment);
   }
 }
  protected CtMethod apply(CtMethod method, List<Statement> statements, int index) {
    CtMethod cloned_method = AmplificationHelper.cloneMethodTest(method, "_cf", 1000);
    CtStatement stmt = getAssertStatement(cloned_method).get(index);
    statements
        .stream()
        .forEach(
            c -> {
              stmt.insertBefore((CtStatement) c.getCtCodeFragment());
              c.getCtCodeFragment().setParent(stmt.getParent());
            });

    return cloned_method;
  }
  protected List<CtLocalVariable> getLocalVarInScope(CtStatement stmt) {
    List<CtLocalVariable> vars = new ArrayList<>();
    try {
      CtBlock parentBlock = stmt.getParent(CtBlock.class);
      if (parentBlock != null) {
        boolean beforeCurrentStmt = true;
        int i = 0;
        List<CtStatement> stmts = parentBlock.getStatements();

        while (beforeCurrentStmt && i < stmts.size()) {
          CtStatement currentStatement = stmts.get(i);
          i++;
          beforeCurrentStmt = beforeCurrentStmt && currentStatement != stmt;
          if (currentStatement instanceof CtLocalVariable) {
            vars.add((CtLocalVariable) currentStatement);
          }
        }
        vars.addAll(getLocalVarInScope(parentBlock));
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    return vars;
  }
  /**
   * Resolves all the variables used by the snippet. The ones which are initialized and the ones
   * which are not
   *
   * @return True if the context was extracted successfully
   */
  private boolean resolveDataContext(CtStatement statement) {

    needsInitialization = false;

    // Check some preconditions needed for the processor to run:
    // All variable access made inside the statement
    List<CtVariableAccess> access = statement.getElements(new TypeFilter<>(CtVariableAccess.class));

    if (statement.getElements(new TypeFilter<>(CtThisAccess.class)).size() > 0) {
      mustSerializeThiz =
          new Preconditions().checkTypeRef(statement.getParent(CtClass.class).getReference());
    }

    initialized = new HashSet<>();

    // Get all THIZ field access from all invocations used in the element
    HashSet<CtInvocation> visited = new HashSet<>();
    Stack<CtInvocation> invStack = new Stack<>();
    invStack.addAll(statement.getElements(new TypeFilter<>(CtInvocation.class)));
    while (!invStack.empty()) {
      CtInvocation inv = invStack.pop();
      if (!visited.contains(inv)) {
        visited.add(inv);
        CtBlock b;
        try {
          b = inv.getExecutable().getDeclaration().getBody();
        } catch (NullPointerException ex) {
          b = null;
        }
        if (visibility(inv) != PUBLIC && b != null) {
          for (CtFieldAccess ta :
              b.getElements(new TypeFilter<CtFieldAccess>(CtFieldAccess.class))) {
            if (ta.getTarget() instanceof CtThisAccess || ta.getTarget() == null) {
              access.add(ta);
              // initialized.add(ta);
            }
          }
          for (CtInvocation i : b.getElements(new TypeFilter<CtInvocation>(CtInvocation.class))) {
            if (!visited.contains(i)) invStack.push(i);
          }
        }
      }
    }

    access = cleanRepeatedAccesses(access);
    setAccesses(access);

    // Find initialized variables of allowed types
    ControlFlowBuilder v = new ControlFlowBuilder();
    CtMethod m = statement.getParent(CtMethod.class);
    if (m == null) return false;
    m.accept(v);
    ControlFlowGraph g = v.getResult();
    g.simplify();
    try {
      InitializedVariables vars = new InitializedVariables();
      vars.run(ControlFlowBuilder.firstNode(g, statement));

      for (CtVariableAccess a : access) {
        // A variable must be initialized if is local and is not a serializable field
        // Also if is not a constant. Constant get declared in the body of the microbenchmark
        if (isInitialized(a, statement, vars) && !isAConstant(a)) initialized.add(a);
      }
    } catch (NotFoundException e) {
      return false;
    } catch (StackOverflowError e) {
      System.out.print(g.toGraphVisText());
      throw e;
    }

    needsInitialization = initialized.size() > 0;
    if (access.size() <= 0) return true;

    int i = 0;
    int replaced = 0;
    do {
      if (i == 0) replaced = 0;
      CtVariableAccess a = access.get(i);
      if (canBeReplacedByTarget(a)) {
        CtVariableAccess targetOfA = null;
        CtTargetedExpression ta = (CtTargetedExpression) a;
        if (ta.getTarget() != null) {
          if (ta.getTarget() instanceof CtVariableAccess) {
            targetOfA = (CtVariableAccess) ta.getTarget();
          } else if (ta.getTarget() instanceof CtThisAccess) {
            mustSerializeThiz = true;
          }
        } else {
          mustSerializeThiz = true;
        }
        if (targetOfA != null) {
          if (!access.contains(targetOfA)) access.add(targetOfA);
          if (initialized.contains(a) && !initialized.contains(targetOfA))
            initialized.add(targetOfA);
        }
        access.remove(a);
        if (initialized.contains(a)) initialized.remove(a);
        replaced++;
      } else i++;
      if (i >= access.size()) i = 0;
    } while (replaced > 0 || i != 0);
    return true;
  }
  @Override
  public void process(CtConditional ctConditional) {
    CtStatement parent = ctConditional.getParent(CtStatement.class);
    while (!(parent.getParent() instanceof CtStatementList)) {
      parent = parent.getParent(CtStatement.class);
    }
    CtExpression condition = ctConditional.getCondition();

    CtIf anIf = getFactory().Core().createIf();
    anIf.setPosition(ctConditional.getPosition());

    if (parent instanceof CtReturn) {
      if (!((CtReturn) parent).getReturnedExpression().equals(ctConditional)) {
        return;
      }

      CtReturn returnThen = (CtReturn) parent.clone();
      CtReturn returnElse = (CtReturn) parent.clone();

      returnThen.setReturnedExpression(ctConditional.getThenExpression());
      returnElse.setReturnedExpression(ctConditional.getElseExpression());

      List<CtTypeReference> typeCasts = ctConditional.getTypeCasts();
      for (int i = 0; i < typeCasts.size(); i++) {
        CtTypeReference ctTypeReference = typeCasts.get(i);
        returnThen.getReturnedExpression().addTypeCast(ctTypeReference.clone());
        returnElse.getReturnedExpression().addTypeCast(ctTypeReference.clone());
      }

      anIf.setElseStatement(getFactory().Code().createCtBlock(returnElse));
      anIf.setThenStatement(getFactory().Code().createCtBlock(returnThen));
    } else if (parent instanceof CtAssignment) {
      CtAssignment assignment = (CtAssignment) parent;
      CtExpression ctExpression = assignment.getAssignment();
      if (!ctExpression.equals(ctConditional)) {
        if (ctExpression instanceof CtBinaryOperator) {
          CtBinaryOperator ctBinaryOperator = (CtBinaryOperator) ctExpression;

          createAssignment(ctConditional, anIf, assignment);
          CtBinaryOperator cloneThen = ctBinaryOperator.clone();
          CtBinaryOperator cloneElse = ctBinaryOperator.clone();

          if (ctBinaryOperator.getLeftHandOperand().equals(ctConditional)) {
            cloneThen.setLeftHandOperand(ctConditional.getThenExpression());
            ctConditional.getThenExpression().setParent(cloneThen);
            cloneElse.setLeftHandOperand(ctConditional.getElseExpression());
          } else if (ctBinaryOperator.getRightHandOperand().equals(ctConditional)) {
            cloneThen.setRightHandOperand(ctConditional.getThenExpression());
            cloneElse.setRightHandOperand(ctConditional.getElseExpression());
          }

          cloneThen.getLeftHandOperand().setParent(cloneThen);
          cloneElse.getLeftHandOperand().setParent(cloneElse);
          ((CtAssignment) ((CtBlock) anIf.getThenStatement()).getStatement(0))
              .setAssignment(cloneThen);
          ((CtAssignment) ((CtBlock) anIf.getElseStatement()).getStatement(0))
              .setAssignment(cloneElse);
        } else {
          return;
        }
      } else {
        createAssignment(ctConditional, anIf, assignment);
      }
    } else if (parent instanceof CtLocalVariable) {
      CtLocalVariable localVariable = (CtLocalVariable) parent;
      if (!localVariable.getDefaultExpression().equals(ctConditional)) {
        return;
      }

      CtLocalVariable clone = localVariable.clone();
      clone.setDefaultExpression(null);

      localVariable.insertBefore(clone);

      CtAssignment variableAssignment =
          getFactory()
              .Code()
              .createVariableAssignment(localVariable.getReference(), false, ctConditional);
      variableAssignment.setType(localVariable.getType().clone());
      variableAssignment.setPosition(ctConditional.getPosition());
      createAssignment(ctConditional, anIf, variableAssignment);
    } else if (parent instanceof CtInvocation) {
      CtInvocation invocation = (CtInvocation) parent;
      CtInvocation cloneThen = invocation.clone();
      CtInvocation cloneElse = invocation.clone();

      List arguments = cloneThen.getArguments();
      boolean found = false;
      for (int i = 0; i < arguments.size(); i++) {
        Object o = arguments.get(i);
        if (o.equals(ctConditional)) {
          ctConditional.getThenExpression().setParent(invocation);
          arguments.set(i, ctConditional.getThenExpression());
          ctConditional.getElseExpression().setParent(invocation);
          cloneElse.getArguments().set(i, ctConditional.getElseExpression());
          found = true;
          break;
        }
      }
      if (!found) {
        return;
      }

      cloneThen.setParent(anIf);
      cloneElse.setParent(anIf);

      anIf.setElseStatement(getFactory().Code().createCtBlock(cloneElse));
      anIf.setThenStatement(getFactory().Code().createCtBlock(cloneThen));
    } else if (parent instanceof CtConstructorCall) {
      CtConstructorCall invocation = (CtConstructorCall) parent;
      CtConstructorCall cloneThen = invocation.clone();
      CtConstructorCall cloneElse = invocation.clone();

      List arguments = cloneThen.getArguments();
      boolean found = false;
      for (int i = 0; i < arguments.size(); i++) {
        Object o = arguments.get(i);
        if (o.equals(ctConditional)) {
          arguments.set(i, ctConditional.getThenExpression());
          cloneElse.getArguments().set(i, ctConditional.getElseExpression());
          found = true;
          break;
        }
      }
      if (!found) {
        return;
      }

      cloneThen.setParent(anIf);
      cloneElse.setParent(anIf);

      anIf.setElseStatement(getFactory().Code().createCtBlock(cloneElse));
      anIf.setThenStatement(getFactory().Code().createCtBlock(cloneThen));
    } else if (parent instanceof CtIf) {
      CtIf elem = (CtIf) parent;
      if (!elem.getCondition().equals(ctConditional)) {
        return;
      }

      CtIf cloneThen = elem.clone();
      cloneThen.setParent(anIf);
      CtIf cloneElse = elem.clone();
      cloneElse.setParent(anIf);

      cloneThen.setCondition(ctConditional.getThenExpression());
      ctConditional.getThenExpression().setParent(cloneThen);

      cloneElse.setCondition(ctConditional.getElseExpression());
      ctConditional.getElseExpression().setParent(cloneElse);

      anIf.setElseStatement(getFactory().Code().createCtBlock(cloneElse));
      anIf.setThenStatement(getFactory().Code().createCtBlock(cloneThen));
    } else if (parent instanceof CtThrow) {
      return;
    } else if (parent instanceof CtLoop) {
      return;
    } else if (parent instanceof CtUnaryOperator) {
      return;
    } else {
      System.err.println(parent);
      throw new RuntimeException("Other " + parent.getClass());
    }
    /*if(ctConditional.getThenExpression().getTypeCasts() == null ||
            ctConditional.getThenExpression().getTypeCasts().isEmpty()) {
        ((CtExpression)anIf.getThenStatement()).setTypeCasts(ctConditional.getTypeCasts());
    }
    if(ctConditional.getElseExpression().getTypeCasts() == null ||
            ctConditional.getElseExpression().getTypeCasts().isEmpty()) {
        ((CtExpression) anIf.getElseStatement())
                .setTypeCasts(ctConditional.getTypeCasts());
    }*/
    anIf.setCondition(condition);
    condition.setParent(anIf);
    parent.replace(anIf);
  }