package soot.dava.toolkits.base.AST.transformations;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 2006 Nomair A. Naeem
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */

import java.util.Iterator;
import java.util.List;

import soot.G;
import soot.dava.DecompilationException;
import soot.dava.internal.AST.ASTDoWhileNode;
import soot.dava.internal.AST.ASTForLoopNode;
import soot.dava.internal.AST.ASTLabeledNode;
import soot.dava.internal.AST.ASTMethodNode;
import soot.dava.internal.AST.ASTNode;
import soot.dava.internal.AST.ASTStatementSequenceNode;
import soot.dava.internal.AST.ASTSwitchNode;
import soot.dava.internal.AST.ASTTryNode;
import soot.dava.internal.AST.ASTUnconditionalLoopNode;
import soot.dava.internal.AST.ASTWhileNode;
import soot.dava.internal.asg.AugmentedStmt;
import soot.dava.internal.javaRep.DAbruptStmt;
import soot.dava.toolkits.base.AST.analysis.DepthFirstAdapter;
import soot.dava.toolkits.base.AST.traversals.ASTParentNodeFinder;
import soot.dava.toolkits.base.AST.traversals.LabelToNodeMapper;
import soot.jimple.Stmt;

/*
 * It has been seen that a lot of times there are break statements targeting
 * a label absolutely unnecessarily (with continues this is rare but to be complete
 * we will handle them too)
 * An example:
 *    label1:
 *    if(cond1){
 *      BodyA
 *      break label1
 *    }
 *    
 *    As in the above code the break stmt is absolutely unnessary as the code
 *    will itself flow to the required position.
 *    
 *    However, remember that breaks and continues are also used to 
 *    exit or repeat a loop in which case we should keep the break and continue!!!
 *    
 *    TODO Also if we do decide to remove an abrupt stmt make sure that the
 *    stmt seq node has not become empty. If it has remove the node and see
 *    if the construct carrying this node has become empty and so on..... 
 */
public class UselessAbruptStmtRemover extends DepthFirstAdapter {
  public static boolean DEBUG = false;

  ASTParentNodeFinder finder;
  ASTMethodNode methodNode;
  LabelToNodeMapper mapper;

  public UselessAbruptStmtRemover() {
    finder = null;
  }

  public UselessAbruptStmtRemover(boolean verbose) {
    super(verbose);
    finder = null;
  }

  public void inASTMethodNode(ASTMethodNode node) {
    methodNode = node;
    mapper = new LabelToNodeMapper();
    methodNode.apply(mapper);
  }

  public void caseASTStatementSequenceNode(ASTStatementSequenceNode node) {
    Iterator<AugmentedStmt> it = node.getStatements().iterator();
    AugmentedStmt remove = null;
    ASTLabeledNode target = null;
    while (it.hasNext()) {
      AugmentedStmt as = it.next();
      Stmt s = as.get_Stmt();

      // we only care about break and continue stmts
      if (!(s instanceof DAbruptStmt)) {
        continue;
      }

      DAbruptStmt abrupt = (DAbruptStmt) s;
      String label = abrupt.getLabel().toString();
      if (label == null) {
        // could at some time implement a version of the same
        // analysis with implicit abrupt flow but not needed currently
        continue;
      }

      if (it.hasNext()) {
        // there is an abrupt stmt and this stmt seq node has something
        // afterwards...that is for sure dead code
        throw new DecompilationException("Dead code detected. Report to developer");
      }

      // get the target node
      Object temp = mapper.getTarget(label);
      if (temp == null) {
        continue;
        // throw new DecompilationException("Could not find target for abrupt stmt"+abrupt.toString());
      }

      target = (ASTLabeledNode) temp;

      // will need to find parents of ancestors see if we need to initialize the finder
      if (finder == null) {
        finder = new ASTParentNodeFinder();
        methodNode.apply(finder);
      }

      if (DEBUG) {
        System.out.println("Starting useless check for abrupt stmt: " + abrupt);
      }

      // start condition is that ancestor is the stmt seq node
      ASTNode ancestor = node;

      while (ancestor != target) {
        Object tempParent = finder.getParentOf(ancestor);
        if (tempParent == null) {
          throw new DecompilationException("Parent found was null!!. Report to Developer");
        }

        ASTNode ancestorsParent = (ASTNode) tempParent;
        if (DEBUG) {
          System.out.println("\tCurrent ancestorsParent has type" + ancestorsParent.getClass());
        }

        // ancestor should be last child of ancestorsParent
        if (!checkChildLastInParent(ancestor, ancestorsParent)) {
          if (DEBUG) {
            System.out.println("\t\tCurrent ancestorParent has more children after this ancestor");
          }

          // return from the method since this is the last stmt and we cant do anything
          return;
        }

        // ancestorsParent should not be a loop of any kind OR A SWITCH
        if (ancestorsParent instanceof ASTWhileNode || ancestorsParent instanceof ASTDoWhileNode
            || ancestorsParent instanceof ASTUnconditionalLoopNode || ancestorsParent instanceof ASTForLoopNode
            || ancestorsParent instanceof ASTSwitchNode) {
          if (DEBUG) {
            System.out.println("\t\tAncestorsParent is a loop shouldnt remove abrupt stmt");
          }
          return;
        }
        ancestor = ancestorsParent;
      }

      if (DEBUG) {
        System.out.println("\tGot to target without returning means we can remove stmt");
      }

      remove = as;
    } // end of while going through the statement sequence

    if (remove != null) {
      List<AugmentedStmt> stmts = node.getStatements();
      stmts.remove(remove);
      if (DEBUG) {
        System.out.println("\tRemoved abrupt stmt");
      }

      if (target != null) {
        if (DEBUG) {
          System.out.println("Invoking findAndKill on the target");
        }
        UselessLabelFinder.v().findAndKill(target);
      }
      // TODO what if we just emptied a stmt seq block??
      // not doing this for the moment

      // set modified flag make finder null
      G.v().ASTTransformations_modified = true;
      finder = null;
    }
  }

  public boolean checkChildLastInParent(ASTNode child, ASTNode parent) {
    List<Object> subBodies = parent.get_SubBodies();
    Iterator<Object> it = subBodies.iterator();

    while (it.hasNext()) {
      List subBody = null;
      if (parent instanceof ASTTryNode) {
        subBody = (List) ((ASTTryNode.container) it.next()).o;
      } else {
        subBody = (List) it.next();
      }

      if (subBody.contains(child)) {
        if (subBody.indexOf(child) != subBody.size() - 1) {
          return false;
        } else {
          return true;
        }
      }
    }

    return false;
  }
}
