package soot.poc.reachingdef;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import soot.Local;
import soot.Unit;
import soot.Value;
import soot.ValueBox;
import soot.jimple.FieldRef;
import soot.poc.inter.Definition;
import soot.toolkits.graph.DirectedGraph;
import soot.toolkits.scalar.ArraySparseSet;
import soot.toolkits.scalar.FlowSet;
import soot.toolkits.scalar.ForwardFlowAnalysis;

public class SimpleReachingDefinitions implements ReachingDefinitions
{
	private HashMap<Unit,List<Definition>> unitToDefinitionAfter;
	private HashMap<Unit,List<Definition>> unitToDefinitionBefore;
	
	public SimpleReachingDefinitions(DirectedGraph<Unit> graph)
	{
		SimpleReachingDefinitionsAnalysis analysis = new SimpleReachingDefinitionsAnalysis(graph);

		this.unitToDefinitionAfter = new HashMap<Unit,List<Definition>>(graph.size() * 2 + 1, 0.7f);
		this.unitToDefinitionBefore = new HashMap<Unit,List<Definition>>(graph.size() * 2 + 1, 0.7f);

		for (Unit unit: graph)
		{
			FlowSet set = (FlowSet) analysis.getFlowBefore(unit);
			this.unitToDefinitionBefore.put(unit,
					Collections.unmodifiableList(set.toList()));

			set = (FlowSet) analysis.getFlowAfter(unit);
			this.unitToDefinitionAfter.put(unit,
					Collections.unmodifiableList(set.toList()));
		}
	}

	public List<Definition> getReachingDefinitionsAfter(Unit _unit) {
		return this.unitToDefinitionAfter.get(_unit);
	}

	public List<Definition> getReachingDefinitionsBefore(Unit _unit) {
		return this.unitToDefinitionBefore.get(_unit);
	}	

}

class SimpleReachingDefinitionsAnalysis extends ForwardFlowAnalysis<Unit, FlowSet> {

	private FlowSet emptySet;
	
	public SimpleReachingDefinitionsAnalysis(DirectedGraph<Unit> _graph) {
		super(_graph);
		
		this.emptySet = new ArraySparseSet();
		
		super.doAnalysis();
	}

	// ForwardFlowAnalysis implementation --------------------------
	
	@Override
	protected void copy(FlowSet _source, FlowSet _dest) {
//		System.out.println("[copy] source set -> " + _source);
		_source.copy(_dest);
//		System.out.println("[copy] dest set -> " + _dest);
	}

	@Override
	protected void merge(FlowSet _source1, FlowSet _source2, FlowSet _dest) {
//		System.out.println("[merge] ------------------------------");
//		System.out.println("[merge] source set1 -> " + _source1);
//		System.out.println("[merge] source set2 -> " + _source2);
		_source1.union(_source2, _dest);
//		System.out.println("[merge] dest set -> " + _dest);
//		System.out.println("[merge] ------------------------------");
	}

	@Override
	protected FlowSet entryInitialFlow() {
		return this.emptySet.clone();
	}
	
	@Override
	protected FlowSet newInitialFlow() {
		return this.emptySet.clone();
	}

	@Override
	protected void flowThrough(FlowSet _source, Unit _unit, FlowSet _dest)
	{
//		System.out.println("[flowThrough] ------------------------------");
//		System.out.println("[flowThrough] unit -> " + _unit.toString());
//		System.out.println("[flowThrough] in -> " + _source.toString());
//		System.out.println("[flowThrough] out -> " + _dest.toString());
		kill(_source, _unit, _dest);
		gen(_dest, _unit);
//		System.out.println("[flowThrough] in -> " + _source.toString());
//		System.out.println("[flowThrough] out -> " + _dest.toString());
//		System.out.println("[flowThrough] ------------------------------");
	}
	
	private void kill(FlowSet _source, Unit _unit, FlowSet _dest)
	{
		FlowSet kills = this.emptySet.clone();
		List<Definition> sourceList = _source.toList(); 

		for (ValueBox valueBox: _unit.getDefBoxes())
		{
			Value value = valueBox.getValue();

//			if (value instanceof Local)
			if (value instanceof FieldRef)
			{
				for (Definition definition: sourceList)
				{
					if (definition.defines(((FieldRef)value).getField()))
						kills.add(definition);
				}
			}
		}
		
		_source.difference(kills,_dest);
	}
	
	private void gen(FlowSet _dest, Unit _unit)
	{
		for (ValueBox valueBox: _unit.getDefBoxes())
		{
			Value value = valueBox.getValue();

//			if (value instanceof Local)
			if (value instanceof FieldRef)
			{
				_dest.add(new Definition(((FieldRef)value).getField(),_unit));
			}
		}
	}

}
