/*
 * ExpressionSubstitution.java - This file is part of the Jakstab project.
 * Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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 Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
 */
package org.jakstab.transformation;

import java.util.HashSet;
import java.util.Set;

import org.jakstab.analysis.*;
import org.jakstab.analysis.substitution.ExpressionSubstitutionAnalysis;
import org.jakstab.analysis.substitution.SubstitutionElement;
import org.jakstab.analysis.substitution.SubstitutionState;
import org.jakstab.cfa.CFAEdge;
import org.jakstab.cfa.ControlFlowGraph;
import org.jakstab.rtl.Context;
import org.jakstab.rtl.expressions.RTLVariable;
import org.jakstab.rtl.statements.RTLSkip;
import org.jakstab.rtl.statements.RTLStatement;
import org.jakstab.util.Logger;

/**
 * @author Johannes Kinder
 */
public class ExpressionSubstitution implements CFATransformation {

	private static final Logger logger = Logger
			.getLogger(ExpressionSubstitution.class);
	
	private CPAAlgorithm cpaAlgo;
	private Set<CFAEdge> edgeSet;
	
	public static void substituteCFAEdge(CFAEdge edge, SubstitutionState s) {
		RTLStatement stmt = (RTLStatement)edge.getTransformer();
		RTLStatement newStmt = substituteStatement(stmt, s);
		if (newStmt != stmt)
			edge.setTransformer(newStmt);
	}
	
	public static RTLStatement substituteStatement(RTLStatement stmt, SubstitutionState s) {
		Context substCtx = new Context();
		for (RTLVariable v : stmt.getUsedVariables()) {
			SubstitutionElement el = s.getValue(v);
			if (!el.isTop()) {
				substCtx.addAssignment(v, el.getExpression());
			}
		}
		if (!substCtx.getAssignments().isEmpty()) {
			//logger.info("Old stmt: " + stmt);
			RTLStatement newStmt = stmt.copy().evaluate(substCtx);
			//logger.info("New stmt: " + newStmt);
			if (newStmt != null) {
				return newStmt.evaluate(new Context());
			} else {
				RTLSkip skip = new RTLSkip();
				skip.setLabel(stmt.getLabel());
				skip.setNextLabel(stmt.getNextLabel());
				return skip;
			}
		}
		return stmt;
	}
	
	public ExpressionSubstitution(ControlFlowGraph cfg) {
		cpaAlgo = CPAAlgorithm.createForwardAlgorithm(cfg, new ExpressionSubstitutionAnalysis());
		edgeSet = new HashSet<CFAEdge>(cfg.getEdges());
	}
	
	public Set<CFAEdge> getCFA() {
		return edgeSet;
	}


	@Override
	public void run() {
		
		logger.info("Starting expression substitution.");
		long startTime = System.currentTimeMillis();
		
		cpaAlgo.run();
		ReachedSet exprSubstStates = cpaAlgo.getReachedStates().select(1);
		
		for (CFAEdge edge : edgeSet) {
			assert exprSubstStates.where(edge.getSource()).size() == 1;
			SubstitutionState s = (SubstitutionState)exprSubstStates.where(edge.getSource()).iterator().next();
			substituteCFAEdge(edge, s);
		}
		
		long endTime = System.currentTimeMillis();
		logger.verbose("Finished after " + (endTime - startTime) + "ms.");
	}
	
	public void stop() {
		cpaAlgo.stop();
	}
}
