/**
 * 
 */
package soottocfg.cfg;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jgrapht.DirectedGraph;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;

import soottocfg.cfg.method.CfgBlock;
import soottocfg.cfg.method.Method;
import soottocfg.cfg.statement.CallStatement;
import soottocfg.cfg.statement.Statement;
import soottocfg.cfg.type.Type;
import soottocfg.cfg.variable.ClassVariable;
import soottocfg.cfg.variable.Variable;

/**
 * @author schaef
 *
 */
public class Program {

	private final Map<String, Variable> globalVariables = new LinkedHashMap<String, Variable>();
	private final Map<String, Method> methods = new LinkedHashMap<String, Method>();

	private Method entryPoint;

	private DirectedGraph<Method, DefaultEdge> callGraph;
	private final DirectedGraph<ClassVariable, DefaultEdge> typeGraph = new DefaultDirectedGraph<ClassVariable, DefaultEdge>(
			DefaultEdge.class);;


	public void addClassVariable(ClassVariable cv) {
		if (!this.typeGraph.containsVertex(cv)) {
			this.typeGraph.addVertex(cv);
			for (ClassVariable parent : cv.getParents()) {
				addClassVariable(parent);
				this.typeGraph.addEdge(parent, cv);
			}
		}
	}

	public Set<ClassVariable> getClassVariables() {
		return this.typeGraph.vertexSet();
	}

	public ClassVariable findClassVariableByName(final String name) {
		for (ClassVariable cv : this.typeGraph.vertexSet()) {
			if (name.equals(cv.getName())) {
				return cv;
			}
		}
		return null;
	}

	public DirectedGraph<ClassVariable, DefaultEdge> getTypeGraph() {
		return this.typeGraph;
	}

	public Variable lookupGlobalVariable(String varName, Type t) {
		if (!this.globalVariables.containsKey(varName)) {
			this.globalVariables.put(varName, new Variable(varName, t, true, true));
		}
		return this.globalVariables.get(varName);
	}	


	public List<Variable> getGlobalVariables() {
		return new LinkedList<Variable>(this.globalVariables.values());
	}

	public Map<String, Variable> getGlobalsMap() {
		return this.globalVariables;
	}
	
	public Method lookupMethod(String methodSignature) {
		return methods.get(methodSignature);
	}

	public void addMethod(Method m) {
		// set the callGraph to null because it has to be recomputed.
		callGraph = null;
		this.methods.put(m.getMethodName(), m);
	}

	public void removeMethods(Collection<Method> methods) {
		callGraph = null;
		for (Method m : methods) {
			this.methods.remove(m.getMethodName());
		}
	}

	public void setEntryPoint(Method entry) {
		entryPoint = entry;
	}

	public Method getEntryPoint() {
		return entryPoint;
	}

	public Method[] getMethods() {
		return methods.values().toArray(new Method[methods.size()]);
	}

	public DirectedGraph<Method, DefaultEdge> getCallGraph() {
		if (callGraph == null) {
			computeCallGraph();
		}
		return callGraph;
	}

	private void computeCallGraph() {
		callGraph = new DefaultDirectedGraph<Method, DefaultEdge>(DefaultEdge.class);
		for (Method m : methods.values()) {
			callGraph.addVertex(m);
		}
		for (Method m : methods.values()) {
			for (CfgBlock b : m.vertexSet()) {
				for (Statement s : b.getStatements()) {
					if (s instanceof CallStatement) {
						Method callee = ((CallStatement) s).getCallTarget();
						if (!callGraph.containsEdge(m, callee)) {
							callGraph.addEdge(m, callee);
						}
					}
				}
			}
		}
	}

	public String toString() {
		StringBuilder prog = new StringBuilder();

		// global variables
		for (Variable g : globalVariables.values()) {
			prog.append(g + ";\n");
		}
		prog.append("\n");

		List<Method> sortedMethods = new LinkedList<Method>(methods.values());
		Collections.sort(sortedMethods, new Comparator<Method>() {
			@Override
			public int compare(Method o1, Method o2) {
				return o1.getMethodName().compareTo(o2.getMethodName());
			}
		});

		// methods
		for (Method m : sortedMethods) {
			prog.append(m + "\n");
		}

		return prog.toString();
	}
}
