package org.checkerframework.dataflow.cfg.builder;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.util.TreePath;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.dataflow.cfg.ControlFlowGraph;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
import org.checkerframework.dataflow.cfg.block.Block;
import org.checkerframework.dataflow.cfg.block.ConditionalBlockImpl;
import org.checkerframework.dataflow.cfg.block.ExceptionBlockImpl;
import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.BasicAnnotationProvider;
import org.checkerframework.javacutil.trees.TreeBuilder;

/**
 * Builds the control flow graph of some Java code (either a method, or an arbitrary statement).
 *
 * <p>The translation of the AST to the CFG is split into three phases:
 *
 * <ol>
 *   <li><em>Phase one.</em> In the first phase, the AST is translated into a sequence of {@link
 *       org.checkerframework.dataflow.cfg.builder.ExtendedNode}s. An extended node can either be a
 *       {@link Node}, or one of several meta elements such as a conditional or unconditional jump
 *       or a node with additional information about exceptions. Some of the extended nodes contain
 *       labels (e.g., for the jump target), and phase one additionally creates a mapping from
 *       labels to extended nodes. Finally, the list of leaders is computed: A leader is an extended
 *       node which will give rise to a basic block in phase two.
 *   <li><em>Phase two.</em> In this phase, the sequence of extended nodes is translated to a graph
 *       of control flow blocks that contain nodes. The meta elements from phase one are translated
 *       into the correct edges.
 *   <li><em>Phase three.</em> The control flow graph generated in phase two can contain degenerate
 *       basic blocks such as empty regular basic blocks or conditional basic blocks that have the
 *       same block as both 'then' and 'else' successor. This phase removes these cases while
 *       preserving the control flow structure.
 * </ol>
 */
public abstract class CFGBuilder {

  /** Creates a CFGBuilder. */
  protected CFGBuilder() {}

  /**
   * Build the control flow graph of some code.
   *
   * @param root the compilation unit
   * @param underlyingAST the AST that underlies the control frow graph
   * @param assumeAssertionsDisabled can assertions be assumed to be disabled?
   * @param assumeAssertionsEnabled can assertions be assumed to be enabled?
   * @param env annotation processing environment containing type utilities
   * @return a control flow graph
   */
  public static ControlFlowGraph build(
      CompilationUnitTree root,
      UnderlyingAST underlyingAST,
      boolean assumeAssertionsEnabled,
      boolean assumeAssertionsDisabled,
      ProcessingEnvironment env) {
    TreeBuilder builder = new TreeBuilder(env);
    AnnotationProvider annotationProvider = new BasicAnnotationProvider();
    PhaseOneResult phase1result =
        new CFGTranslationPhaseOne(
                builder, annotationProvider, assumeAssertionsEnabled, assumeAssertionsDisabled, env)
            .process(root, underlyingAST);
    ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result);
    ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result);
    return phase3result;
  }

  /**
   * Build the control flow graph of some code (method, initializer block, ...). bodyPath is the
   * TreePath to the body of that code.
   */
  public static ControlFlowGraph build(
      TreePath bodyPath,
      UnderlyingAST underlyingAST,
      boolean assumeAssertionsEnabled,
      boolean assumeAssertionsDisabled,
      ProcessingEnvironment env) {
    TreeBuilder builder = new TreeBuilder(env);
    AnnotationProvider annotationProvider = new BasicAnnotationProvider();
    PhaseOneResult phase1result =
        new CFGTranslationPhaseOne(
                builder, annotationProvider, assumeAssertionsEnabled, assumeAssertionsDisabled, env)
            .process(bodyPath, underlyingAST);
    ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result);
    ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result);
    return phase3result;
  }

  /** Build the control flow graph of some code. */
  public static ControlFlowGraph build(
      CompilationUnitTree root, UnderlyingAST underlyingAST, ProcessingEnvironment env) {
    return build(root, underlyingAST, false, false, env);
  }

  /** Build the control flow graph of a method. */
  public static ControlFlowGraph build(
      CompilationUnitTree root, MethodTree tree, ClassTree classTree, ProcessingEnvironment env) {
    UnderlyingAST underlyingAST = new CFGMethod(tree, classTree);
    return build(root, underlyingAST, false, false, env);
  }

  /**
   * Return a printed representation of a collection of extended nodes.
   *
   * @param nodes a collection of extended nodes to format
   * @return a printed representation of the given collection
   */
  public static String extendedNodeCollectionToStringDebug(
      Collection<? extends ExtendedNode> nodes) {
    StringJoiner result = new StringJoiner(", ", "[", "]");
    for (ExtendedNode n : nodes) {
      result.add(n.toStringDebug());
    }
    return result.toString();
  }

  /* --------------------------------------------------------- */
  /* Utility routines for debugging CFG building */
  /* --------------------------------------------------------- */

  /**
   * Print a set of {@link Block}s and the edges between them. This is useful for examining the
   * results of phase two.
   *
   * @param blocks the blocks to print
   */
  protected static void printBlocks(Set<Block> blocks) {
    for (Block b : blocks) {
      System.out.print(b.getUid() + ": " + b);
      switch (b.getType()) {
        case REGULAR_BLOCK:
        case SPECIAL_BLOCK:
          {
            Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor();
            System.out.println(" -> " + (succ != null ? succ.getUid() : "||"));
            break;
          }
        case EXCEPTION_BLOCK:
          {
            Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor();
            System.out.print(" -> " + (succ != null ? succ.getUid() : "||") + " {");
            for (Map.Entry<TypeMirror, Set<Block>> entry :
                ((ExceptionBlockImpl) b).getExceptionalSuccessors().entrySet()) {
              System.out.print(entry.getKey() + " : " + entry.getValue() + ", ");
            }
            System.out.println("}");
            break;
          }
        case CONDITIONAL_BLOCK:
          {
            Block tSucc = ((ConditionalBlockImpl) b).getThenSuccessor();
            Block eSucc = ((ConditionalBlockImpl) b).getElseSuccessor();
            System.out.println(
                " -> T "
                    + (tSucc != null ? tSucc.getUid() : "||")
                    + " F "
                    + (eSucc != null ? eSucc.getUid() : "||"));
            break;
          }
      }
    }
  }
}
