/**
 * Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 *
 * This file is part of EvoSuite.
 *
 * EvoSuite is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3.0 of the License, or
 * (at your option) any later version.
 *
 * EvoSuite 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
 * Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
 */
package org.evosuite.coverage.branch;

import org.evosuite.graphs.cfg.BytecodeInstruction;
import org.evosuite.setup.DependencyAnalysis;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

// TODO: root branches should not be special cases
// every root branch should be a branch just
// like every other branch with it's own branchId and all

/**
 * This class is supposed to hold all the available information concerning
 * Branches.
 * 
 * The addBranch()-Method gets called during class analysis. Whenever the
 * BytecodeInstructionPool detects a BytecodeInstruction that corresponds to a
 * Branch in the class under test as defined in
 * BytecodeInstruction.isActualBranch() it calls the registerAsBranch() method
 * of this class which in turn properly registers the instruction within this
 * pool.
 * 
 * There are two kinds of Branch objects: normal branches and switch case
 * branches. For more details about the difference between these two look at the
 * Branch class.
 * 
 * @author Andre Mis
 */
public class BranchPool {

	private static Logger logger = LoggerFactory.getLogger(BranchPool.class);

	// maps className -> method inside that class -> list of branches inside
	// that method
	private Map<String, Map<String, List<Branch>>> branchMap = new HashMap<String, Map<String, List<Branch>>>();

	// set of all known methods without a Branch
	private Map<String, Map<String, Integer>> branchlessMethods = new HashMap<String, Map<String, Integer>>();

	// maps the branchIDs assigned by this pool to their respective Branches
	private Map<Integer, Branch> branchIdMap = new HashMap<Integer, Branch>();

	// maps all known branch instructions to their branchId
	private Map<BytecodeInstruction, Integer> registeredNormalBranches = new HashMap<BytecodeInstruction, Integer>();

	// maps all known switch instructions to a list containing all of their
	// associated Branch objects
	private Map<BytecodeInstruction, List<Branch>> registeredSwitches = new HashMap<BytecodeInstruction, List<Branch>>();

	private Map<BytecodeInstruction, Branch> registeredDefaultCases = new HashMap<BytecodeInstruction, Branch>();

	private Map<LabelNode, List<Branch>> switchLabels = new HashMap<LabelNode, List<Branch>>();

	// number of known Branches - used for actualBranchIds
	private int branchCounter = 0;

	private static Map<ClassLoader, BranchPool> instanceMap = new HashMap<ClassLoader, BranchPool>();

	public static BranchPool getInstance(ClassLoader classLoader) {
		if (!instanceMap.containsKey(classLoader)) {
			instanceMap.put(classLoader, new BranchPool());
		}

		return instanceMap.get(classLoader);
	}
	// fill the pool

	/**
	 * Gets called by the CFGMethodAdapter whenever it detects a method without
	 * any branches.
	 * 
	 * @param methodName
	 *            Unique methodName - consisting of <className>.<methodName> -
	 *            of a method without Branches
	 * @param className
	 *            a {@link java.lang.String} object.
	 */
	public void addBranchlessMethod(String className, String methodName,
	        int lineNumber) {
		if (!branchlessMethods.containsKey(className))
			branchlessMethods.put(className, new HashMap<String, Integer>());
		branchlessMethods.get(className).put(methodName, lineNumber);
	}

	/**
	 * Called by the BytecodeInstructionPool whenever it detects an instruction
	 * that corresponds to a Branch in the class under test as defined by
	 * BytecodeInstruction.isActualBranch().
	 * 
	 * @param instruction
	 *            a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 */
	public void registerAsBranch(BytecodeInstruction instruction) {
		if (!(instruction.isActualBranch()))
			throw new IllegalArgumentException("CFGVertex of a branch expected");
		if (isKnownAsBranch(instruction))
			return;
		if (!DependencyAnalysis.shouldInstrument(instruction.getClassName(),
		                                         instruction.getMethodName())) {
			return;
		} 
		
		registerInstruction(instruction);

	}

	private void registerInstruction(BytecodeInstruction v) {
		if (isKnownAsBranch(v))
			throw new IllegalStateException(
			        "expect registerInstruction() to be called at most once for each instruction");

		if (v.isBranch())
			registerNormalBranchInstruction(v);
		else if (v.isSwitch())
			registerSwitchInstruction(v);
		else
			throw new IllegalArgumentException(
			        "expect given instruction to be an actual branch");
	}

