/*
 * ProcedureAnalysis.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.analysis.procedures;

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

import org.jakstab.AnalysisProperties;
import org.jakstab.analysis.*;
import org.jakstab.cfa.CFAEdge;
import org.jakstab.cfa.Location;
import org.jakstab.cfa.StateTransformer;
import org.jakstab.rtl.statements.DefaultStatementVisitor;
import org.jakstab.rtl.statements.RTLAssume;
import org.jakstab.rtl.statements.RTLCallReturn;
import org.jakstab.rtl.statements.RTLStatement;
import org.jakstab.util.FastSet;
import org.jakstab.util.Logger;
import org.jakstab.util.Pair;

/**
 * @author Johannes Kinder
 */
public class ProcedureAnalysis implements ConfigurableProgramAnalysis {

	public static void register(AnalysisProperties p) {
		p.setName("Procedure detection");
		p.setDescription("Detect procedures using call-return matching.");
		p.setShortHand('o');
	}

	@SuppressWarnings("unused")
	private static final Logger logger = Logger.getLogger(ProcedureAnalysis.class);
	
	private final Set<Pair<Location, Location>> callSites;
	private final Set<Location> callees;

	public ProcedureAnalysis() {
		callSites = new HashSet<Pair<Location, Location>>();
		callees = new HashSet<Location>();
	}

	@Override
	public Precision initPrecision(Location location, StateTransformer transformer) {
		return null;
	}

	@Override
	public AbstractState initStartState(Location location) {
		return new ProcedureState(new FastSet<Location>(location));
	}

	@Override
	public AbstractState merge(AbstractState s1, AbstractState s2,
			Precision precision) {
		return CPAOperators.mergeJoin(s1, s2, precision);
	}

	@Override
	public Set<AbstractState> post(final AbstractState state, final CFAEdge edge,
			Precision precision) {
		
		final RTLStatement stmt = (RTLStatement)edge.getTransformer();
		
		return stmt.accept(new DefaultStatementVisitor<Set<AbstractState>>() {

			@Override
			public Set<AbstractState> visit(RTLAssume assume) {
				if (assume.getSource() != null) {
					AbstractState post;
					switch (assume.getSource().getType()) {
					case CALL:
						callSites.add(Pair.create(edge.getSource(), edge.getTarget()));
						callees.add(edge.getTarget());
						post = new ProcedureState(new FastSet<Location>(edge.getTarget()));
						return Collections.singleton(post);
					case RETURN:
						//post = new ProcedureState(new FastSet<Location>(((Location)edge.getTarget())));
						// FIXME: We would need to have a callstack to determine the procedure we are returning to
						// So currently this works only with OptimisticTransformerFactory
						// post = getProcedureState((getCallStackElement().getLabel()))
						// Returning empty set means that the target is unreachable (because the analysis has to be
						// over-approximate), which isn't true.
						//return Collections.emptySet();
						// 
						// return BOT procedure state so it gets merged with fall-through of call 
						post = new ProcedureState(new FastSet<Location>());
						return Collections.singleton(post);
					default: 
						// nothing
					}
				}
				return Collections.singleton(state);
			}

			@Override
			public Set<AbstractState> visit(RTLCallReturn stmt) {
				// Keep current procedure
				return Collections.singleton(state);
			}

			@Override
			protected Set<AbstractState> visitDefault(RTLStatement stmt) {
				// Else just keep the current procedure, includes UnknownProcedureCall edges!
				return Collections.singleton(state);
			}
			
		});
	}

	@Override
	public AbstractState strengthen(AbstractState s, Iterable<AbstractState> otherStates,
			CFAEdge cfaEdge, Precision precision) {
		return s;
	}

	@Override
	public Pair<AbstractState, Precision> prec(AbstractState s,
			Precision precision, ReachedSet reached) {
		return Pair.create(s, precision);
	}
	
	public Set<Location> getCallees() {
		return callees;
	}
	
	public Set<Pair<Location,Location>> getCallSites() {
		return callSites;
	}
	
	public String toString() {
		return getCallees().toString();
	}

	@Override
	public boolean stop(AbstractState s, ReachedSet reached, Precision precision) {
		return CPAOperators.stopSep(s, reached, precision);
	}
}
