/*
 * AsmCFG.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.cfa;

import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.jakstab.Program;
import org.jakstab.asm.BranchInstruction;
import org.jakstab.asm.Instruction;
import org.jakstab.rtl.statements.RTLAssume;
import org.jakstab.rtl.statements.RTLGoto;
import org.jakstab.rtl.statements.RTLStatement;
import org.jakstab.util.Pair;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;

public class AsmCFG {
	
	private Map<Location, Instruction> nodes;
	private SetMultimap<Location, Pair<Location, Object>> edges;
	
	public AsmCFG(ControlFlowGraph cfg) {
		
		/*
		 * When constructing an ASM CFG from a CFA, we have the following problem:
		 * The VPC can change within an instruction, so if we just skip intra-instruction
		 * edges, the VPC value for CFA edge entering the instruction can be different from
		 * the one for the CFA edge leaving the instruction. In that case, the instruction 
		 * label is different, and the edges don't connect.
		 * 
		 * Therefore, we define the VPC of an instruction location to be the VPC at the 
		 * instruction entry.
		 * 
		 * We can't just iterate over all VPC locations and check whether an VPC, address pair
		 * already has an instruction, because this might put an instruction twice if the VPC
		 * changes in the middle of it.  
		 */
		
		nodes = new HashMap<Location, Instruction>();
		edges = HashMultimap.create();
		
		Program p = Program.getProgram();
		
		Deque<Location> worklist = new LinkedList<Location>();
		worklist.add(cfg.getEntryPoint());
		
		while (!worklist.isEmpty()) {
			Location instrEntry = worklist.pop();
			if (nodes.containsKey(instrEntry))
				continue;
			// the instruction might be null (if it's a stub/prologue location)
			Instruction instr = p.getInstruction(instrEntry.getAddress());
			nodes.put(instrEntry, instr);
			
			Set<CFAEdge> outEdges;
			Location curLoc = instrEntry;
			do {
				outEdges = cfg.getOutEdges(curLoc);
				if (outEdges.size() != 1)
					break;
				curLoc = outEdges.iterator().next().getTarget();				
			} while (curLoc.getAddress().equals(instrEntry.getAddress()) && 
					curLoc.getLabel().getIndex() >= instrEntry.getLabel().getIndex());
			
			for (CFAEdge e : outEdges) {
				RTLStatement stmt = (RTLStatement)e.getTransformer();
				String label = "";
				
				if (instr instanceof BranchInstruction) {
					assert stmt instanceof RTLAssume;
					BranchInstruction bi = (BranchInstruction)instr;
					if (bi.isConditional()) {
						// Get the original goto from the program (not the converted assume) 
						RTLGoto rtlGoto = ((RTLAssume) stmt).getSource();

						// If this is the fall-through edge, output F, otherwise T
						// If the assume in the edge has the same nextlabel as Goto, then it's the fall-through
						label = stmt.getNextLabel().equals(rtlGoto.getNextLabel()) ? "F" : "T";
					}
				}

				edges.put(instrEntry, Pair.create(e.getTarget(), (Object)label));
				worklist.add(e.getTarget());
			}
				
		}

	}
	
	public Instruction getInstruction(Location l) {
		return nodes.get(l);
	}
	
	public void setInstruction(Location l, Instruction i) {
		nodes.put(l, i);
	}
	
	public Map<Location, Instruction> getNodes() {
		return nodes;
	}
	
	public SetMultimap<Location, Pair<Location, Object>> getEdges() {
		return edges;
	}

	public Set<Pair<Location, Object>> getOutEdges(Location l) {
		return edges.get(l);
	}

}