	private void registerNormalBranchInstruction(BytecodeInstruction v) {
		if (!v.isBranch())
			throw new IllegalArgumentException("normal branch instruction expceted");

		if (registeredNormalBranches.containsKey(v))
			throw new IllegalArgumentException(
			        "instruction already registered as a normal branch");

		branchCounter++;
		registeredNormalBranches.put(v, branchCounter);

		Branch b = new Branch(v, branchCounter);
		addBranchToMap(b);
		branchIdMap.put(branchCounter, b);

		logger.info("Branch " + branchCounter + " at line " + v.getLineNumber());
	}

	private void registerSwitchInstruction(BytecodeInstruction v) {
		if (!v.isSwitch())
			throw new IllegalArgumentException("expect a switch instruction");

		LabelNode defaultLabel = null;

		switch (v.getASMNode().getOpcode()) {
		case Opcodes.TABLESWITCH:
			TableSwitchInsnNode tableSwitchNode = (TableSwitchInsnNode) v.getASMNode();
			registerTableSwitchCases(v, tableSwitchNode);
			defaultLabel = tableSwitchNode.dflt;

			break;
		case Opcodes.LOOKUPSWITCH:
			LookupSwitchInsnNode lookupSwitchNode = (LookupSwitchInsnNode) v.getASMNode();
			registerLookupSwitchCases(v, lookupSwitchNode);
			defaultLabel = lookupSwitchNode.dflt;
			break;
		default:
			throw new IllegalStateException(
			        "expect ASMNode of a switch to either be a LOOKUP- or TABLESWITCH");
		}

		registerDefaultCase(v, defaultLabel);
	}

	private void registerDefaultCase(BytecodeInstruction v, LabelNode defaultLabel) {

		if (defaultLabel == null)
			throw new IllegalStateException("expect variable to bet set");

		Branch defaultBranch = createSwitchCaseBranch(v, null, defaultLabel);
		if (!defaultBranch.isSwitchCaseBranch() || !defaultBranch.isDefaultCase())
			throw new IllegalStateException(
			        "expect created branch to be a default case branch of a switch");
	}

	private void registerTableSwitchCases(BytecodeInstruction v,
	        TableSwitchInsnNode tableSwitchNode) {

		int num = 0;

		for (int i = tableSwitchNode.min; i <= tableSwitchNode.max; i++) {
			LabelNode targetLabel = (LabelNode) tableSwitchNode.labels.get(num);
			Branch switchBranch = createSwitchCaseBranch(v, i, targetLabel);
			if (!switchBranch.isSwitchCaseBranch() || !switchBranch.isActualCase())
				throw new IllegalStateException(
				        "expect created branch to be an actual case branch of a switch");
			num++;
		}
	}

	private void registerLookupSwitchCases(BytecodeInstruction v,
	        LookupSwitchInsnNode lookupSwitchNode) {

		for (int i = 0; i < lookupSwitchNode.keys.size(); i++) {
			LabelNode targetLabel = (LabelNode) lookupSwitchNode.labels.get(i);
			Branch switchBranch = createSwitchCaseBranch(v,
			                                             (Integer) lookupSwitchNode.keys.get(i),
			                                             targetLabel);
			if (!switchBranch.isSwitchCaseBranch() || !switchBranch.isActualCase())
				throw new IllegalStateException(
				        "expect created branch to be an actual case branch of a switch");
		}
	}

	private Branch createSwitchCaseBranch(BytecodeInstruction v,
	        Integer caseValue, LabelNode targetLabel) {

		branchCounter++;

		Branch switchBranch = new Branch(v, caseValue, targetLabel, branchCounter);
		registerSwitchBranch(v, switchBranch);
		addBranchToMap(switchBranch);
		branchIdMap.put(branchCounter, switchBranch);

		registerSwitchLabel(switchBranch, targetLabel);

		// default case
		if (caseValue == null) {
			if (registeredDefaultCases.containsKey(v))
				throw new IllegalStateException(
				        "instruction already registered as a branch");
			registeredDefaultCases.put(v, switchBranch);
		}

		if (!switchBranch.isSwitchCaseBranch())
			throw new IllegalStateException("expect created Branch to be a switch branch");

		return switchBranch;
	}

