package com.ibm.wala.cast.tree.rewrite;

import java.util.Map;

import com.ibm.wala.cast.tree.CAst;
import com.ibm.wala.cast.tree.CAstControlFlowMap;
import com.ibm.wala.cast.tree.CAstEntity;
import com.ibm.wala.cast.tree.CAstNode;
import com.ibm.wala.cast.tree.CAstSourcePositionMap;
import com.ibm.wala.cast.tree.impl.CAstOperator;
import com.ibm.wala.util.collections.Pair;

public class AstLoopUnwinder
  extends CAstRewriter<CAstRewriter.RewriteContext<AstLoopUnwinder.UnwindKey>,AstLoopUnwinder.UnwindKey>
{

  public static class UnwindKey implements CAstRewriter.CopyKey<UnwindKey> {
    private int iteration;
    private UnwindKey rest;
			  
    private UnwindKey(int iteration, UnwindKey rest) {
      this.rest = rest;
      this.iteration = iteration;
    }
			  
    public int hashCode() {
      return iteration * (rest == null? 1: rest.hashCode());
    }
		
    public UnwindKey parent() {
      return rest;
    }

    public boolean equals(Object o) {
      return (o instanceof UnwindKey) &&
	((UnwindKey)o).iteration == iteration &&
	( rest==null? ((UnwindKey)o).rest == null: rest.equals(((UnwindKey)o).rest) );
    }

    public String toString() {
      return "#"+iteration+ ((rest==null)?"":rest.toString());
    }
  }

  // private static final boolean DEBUG = false;

  private final int unwindFactor;
	
  public AstLoopUnwinder(CAst Ast, boolean recursive) {
    this(Ast, recursive, 3);
  }
	  
  public AstLoopUnwinder(CAst Ast, boolean recursive, int unwindFactor) {
    super(Ast, recursive, new RootContext());
    this.unwindFactor = unwindFactor;
  }
	  
  public CAstEntity translate(CAstEntity original) {
    return rewrite(original);
  }

  private static class RootContext implements RewriteContext<UnwindKey> {
		  
    public UnwindKey key() {
      return null;
    }
  }
	  	  
  private class LoopContext implements RewriteContext<UnwindKey> {
    private final CAstRewriter.RewriteContext<UnwindKey> parent;
    private final int iteration;
        
    private LoopContext(int iteration, RewriteContext<UnwindKey> parent) {
      this.iteration = iteration;
      this.parent = parent;
    }
        
    public UnwindKey key() {
      return new UnwindKey(iteration, parent.key());
    }
      
  }
	  
  protected CAstNode flowOutTo(Map nodeMap, 
			       CAstNode oldSource,
			       Object label,
			       CAstNode oldTarget,
			       CAstControlFlowMap orig, 
			       CAstSourcePositionMap src) 
  {
    assert oldTarget == CAstControlFlowMap.EXCEPTION_TO_EXIT;
    return oldTarget;
  }

  protected CAstNode copyNodes(CAstNode n, final CAstControlFlowMap cfg, RewriteContext<UnwindKey> c, Map<Pair<CAstNode,UnwindKey>,CAstNode> nodeMap) {
    if (n instanceof CAstOperator) {
      return n;
    } else if (n.getValue() != null) {
      return Ast.makeConstant( n.getValue() );
    } else if (n.getKind() == CAstNode.LOOP) {
      CAstNode test = n.getChild(0);
      CAstNode body = n.getChild(1);
      
      int count = unwindFactor;
      RewriteContext<UnwindKey> lc = new LoopContext(count, c);
      CAstNode code = 
        Ast.makeNode(CAstNode.ASSERT,
	  Ast.makeNode(CAstNode.UNARY_EXPR,
	    CAstOperator.OP_NOT,
            copyNodes(test, cfg, lc, nodeMap)),
          Ast.makeConstant(false));
      while (count-- > 0) {
	lc = new LoopContext(count, c);
	code = Ast.makeNode(CAstNode.IF_STMT, 
			    copyNodes(test, cfg, lc, nodeMap),
			    Ast.makeNode(CAstNode.BLOCK_STMT,
					 copyNodes(body, cfg, lc, nodeMap),
					 code));
      }
			  
      return code;
    } else {
	CAstNode[] newChildren = new CAstNode[ n.getChildCount() ];
	for(int i = 0; i < newChildren.length; i++) 
	  newChildren[i] = copyNodes(n.getChild(i), cfg, c, nodeMap);
			  
	CAstNode newN = Ast.makeNode(n.getKind(), newChildren);
			  			
	nodeMap.put(Pair.make(n, c.key()), newN);
	
	return newN; 
    }
  }	  
}
