package pag.builder;

import soot.jimple.ClassConstant;
import soot.jimple.StringConstant;
import soot.jimple.spark.pag.*;
import soot.toolkits.scalar.Pair;
import pag.PAG;
import pag.node.GNode;
import pag.node.alloc.Alloc_Node;
import pag.node.var.GlobalVar_Node;
import pag.node.var.Var_Node;
import pta.ContextSensPTA;
import pta.PTA;
import soot.*;

/**
 * Factory for nodes not specific to a given method.
 * 
 * @author Ondrej Lhotak
 */
public class GlobalPAGBuilder {

	public GlobalPAGBuilder(PAG pag, PTA pta) {
		this.pag = pag;
		this.pta = pta;
	}

	final public GNode caseDefaultClassLoader() {
		return createUpcastGlobal(PointsToAnalysis.DEFAULT_CLASS_LOADER, PointsToAnalysis.DEFAULT_CLASS_LOADER_LOCAL, RefType.v("java.lang.ClassLoader"));
	}

	final public GNode caseMainClassNameString() {
		return createGlobal(PointsToAnalysis.MAIN_CLASS_NAME_STRING, PointsToAnalysis.MAIN_CLASS_NAME_STRING_LOCAL, RefType.v("java.lang.String"));
	}

	final public GNode caseMainThreadGroup() {
		return createGlobal(PointsToAnalysis.MAIN_THREAD_GROUP_NODE, PointsToAnalysis.MAIN_THREAD_GROUP_NODE_LOCAL, RefType.v("java.lang.ThreadGroup"));
	}

	final public GNode casePrivilegedActionException() {
		return createUpcastGlobal(PointsToAnalysis.PRIVILEGED_ACTION_EXCEPTION, PointsToAnalysis.PRIVILEGED_ACTION_EXCEPTION_LOCAL, RefType.v("java.security.PrivilegedActionException"));
	}

	final public GNode caseCanonicalPath() {
		return createGlobal(PointsToAnalysis.CANONICAL_PATH, PointsToAnalysis.CANONICAL_PATH_LOCAL, RefType.v("java.lang.String"));
	}

	final public GNode caseMainThread() {
		return createGlobal(PointsToAnalysis.MAIN_THREAD_NODE, PointsToAnalysis.MAIN_THREAD_NODE_LOCAL, RefType.v("java.lang.Thread"));
	}

	final public GNode caseFinalizeQueue() {
		return pag.makeGlobalVarNode(PointsToAnalysis.FINALIZE_QUEUE, RefType.v("java.lang.Object"));
	}

	final public GNode caseArgv() {
		RefType stringType = RefType.v("java.lang.String");
		Type stringArrayType = ArrayType.v(stringType, 1);
		Alloc_Node argv = pag.makeAllocNode(PointsToAnalysis.STRING_ARRAY_NODE, stringArrayType, null);
		Var_Node sanl = pag.makeLocalVarNode(PointsToAnalysis.STRING_ARRAY_NODE_LOCAL, stringArrayType , null);
		addParameterizedGlobalPAGEdge(argv, sanl);
		Var_Node sang = pag.makeGlobalVarNode(PointsToAnalysis.STRING_ARRAY_NODE, stringArrayType);
		addParameterizedGlobalPAGEdge(sanl, sang);
		Alloc_Node stringNode = pag.makeAllocNode(PointsToAnalysis.STRING_NODE, stringType, null);
		Var_Node stringNodeLocal = pag.makeLocalVarNode(PointsToAnalysis.STRING_NODE_LOCAL, stringType, null);
		addParameterizedGlobalPAGEdge(stringNode, stringNodeLocal);
		addParameterizedGlobalPAGEdge(stringNodeLocal, pag.makeFieldRefNode(sanl, ArrayElement.v()));
		return sang;
	}

//	final public GNode caseNewInstance(Var_Node cls) {
//		Var_Node local = pag.makeGlobalVarNode(cls, RefType.v("java.lang.Object"));
//		for (SootClass cl : Scene.v().dynamicClasses()) {
//			Alloc_Node site = pag.makeAllocNode(new Pair<Var_Node, SootClass>(cls, cl), cl.getType(), null);
//			addParameterizedGlobalPAGEdge(site, local);
//		}
//		return local;
//	}

	Var_Node caseThrow;
	public GNode caseThrow() {
		Var_Node ret = caseThrow;
		if(ret==null){
			caseThrow = ret = pag.makeGlobalVarNode(PointsToAnalysis.EXCEPTION_NODE, RefType.v("java.lang.Throwable"));
			ret.setInterProcTarget();
			ret.setInterProcSource();
		}
		return ret;
	}
	
	public GNode caseStringConstant(StringConstant sc) {
		Var_Node ret = pag.findVarNode(sc);
		if(ret==null){
			Alloc_Node stringConstantNode = pag.makeStringConstantNode(sc);
			return createGlobalExit(stringConstantNode, new Pair<>(null, sc), sc, RefType.v("java.lang.String"));
		}
		return ret;
	}
	
	public GNode caseClassConstant(ClassConstant cc) {
		Var_Node ret = pag.findVarNode(cc);
		if(ret==null){
			Alloc_Node classConstantNode = pag.makeClassConstantNode(cc);
			return createGlobalExit(classConstantNode, new Pair<>(null, cc), cc, RefType.v("java.lang.Class"));
		}
		return ret;
	}
	
	/* End of public methods. */
	
	protected GlobalVar_Node createUpcastGlobal(Object value, Object localValue, RefType type){
		return createGlobalExit(pag.makeAllocNode(value, AnySubType.v(type), null), localValue, value, type);
	}
	
	protected GlobalVar_Node createGlobal(Object value, Object localValue, Type type){
		return createGlobalExit(pag.makeAllocNode(value, type, null), localValue, value, type);
	}
	
	protected GlobalVar_Node createGlobalExit(Alloc_Node alloc, Object localValue, Object value, Type type){
		Var_Node l = pag.makeLocalVarNode(localValue, type, null);
		addParameterizedGlobalPAGEdge(alloc, l);
		GlobalVar_Node g = pag.makeGlobalVarNode(value, type);
		addParameterizedGlobalPAGEdge(l, g);
		return g;
	}
	
	protected void addParameterizedGlobalPAGEdge(GNode from, GNode to) {
		if (pta instanceof ContextSensPTA){
			ContextSensPTA cspta = (ContextSensPTA) pta;
			from = cspta.parameterize(from, cspta.emptyContext());
			to = cspta.parameterize(to, cspta.emptyContext());
		}
		pag.addEdge(from, to);
	}

	protected PAG pag;
	protected PTA pta;
}