	private void registerSwitchLabel(Branch b, LabelNode targetLabel) {

		if (switchLabels.get(targetLabel) == null)
			switchLabels.put(targetLabel, new ArrayList<Branch>());

		List<Branch> oldList = switchLabels.get(targetLabel);

		if (oldList.contains(b))
			throw new IllegalStateException(
			        "branch already registered for this switch label");

		oldList.add(b);

		// TODO several Branches can map to one Label, so switchLabels should
		// either map from branches to labels, not the other way around. or it
		// should map labels to a list of branches
		// this stems from the fact that empty case: blocks do not have their
		// own label

		// TODO STOPPED HERE

		switchLabels.put(targetLabel, oldList);
	}

	private void registerSwitchBranch(BytecodeInstruction v, Branch switchBranch) {
		if (!v.isSwitch())
			throw new IllegalArgumentException("switch instruction expected");

		if (registeredSwitches.get(v) == null)
			registeredSwitches.put(v, new ArrayList<Branch>());

		List<Branch> oldList = registeredSwitches.get(v);

		if (oldList.contains(switchBranch))
			throw new IllegalArgumentException("switch branch already registered  "
			        + switchBranch.toString());

		oldList.add(switchBranch);

		registeredSwitches.put(v, oldList);
	}

	private void addBranchToMap(Branch b) {

		logger.info("Adding to map the branch {}", b);

		String className = b.getClassName();
		String methodName = b.getMethodName();

		if (!branchMap.containsKey(className))
			branchMap.put(className, new HashMap<String, List<Branch>>());
		if (!branchMap.get(className).containsKey(methodName))
			branchMap.get(className).put(methodName, new ArrayList<Branch>());
		branchMap.get(className).get(methodName).add(b);
	}

	// retrieve information from the pool

	/**
	 * Checks whether the given instruction has Branch objects associated with
	 * it.
	 * 
	 * Returns true if the given BytecodeInstruction previously passed a call to
	 * registerAsBranch(instruction), false otherwise
	 * 
	 * @param instruction
	 *            a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 * @return a boolean.
	 */
	public boolean isKnownAsBranch(BytecodeInstruction instruction) {
		return isKnownAsNormalBranchInstruction(instruction)
		        || isKnownAsSwitchBranchInstruction(instruction);
	}

	/**
	 * <p>
	 * isKnownAsNormalBranchInstruction
	 * </p>
	 * 
	 * @param ins
	 *            a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 * @return a boolean.
	 */
	public boolean isKnownAsNormalBranchInstruction(BytecodeInstruction ins) {

		return registeredNormalBranches.containsKey(ins);
	}

	/**
	 * <p>
	 * isKnownAsSwitchBranchInstruction
	 * </p>
	 * 
	 * @param instruction
	 *            a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 * @return a boolean.
	 */
	public boolean isKnownAsSwitchBranchInstruction(BytecodeInstruction instruction) {

		return registeredSwitches.containsKey(instruction);
	}

	/**
	 * <p>
	 * getActualBranchIdForNormalBranchInstruction
	 * </p>
	 * 
	 * @param ins
	 *            a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 * @return a int.
	 */
	public int getActualBranchIdForNormalBranchInstruction(BytecodeInstruction ins) {
		if (!isKnownAsNormalBranchInstruction(ins))
			throw new IllegalArgumentException(
			        "instruction not registered as a normal branch");

		if (registeredNormalBranches.containsKey(ins))
			return registeredNormalBranches.get(ins);

		throw new IllegalStateException(
		        "expect registeredNormalBranches to contain a key for each known normal branch instruction");
	}

	/**
	 * <p>
	 * getCaseBranchesForSwitch
	 * </p>
	 * 
	 * @param instruction
	 *            a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 * @return a {@link java.util.List} object.
	 */
	public List<Branch> getCaseBranchesForSwitch(BytecodeInstruction instruction) {
		if (instruction == null)
			throw new IllegalArgumentException("null given");
		if (!instruction.isSwitch())
			throw new IllegalArgumentException("switch instruction expected");
		if (!isKnownAsSwitchBranchInstruction(instruction))
			throw new IllegalArgumentException("not registered as a switch instruction");

		return registeredSwitches.get(instruction);
	}

	/**
	 * <p>
	 * getBranchForInstruction
	 * </p>
	 * 
	 * @param instruction
	 *            a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 * @return a {@link org.evosuite.coverage.branch.Branch} object.
	 */
	public Branch getBranchForInstruction(BytecodeInstruction instruction) {
		if (instruction == null)
			throw new IllegalArgumentException("null given");
		if (!isKnownAsNormalBranchInstruction(instruction))
			throw new IllegalArgumentException(
			        "expect given instruction to be known as a normal branch");

		return getBranch(registeredNormalBranches.get(instruction));
	}

	/**
	 * <p>
	 * getBranchForLabel
	 * </p>
	 * 
	 * @param label
	 *            a {@link org.objectweb.asm.tree.LabelNode} object.
	 * @return a {@link java.util.List} object.
	 */
	public List<Branch> getBranchForLabel(LabelNode label) {

		// TODO see registerSwitchLabel()!

		return switchLabels.get(label);
	}

	/**
	 * Returns the number of known Branches for a given methodName in a given
	 * class.
	 * 
	 * @return The number of currently known Branches inside the given method
	 * @param className
	 *            a {@link java.lang.String} object.
	 * @param methodName
	 *            a {@link java.lang.String} object.
	 */
	public int getBranchCountForMethod(String className, String methodName) {
		if (branchMap.get(className) == null)
			return 0;
		if (branchMap.get(className).get(methodName) == null)
			return 0;

		return branchMap.get(className).get(methodName).size();
	}

	public int getNonArtificialBranchCountForMethod(String className,
	        String methodName) {
		if (branchMap.get(className) == null)
			return 0;
		if (branchMap.get(className).get(methodName) == null)
			return 0;

		int num = 0;
		for (Branch b : branchMap.get(className).get(methodName)) {
			if (!b.isInstrumented())
				num++;
		}

		return num;
	}

	/**
	 * Returns the number of known Branches for a given class
	 * 
	 * @return The number of currently known Branches inside the given class
	 * @param className
	 *            a {@link java.lang.String} object.
	 */
	public int getBranchCountForClass(String className) {
		if (branchMap.get(className) == null)
			return 0;
		int total = 0;
		for (String method : branchMap.get(className).keySet()) {
			total += branchMap.get(className).get(method).size();
		}
		return total;
	}

	/**
	 * Returns the number of known Branches for a given class
	 * 
	 * @return The number of currently known Branches inside the given class
	 * @param prefix
	 *            a {@link java.lang.String} object.
	 */
	public int getBranchCountForPrefix(String prefix) {
		int num = 0;
		for (String className : branchMap.keySet()) {
			if (className.startsWith(prefix)) {
				logger.info("Found matching class for branch count: " + className + "/"
				        + prefix);
				for (String method : branchMap.get(className).keySet()) {
					num += branchMap.get(className).get(method).size();
				}
			}
		}
		return num;
	}
	
	/**
	 * Returns the number of known Branches for a given class
	 * 
	 * @return The number of currently known Branches inside the given class
	 * @param prefix
	 *            a {@link java.lang.String} object.
	 */
	public Set<Integer> getBranchIdsForPrefix(String prefix) {
		Set<Integer> ids = new HashSet<>();
		Set<Branch> sutBranches = new HashSet<>();
		for (String className : branchMap.keySet()) {
			if (className.startsWith(prefix)) {
				logger.info("Found matching class for branch ids: " + className + "/"
				        + prefix);
				for (String method : branchMap.get(className).keySet()) {
					sutBranches.addAll(branchMap.get(className).get(method));
				}
			}
		}
		
		for (Integer id : branchIdMap.keySet()) {
			if(sutBranches.contains(branchIdMap.get(id))){
				ids.add(id);
			}
		}
		
		return ids;
	} 

	/**
	 * Returns the number of known Branches for a given class
	 * 
	 * @return The number of currently known Branches inside the given class
	 * @param prefix
	 *            a {@link java.lang.String} object.
	 */
	public int getBranchCountForMemberClasses(String prefix) {
		int num = 0;
		for (String className : branchMap.keySet()) {
			if (className.equals(prefix) || className.startsWith(prefix + "$")) {
				logger.info("Found matching class for branch count: " + className + "/"
				        + prefix);
				for (String method : branchMap.get(className).keySet()) {
					num += branchMap.get(className).get(method).size();
				}
			}
		}
		return num;
	}

	/**
	 * Returns the number of currently known Branches
	 * 
	 * @return The number of currently known Branches
	 */
	public int getBranchCounter() {
		return branchCounter;
	}

	public int getNumArtificialBranches() {
		int num = 0;
		for (Branch b : branchIdMap.values()) {
			if (b.isInstrumented())
				num++;
		}

		return num;
	}

	/**
	 * Returns the Branch object associated with the given branchID
	 * 
	 * @param branchId
	 *            The ID of a branch
	 * @return The branch, or null if it does not exist
	 */
	public Branch getBranch(int branchId) {

		return branchIdMap.get(branchId);
	}
	
	public Collection<Branch> getAllBranches() {
		return branchIdMap.values();
	}

	/**
	 * Returns a set with all unique methodNames of methods without Branches.
	 * 
	 * @return A set with all unique methodNames of methods without Branches.
	 * @param className
	 *            a {@link java.lang.String} object.
	 */
	public Set<String> getBranchlessMethods(String className) {
		if (!branchlessMethods.containsKey(className))
			return new HashSet<String>();

		return branchlessMethods.get(className).keySet();
	}

	/**
	 * Returns a set with all unique methodNames of methods without Branches.
	 * 
	 * @return A set with all unique methodNames of methods without Branches.
	 * @param className
	 *            a {@link java.lang.String} object.
	 */
	public Set<String> getBranchlessMethodsPrefix(String className) {
		Set<String> methods = new HashSet<String>();

		for (String name : branchlessMethods.keySet()) {
			if (name.equals(className) || name.startsWith(className + "$")) {
				methods.addAll(branchlessMethods.get(name).keySet());
			}
		}

		return methods;
	}

	/**
	 * Returns a set with all unique methodNames of methods without Branches.
	 * 
	 * @return A set with all unique methodNames of methods without Branches.
	 * @param className
	 *            a {@link java.lang.String} object.
	 */
	public Set<String> getBranchlessMethodsMemberClasses(String className) {
		Set<String> methods = new HashSet<String>();

		for (String name : branchlessMethods.keySet()) {
			if (name.equals(className) || name.startsWith(className + "$")) {
				methods.addAll(branchlessMethods.get(name).keySet());
			}
		}

		return methods;
	}

	public int getBranchlessMethodLineNumber(String className, String methodName) {
		// check if the given method is branchless
		if (branchlessMethods.get(className) != null
		        && branchlessMethods.get(className).get(className + "." + methodName) != null) {
			return branchlessMethods.get(className).get(className + "." + methodName);
		}
		// otherwise consult the branchMap and return the lineNumber of the earliest Branch

		return branchlessMethods.get(className).get(className + "." + methodName);
	}

	/**
	 * Returns a set with all unique methodNames of methods without Branches.
	 * 
	 * @return A set with all unique methodNames of methods without Branches.
	 */
	public Set<String> getBranchlessMethods() {
		Set<String> methods = new HashSet<String>();

		for (String name : branchlessMethods.keySet()) {
			methods.addAll(branchlessMethods.get(name).keySet());
		}

		return methods;
	}
	
	public boolean isBranchlessMethod(String className, String methodName) {
		Map<String, Integer> methodMap = branchlessMethods.get(className);
		if(methodMap != null) {
			return methodMap.containsKey(methodName);
		}
		return false;
	}

	/**
	 * Returns the number of methods without Branches for class className
	 * 
	 * @return The number of methods without Branches.
	 * @param className
	 *            a {@link java.lang.String} object.
	 */
	public int getNumBranchlessMethods(String className) {
		if (!branchlessMethods.containsKey(className))
			return 0;
		return branchlessMethods.get(className).size();
	}

	/**
	 * Returns the number of methods without Branches for class className
	 * 
	 * @return The number of methods without Branches.
	 * @param className
	 *            a {@link java.lang.String} object.
	 */
	public int getNumBranchlessMethodsPrefix(String className) {
		int num = 0;
		for (String name : branchlessMethods.keySet()) {
			if (name.startsWith(className))
				num += branchlessMethods.get(name).size();
		}
		return num;
	}

	/**
	 * Returns the number of methods without Branches for class className
	 * 
	 * @return The number of methods without Branches.
	 * @param className
	 *            a {@link java.lang.String} object.
	 */
	public int getNumBranchlessMethodsMemberClasses(String className) {
		int num = 0;
		for (String name : branchlessMethods.keySet()) {
			if (name.equals(className) || name.startsWith(className + "$"))
				num += branchlessMethods.get(name).size();
		}
		return num;
	}

	/**
	 * Returns the total number of methods without branches in the instrumented
	 * classes
	 * 
	 * @return
	 */
	public int getNumBranchlessMethods() {
		int num = 0;
		for (String name : branchlessMethods.keySet()) {
			num += branchlessMethods.get(name).size();
		}
		return num;
	}

	/**
	 * Returns a Set containing all classes for which this pool knows Branches
	 * for as Strings
	 * 
	 * @return a {@link java.util.Set} object.
	 */
	public Set<String> knownClasses() {
		Set<String> r = new HashSet<String>();
		r.addAll(branchMap.keySet());
		r.addAll(branchlessMethods.keySet());

		if (logger.isDebugEnabled()) {
			logger.debug("Known classes: " + r);
		}

		return r;
	}

	/**
	 * Returns a Set containing all methods in the class represented by the
	 * given String for which this pool knows Branches for as Strings
	 * 
	 * @param className
	 *            a {@link java.lang.String} object.
	 * @return a {@link java.util.Set} object.
	 */
	public Set<String> knownMethods(String className) {
		Set<String> r = new HashSet<String>();
		Map<String, List<Branch>> methods = branchMap.get(className);
		if (methods != null)
			r.addAll(methods.keySet());

		return r;
	}

	/**
	 * Returns a List containing all Branches in the given class and method
	 * 
	 * Should no such Branch exist an empty List is returned
	 * 
	 * @param className
	 *            a {@link java.lang.String} object.
	 * @param methodName
	 *            a {@link java.lang.String} object.
	 * @return a {@link java.util.List} object.
	 */
	public List<Branch> retrieveBranchesInMethod(String className,
	        String methodName) {
		List<Branch> r = new ArrayList<Branch>();
		if (branchMap.get(className) == null)
			return r;
		List<Branch> branches = branchMap.get(className).get(methodName);
		if (branches != null)
			r.addAll(branches);
		return r;
	}

	/**
	 * <p>
	 * getDefaultBranchForSwitch
	 * </p>
	 * 
	 * @param v
	 *            a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 * @return a {@link org.evosuite.coverage.branch.Branch} object.
	 */
	public Branch getDefaultBranchForSwitch(BytecodeInstruction v) {
		if (!v.isSwitch())
			throw new IllegalArgumentException("switch instruction expected");
		if (!isKnownAsSwitchBranchInstruction(v))
			throw new IllegalArgumentException(
			        "instruction not known to be a switch instruction");
		if (!registeredDefaultCases.containsKey(v))
			throw new IllegalArgumentException(
			        "there is no registered default case for this instruction");

		return registeredDefaultCases.get(v);
	}

	/**
	 * Reset all the data structures used to keep track of the branch
	 * information
	 */
	public void reset() {
		branchCounter = 0;
		branchMap.clear();
		branchlessMethods.clear();
		branchIdMap.clear();
		registeredNormalBranches.clear();
		registeredSwitches.clear();
		registeredDefaultCases.clear();
		switchLabels.clear();
	}

	/**
	 * <p>
	 * clear
	 * </p>
	 * 
	 * TODO: One of these two methods should go
	 */
	public void clear() {
		branchCounter = 0;
		branchMap.clear();
		branchIdMap.clear();
		branchlessMethods.clear();
		switchLabels.clear();
		registeredDefaultCases.clear();
		registeredNormalBranches.clear();
		registeredSwitches.clear();
	}

	/**
	 * <p>
	 * clear
	 * </p>
	 * 
	 * @param className
	 *            a {@link java.lang.String} object.
	 */
	public void clear(String className) {
		branchMap.remove(className);
		branchlessMethods.remove(className);
	}

	/**
	 * <p>
	 * clear
	 * </p>
	 * 
	 * @param className
	 *            a {@link java.lang.String} object.
	 * @param methodName
	 *            a {@link java.lang.String} object.
	 */
	public void clear(String className, String methodName) {
		int numBranches = 0;

		if (branchMap.containsKey(className)) {
			if (branchMap.get(className).containsKey(methodName))
				numBranches = branchMap.get(className).get(methodName).size();
			branchMap.get(className).remove(methodName);
		}
		if (branchlessMethods.containsKey(className))
			branchlessMethods.get(className).remove(methodName);
		logger.info("Resetting branchCounter from " + branchCounter + " to "
		        + (branchCounter - numBranches));
		branchCounter -= numBranches;
	}

}
