/**
 * 
 */
package soottocfg.soot.memory_model;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Verify;

import soot.Local;
import soot.Scene;
import soot.SootClass;
import soot.SootField;
import soot.SootMethod;
import soot.Unit;
import soot.Value;
import soot.jimple.FieldRef;
import soot.jimple.InstanceFieldRef;
import soot.jimple.JimpleBody;
import soot.jimple.SpecialInvokeExpr;
import soot.jimple.StaticFieldRef;
import soot.jimple.Stmt;
//import soot.jimple.spark.geom.geomPA.GeomPointsTo;
//import soot.options.SparkOptions;
import soot.toolkits.graph.CompleteUnitGraph;
import soot.toolkits.graph.UnitGraph;
import soottocfg.cfg.SourceLocation;
import soottocfg.cfg.expression.BinaryExpression;
import soottocfg.cfg.expression.BinaryExpression.BinaryOperator;
import soottocfg.cfg.expression.Expression;
import soottocfg.cfg.expression.IdentifierExpression;
import soottocfg.cfg.expression.TupleAccessExpression;
import soottocfg.cfg.method.Method;
import soottocfg.cfg.statement.AssignStatement;
import soottocfg.cfg.statement.AssumeStatement;
import soottocfg.cfg.statement.CallStatement;
import soottocfg.cfg.statement.PullStatement;
import soottocfg.cfg.statement.PushStatement;
import soottocfg.cfg.type.ReferenceType;
import soottocfg.cfg.type.Type;
import soottocfg.cfg.variable.ClassVariable;
import soottocfg.cfg.variable.Variable;
import soottocfg.soot.util.MethodInfo;
import soottocfg.soot.util.SootTranslationHelpers;

/**
 * @author schaef
 * @author rodykers
 *
 */
public class NewMemoryModel extends BasicMemoryModel {

	private Map<Variable, Map<String, Variable>> fieldToLocalMap = new HashMap<Variable, Map<String, Variable>>();

	// This one sticks around between method calls...
	private Map<Variable, SootField> localToFieldMap = new HashMap<Variable, SootField>();

	// private static Variable staticFieldContainerVariable;
	// private static List<SootField> usedStaticFields;
	public static final String GlobalsClassName = "$Global_";
	// private static final String globalsClassVarName =
	// "JayhornGlobalsClassVar";

	protected static void resetGlobalState() {
		// NewMemoryModel.staticFieldContainerVariable = null;
		// NewMemoryModel.usedStaticFields = null;
	}

	public NewMemoryModel() {
		NewMemoryModel.resetGlobalState();

		// load points to analysis
		// setGeomPointsToAnalysis();
	}

	public void clearFieldToLocalMap() {
		// TODO: this is only here because we know it's only called once per
		// method:
		fieldToLocalMap.clear();
	}

	private boolean pullAt(Unit u, FieldRef fr) {
		SootMethod m = SootTranslationHelpers.v().getCurrentMethod();

		if (fr instanceof InstanceFieldRef) {
			InstanceFieldRef ifr = (InstanceFieldRef) fr;

			// in constructor never pull 'this'
			if (m.isConstructor() && ifr.getBase().equals(m.getActiveBody().getThisLocal()))
				return false;
		} else if (fr instanceof StaticFieldRef) {

			// in static initializer never pull static field
			if (m.isStaticInitializer())
				return false;
		}
		return true;
	}

	/**
	 * Helper method to create a push of all field locals.
	 * This method should only be used at the very end of a constructor.
	 */
	private void pushAllFields() {
		SootMethod m = SootTranslationHelpers.v().getCurrentMethod();
		if (!m.isConstructor() || m.isStatic() || !m.isConcrete()) {
			return;
		}
		SourceLocation loc = this.statementSwitch.getCurrentLoc();

		Variable thisLocal = this.statementSwitch.getMethodInfo().lookupLocalVariable(m.getActiveBody().getThisLocal());
		ClassVariable classVar = ((ReferenceType) thisLocal.getType()).getClassVariable();

		List<Variable> vars = new LinkedList<Variable>();
		for (SootField sf : SootTranslationHelpers.findNonStaticFieldsRecursively(m.getDeclaringClass())) {
			vars.add(lookupFieldLocal(thisLocal, sf));
		}

		List<Expression> packedVars = new LinkedList<Expression>();
		for (int i = 0; i < vars.size(); i++) {
			packedVars.add(new IdentifierExpression(loc, vars.get(i)));
		}

		this.statementSwitch
				.push(new PushStatement(loc, classVar, new IdentifierExpression(loc, thisLocal), packedVars));
	}

	private boolean pushAt(Unit u, FieldRef fr) {
		SootMethod m = SootTranslationHelpers.v().getCurrentMethod();
		UnitGraph graph = new CompleteUnitGraph(m.getActiveBody());

		if (fr instanceof InstanceFieldRef) {
			InstanceFieldRef ifr = (InstanceFieldRef) fr;

			// in constructor only push 'this' after the last access
			if (m.isConstructor() && ifr.getBase().equals(m.getActiveBody().getThisLocal())) {

				// check if there is any path from 'u' to the tail(s) that does
				// not contain an write to 'this'
				List<Unit> tails = graph.getTails();
				List<Unit> todo = new LinkedList<Unit>();
				Set<Unit> done = new HashSet<Unit>();
				todo.add(u);
				boolean foundPathWithoutAccess = false;
				while (!todo.isEmpty()) {
					Unit unit = todo.remove(0);
					done.add(unit);
					// if it contains another write to 'this', stop exploring
					// this path
					Stmt s = (Stmt) unit;
					if (s.containsFieldRef()) {
						FieldRef fr2 = s.getFieldRef();
						if (fr2 instanceof InstanceFieldRef) {
							InstanceFieldRef ifr2 = (InstanceFieldRef) fr2;
							if (ifr2.getBase().equals(m.getActiveBody().getThisLocal()) && !u.equals(unit)) {
								continue;
							}
						}
					} else if (s.containsInvokeExpr() && s.getInvokeExpr() instanceof SpecialInvokeExpr) {
						// also stop exploring if there is a call to the
						// super class constructor.
						SpecialInvokeExpr ivk = (SpecialInvokeExpr) s.getInvokeExpr();

						if (ivk.getMethod().isConstructor() && ivk.getBase().equals(m.getActiveBody().getThisLocal())) {
							// super class constructor.
							continue;
						}
					}

					if (tails.contains(unit)) {
						if (!s.containsInvokeExpr()) {
							foundPathWithoutAccess = true; // at the end of this
															// path
						}
					} else {
						for (Unit next : graph.getSuccsOf(unit)) {
							if (!todo.contains(next) && !done.contains(next)) {
								todo.add(next);
							}
						}
//						todo.addAll(graph.getSuccsOf(unit));
					}
				}
				return foundPathWithoutAccess;
			}
		} else if (fr instanceof StaticFieldRef) {
			// in static initializer only push at the end
			if (m.isStaticInitializer()) {

				// check if there is any path from 'u' to the tail(s) that does
				// not contain an access to 'this'
				List<Unit> tails = graph.getTails();
				List<Unit> todo = new LinkedList<Unit>();
				todo.add(u);
				boolean foundPathWithoutAccess = false;
				while (!todo.isEmpty()) {
					Unit unit = todo.remove(0);

					// if it contains another access to a static field, stop
					// exploring this path
					Stmt s = (Stmt) unit;
					if (s.containsFieldRef()) {
						FieldRef fr2 = s.getFieldRef();
						if (fr2 instanceof StaticFieldRef && !u.equals(unit))
							continue;
					}

					if (tails.contains(unit))
						foundPathWithoutAccess = true; // at the end of this
														// path
					else
						todo.addAll(graph.getSuccsOf(unit));
				}
				return foundPathWithoutAccess;
			}
		}
		return true;
	}

	@Override
	public void mkHeapWriteStatement(Unit u, FieldRef fieldRef, Value rhs) {
		SourceLocation loc = SootTranslationHelpers.v().getSourceLocation(u);

		rhs.apply(valueSwitch);
		Expression value = valueSwitch.popExpression();

		ClassVariable classVar;
		IdentifierExpression base;
		List<Variable> fieldLocals = new LinkedList<Variable>();

		if (fieldRef instanceof InstanceFieldRef) {
			InstanceFieldRef ifr = (InstanceFieldRef) fieldRef;
			ifr.getBase().apply(valueSwitch);
			base = (IdentifierExpression) valueSwitch.popExpression();
			// classVar = lookupClassVariable(
			// SootTranslationHelpers.v().getClassConstant(fieldRef.getField().getDeclaringClass().getType()));
			classVar = ((ReferenceType) base.getType()).getClassVariable();
			// for (SootField sf :
			// findFieldsRecursively(fieldRef.getField().getDeclaringClass())) {
			for (SootField sf : SootTranslationHelpers.findFieldsRecursivelyForRef(ifr.getBase())) {
				if (!sf.isStatic()) {
					fieldLocals.add(lookupFieldLocal(base.getVariable(), sf));
				}
			}
		} else if (fieldRef instanceof StaticFieldRef) {
			/*
			 * For static fields, we use the artificial global class
			 * that holds all static fields as base.
			 */
			Variable glob = getStaticFieldContainerVariable(fieldRef.getField().getDeclaringClass());
			classVar = ((ReferenceType) glob.getType()).getClassVariable();
			base = new IdentifierExpression(loc, glob);

			for (SootField sf : staticFieldsPerClass.get(glob)) {
				fieldLocals.add(lookupFieldLocal(glob, sf));
			}

		} else {
			throw new RuntimeException("not implemented");
		}

		Variable fieldVar = lookupFieldLocal(fieldRef); // TODO

		Verify.verify(fieldLocals.contains(fieldVar), fieldLocals.toString() + "\ndoesn't contain " + fieldVar);

		// Variable[] vars = classVar.getAssociatedFields();
		Variable[] vars = fieldLocals.toArray(new Variable[fieldLocals.size()]);
		// ------------- pull ---------------
		if (pullAt(u, fieldRef)) {
			List<IdentifierExpression> unpackedVars = new LinkedList<IdentifierExpression>();
			for (int i = 0; i < vars.length; i++) {
				unpackedVars.add(new IdentifierExpression(this.statementSwitch.getCurrentLoc(), vars[i]));
			}
			this.statementSwitch.push(new PullStatement(loc, classVar, base, unpackedVars));
		}
		// ------------------------------------
		this.statementSwitch.push(new AssignStatement(loc,
				new IdentifierExpression(this.statementSwitch.getCurrentLoc(), fieldVar), value));
		// ------------- push -----------------
		if (pushAt(u, fieldRef)) {
			List<Expression> packedVars = new LinkedList<Expression>();
			for (int i = 0; i < vars.length; i++) {
				packedVars.add(new IdentifierExpression(this.statementSwitch.getCurrentLoc(), vars[i]));
			}
			this.statementSwitch.push(new PushStatement(loc, classVar, base, packedVars));
		}
		// ------------------------------------

	}

	@Override
	public void mkHeapReadStatement(Unit u, FieldRef fieldRef, Value lhs) {
		SourceLocation loc = SootTranslationHelpers.v().getSourceLocation(u);
		// Variable fieldVar = lookupField(fieldRef.getField());

		lhs.apply(valueSwitch);
		IdentifierExpression left = (IdentifierExpression) valueSwitch.popExpression();

		ClassVariable classVar;
		IdentifierExpression base;
		List<Variable> fieldLocals = new LinkedList<Variable>();
		if (fieldRef instanceof InstanceFieldRef) {
			InstanceFieldRef ifr = (InstanceFieldRef) fieldRef;
			ifr.getBase().apply(valueSwitch);
			base = (IdentifierExpression) valueSwitch.popExpression();
			// classVar = lookupClassVariable(
			// SootTranslationHelpers.v().getClassConstant(fieldRef.getField().getDeclaringClass().getType()));
			classVar = ((ReferenceType) base.getType()).getClassVariable();

			for (SootField sf : SootTranslationHelpers.findFieldsRecursivelyForRef(ifr.getBase())) {
				if (!sf.isStatic()) {
					fieldLocals.add(lookupFieldLocal(base.getVariable(), sf));
				}
			}
		} else if (fieldRef instanceof StaticFieldRef) {
			/*
			 * For static fields, we use the artificial global class
			 * that holds all static fields as base.
			 */
			Variable glob = getStaticFieldContainerVariable(fieldRef.getField().getDeclaringClass());
			classVar = ((ReferenceType) glob.getType()).getClassVariable();
			base = new IdentifierExpression(loc, glob);
			for (SootField sf : staticFieldsPerClass.get(glob)) {
				fieldLocals.add(lookupFieldLocal(glob, sf));
			}

		} else {
			throw new RuntimeException("not implemented");
		}

		SootField field = fieldRef.getField();
		if (SootTranslationHelpers.v().isWrittenOnce(field)) {
			if (!field.isStatic()) {
				TupleAccessExpression tae = new TupleAccessExpression(loc, base.getVariable(), field.getName());
				this.statementSwitch.push(new AssignStatement(loc, left, tae));
				//New, needs testing!
				/* We don't need to do this for writing these variables 
				 * because we assume they are constant anyway.
				 */
				return;
			} else {
				//TODO:
			}
		}
		
		Variable fieldVar = lookupFieldLocal(fieldRef); // TODO
		Verify.verify(fieldLocals.contains(fieldVar));

		// Variable[] vars = classVar.getAssociatedFields();
		Variable[] vars = fieldLocals.toArray(new Variable[fieldLocals.size()]);

		// ------------- pull ---------------
		if (pullAt(u, fieldRef)) {
			List<IdentifierExpression> unpackedVars = new LinkedList<IdentifierExpression>();
			for (int i = 0; i < vars.length; i++) {
				unpackedVars.add(new IdentifierExpression(this.statementSwitch.getCurrentLoc(), vars[i]));
			}
			this.statementSwitch.push(new PullStatement(loc, classVar, base, unpackedVars));
		}
		// ------------------------------------
		this.statementSwitch.push(new AssignStatement(loc, left,
				new IdentifierExpression(this.statementSwitch.getCurrentLoc(), fieldVar)));
		// ------------- push -----------------
		// if (pushAt(u, fieldRef)) {
		// List<Expression> packedVars = new LinkedList<Expression>();
		// for (int i = 0; i < vars.length; i++) {
		// packedVars.add(new
		// IdentifierExpression(this.statementSwitch.getCurrentLoc(), vars[i]));
		// }
		// this.statementSwitch.push(new PushStatement(loc, classVar, base,
		// packedVars));
		// }
		// ------------------------------------
	}

	Map<SootClass, Variable> staticClassGlobals = new LinkedHashMap<SootClass, Variable>();
	Map<Variable, List<SootField>> staticFieldsPerClass = new HashMap<Variable, List<SootField>>();

	protected Variable getStaticFieldContainerVariable(SootClass sootClass) {
		if (!staticClassGlobals.containsKey(sootClass)) {
			ClassVariable classVar = new ClassVariable("$StaticFields_" + sootClass.getName(),
					new LinkedList<ClassVariable>());
			List<SootField> usedFields = new LinkedList<SootField>();
			List<Variable> fieldVars = new LinkedList<Variable>();

			for (SootClass sc : new LinkedList<SootClass>(Scene.v().getClasses())) {
				if (sc.resolvingLevel() >= SootClass.BODIES) {
					for (SootMethod sm : sc.getMethods()) {
						try {
							for (Unit u : sm.retrieveActiveBody().getUnits()) {
								Stmt st = (Stmt) u;
								if (st.containsFieldRef()) {
									SootField sf = st.getFieldRef().getField();
									if (sf.isStatic() && !usedFields.contains(sf)
											&& sf.getDeclaringClass().equals(sootClass)) {
										if (!sf.equals(SootTranslationHelpers.v().getExceptionGlobal())) {
											fieldVars.add(
													new Variable(sf.getDeclaringClass().getName() + "." + sf.getName(),
															this.lookupType(sf.getType())));
											usedFields.add(sf);
										}
									}
								}
							}
						} catch (Exception e) {

						}
					}
				}
			}
			classVar.addFields(fieldVars);
			SootTranslationHelpers.v().getProgram().addClassVariable(classVar);
			Variable var = new Variable(GlobalsClassName + sootClass.getName(), new ReferenceType(classVar), true,
					true);
			staticClassGlobals.put(sootClass, var);
			staticFieldsPerClass.put(var, usedFields);
			this.program.getGlobalsMap().put(sootClass.getName(), var);
		}
		return staticClassGlobals.get(sootClass);

		// if (staticFieldContainerVariable == null) {
		// usedStaticFields = new LinkedList<SootField>();
		// ClassVariable classVar = new ClassVariable(globalsClassVarName, new
		// LinkedList<ClassVariable>());
		// Set<Variable> fields = new LinkedHashSet<Variable>();
		// for (SootClass sc : new
		// LinkedList<SootClass>(Scene.v().getClasses())) {
		// if (sc.resolvingLevel() >= SootClass.BODIES) {
		// for (SootMethod sm : sc.getMethods()) {
		// try {
		// for (Unit u : sm.retrieveActiveBody().getUnits()) {
		// Stmt st = (Stmt) u;
		// if (st.containsFieldRef()) {
		// SootField sf = st.getFieldRef().getField();
		// if (sf.isStatic() && !usedStaticFields.contains(sf)) {
		//
		// if (!sf.equals(SootTranslationHelpers.v().getExceptionGlobal())) {
		// // TODO hack to exclude the
		// // exception field.
		// fields.add(new Variable(sf.getDeclaringClass().getName() + "." +
		// sf.getName(), this.lookupType(sf.getType())));
		// usedStaticFields.add(sf);
		//
		// }
		// }
		// }
		// }
		// } catch (Exception e) {
		//
		// }
		// }
		// }
		// }
		//
		// List<Variable> fieldList = new LinkedList<Variable>();
		// fieldList.addAll(fields);
		// classVar.addFields(fieldList);
		// SootTranslationHelpers.v().getProgram().addClassVariable(classVar);
		// staticFieldContainerVariable = new Variable(globalsClassName, new
		// ReferenceType(classVar), true, true);
		// }
		// return staticFieldContainerVariable;
	}

	@Override
	public void mkConstructorCall(Unit u, SootMethod constructor, List<Expression> args) {
		SourceLocation loc = SootTranslationHelpers.v().getSourceLocation(u);
		Method method = SootTranslationHelpers.v().lookupOrCreateMethod(constructor);

		List<Expression> receiver = new LinkedList<Expression>();
		receiver.add(this.statementSwitch.getMethodInfo().getExceptionVariable());

		SootClass declClass = constructor.getDeclaringClass();
		SootClass currentClass = SootTranslationHelpers.v().getCurrentMethod().getDeclaringClass();

		if (currentClass.hasSuperclass() && declClass.equals(currentClass.getSuperclass())) {
			/*
			 * If this is a call to the super class constructor, use the return
			 * values to assign all field locals.
			 */
			JimpleBody jb = (JimpleBody) SootTranslationHelpers.v().getCurrentMethod().getActiveBody();
			Variable thisLocal = this.statementSwitch.getMethodInfo().lookupLocalVariable(jb.getThisLocal());

			for (SootField sf : SootTranslationHelpers.findNonStaticFieldsRecursively(declClass)) {
				receiver.add(new IdentifierExpression(loc, lookupFieldLocal(thisLocal, sf)));
			}
			verifyArgLength(u, method, receiver);

			CallStatement stmt = new CallStatement(loc, method, args, receiver);
			this.statementSwitch.push(stmt);
			// TODO: this is a hack at should be handeled properly.
			// TODO: @Rody, instead of pushing after the consturctor call,
			// we could just use the field locals and treat the constructor
			// call as a pull.
			pushAllFields();
		} else {
			/*
			 * If this is not a superclass constructor, just fill the args up
			 * with dummy variables.
			 */

			ClassVariable cv = SootTranslationHelpers.v().getClassVariable(declClass);
			IdentifierExpression baseExpr = (IdentifierExpression) args.get(0);

			List<Variable> finalFields = Arrays.asList(cv.getFinalFields());

			List<AssumeStatement> assumeTupleVals = new LinkedList<AssumeStatement>();
			// ignore the first outparam (for the exception) because we added
			// that already.

			int i = 0;
			for (SootField sf : SootTranslationHelpers.findNonStaticFieldsRecursively(declClass)) {
				Variable fieldLocal = lookupFieldLocal(baseExpr.getVariable(), sf);
				receiver.add(new IdentifierExpression(loc, fieldLocal));
				Variable fieldVar = cv.getAssociatedFields()[i];
				if (finalFields.contains(fieldVar)) {
					TupleAccessExpression tae = new TupleAccessExpression(loc, baseExpr.getVariable(),
							fieldVar.getName());
					Expression exp = new BinaryExpression(loc, BinaryOperator.Eq, tae,
							new IdentifierExpression(loc, fieldLocal));
					assumeTupleVals.add(new AssumeStatement(loc, exp));
				}
				i++;
			}
			verifyArgLength(u, method, receiver);

			CallStatement stmt = new CallStatement(loc, method, args, receiver);
			this.statementSwitch.push(stmt);
			for (AssumeStatement s : assumeTupleVals) {
//				System.err.println("sfgdfgd\t" + s);
				//TODO: debug if this is reachable
				this.statementSwitch.push(s);
			}
		}

	}

	private void verifyArgLength(Unit u, Method method, List<Expression> receiver) {
		if (method.getReturnType().size() != receiver.size()) {
			StringBuilder sb = new StringBuilder();
			sb.append("[");
			for (Type t : method.getReturnType()) {
				sb.append(", ");
				sb.append(t.toString());
			}
			sb.append("]");
			sb.append(" != ");
			sb.append("[");
			for (Expression e : receiver) {
				sb.append(", ");
				sb.append(e.toString());
			}
			sb.append("]");

			Verify.verify(false, method.getMethodName() + " -> " + sb.toString());

		}
	}

	@Override
	public void mkCopy(Local lhs, Local rhs) {
		// System.out.println("Copying " + rhs + " into " + lhs);
		lhs.apply(valueSwitch);
		IdentifierExpression base = (IdentifierExpression) valueSwitch.popExpression();
		for (Map.Entry<Variable, Map<String, Variable>> e : fieldToLocalMap.entrySet()) {
			if (e.getKey().getName().equals(rhs.getName())) {
				fieldToLocalMap.put(base.getVariable(), e.getValue());
				return;
			}
		}
	}

	private Map<String, Variable> getMapForVar(Variable baseVar) {
		if (!fieldToLocalMap.containsKey(baseVar)) {
			fieldToLocalMap.put(baseVar, new HashMap<String, Variable>());
		}
		return fieldToLocalMap.get(baseVar);
	}

	private Variable lookupFieldLocal(Variable baseVar, SootField sf) {
		Map<String, Variable> f2l = getMapForVar(baseVar);
		if (!f2l.containsKey(sf.getDeclaration())) {
			/*
			 * for this references in constructors
			 * we return the out params as field locals.
			 * Unless it is a static field, then we treat it
			 * like any other field.
			 */
			if (this.statementSwitch.getMethod().isConstructor()) {
				JimpleBody jb = (JimpleBody) this.statementSwitch.getMethod().getActiveBody();
				Variable thisVar = statementSwitch.getMethodInfo().lookupLocalVariable(jb.getThisLocal());
				if (baseVar.equals(thisVar)) {
					int i = 1;
					for (SootField cfield : SootTranslationHelpers
							.findNonStaticFieldsRecursively(sf.getDeclaringClass())) {
						if (cfield.equals(sf)) {
							Variable outVar = statementSwitch.getMethodInfo().getOutVariable(i);
							if (!f2l.containsKey(sf.getDeclaration())) {
								f2l.put(sf.getDeclaration(), outVar);
							}
							localToFieldMap.put(outVar, sf);
							break;
						}
						i++;
					}
					Verify.verify(f2l.containsKey(sf.getDeclaration()));
					return f2l.get(sf.getDeclaration());
				}
			}

			MethodInfo currentMethodInfo = this.statementSwitch.getMethodInfo();
			soottocfg.cfg.type.Type tp = this.lookupType(sf.getType());
			String name = baseVar.getName() + "_" + sf.getName() + "_" + sf.getNumber();
			if (sf.isStatic()) {
				name = sf.getDeclaringClass().getName() + "_" + sf.getName();
			}

			boolean makeConst = SootTranslationHelpers.v().isWrittenOnce(sf);
			// boolean makeConst = sf.isFinal();
			Variable l = currentMethodInfo.createFreshLocal(name, tp, makeConst, false);
			f2l.put(sf.getDeclaration(), l);

			// also add it to the localToFieldMap
			localToFieldMap.put(l, sf);
		}

		return f2l.get(sf.getDeclaration());
	}

	private Variable lookupFieldLocal(FieldRef fieldRef) {
		Variable baseVar = null;
		if (fieldRef instanceof InstanceFieldRef) {
			InstanceFieldRef ifr = (InstanceFieldRef) fieldRef;
			ifr.getBase().apply(valueSwitch);
			IdentifierExpression base = (IdentifierExpression) valueSwitch.popExpression();
			baseVar = base.getVariable();
		} else if (fieldRef instanceof StaticFieldRef) {
			baseVar = getStaticFieldContainerVariable(fieldRef.getField().getDeclaringClass());
		} else {
			throw new RuntimeException("not implemented");
		}
		return lookupFieldLocal(baseVar, fieldRef.getField());
	}

	public SootField lookupField(Variable local) {
		return localToFieldMap.get(local);
	}

	// private void setGeomPointsToAnalysis() {
	// HashMap<String, String> opt = new HashMap<String, String>();
	// opt.put("enabled", "true");
	// opt.put("verbose", "true");
	// opt.put("ignore-types", "false");
	// opt.put("force-gc", "false");
	// // opt.put("pre-jimplify","false");
	// // opt.put("vta","false");
	// // opt.put("rta","false");
	// // opt.put("field-based","false");
	// // opt.put("types-for-sites","false");
	// // opt.put("merge-stringbuffer","true");
	// // opt.put("string-constants","false");
	// // opt.put("simulate-natives","true");
	// // opt.put("simple-edges-bidirectional","false");
	// // opt.put("on-fly-cg","true");
	// // opt.put("simplify-offline","false");
	// // opt.put("simplify-sccs","false");
	// // opt.put("ignore-types-for-sccs","false");
	// // opt.put("propagator","worklist");
	// opt.put("set-impl", "double");
	// opt.put("double-set-old", "hybrid");
	// opt.put("double-set-new", "hybrid");
	// // opt.put("dump-html","false");
	// // opt.put("dump-pag","false");
	// // opt.put("dump-solution","false");
	// // opt.put("topo-sort","false");
	// // opt.put("dump-types","true");
	// // opt.put("class-method-var","true");
	// // opt.put("dump-answer","false");
	// // opt.put("add-tags","false");
	// // opt.put("set-mass","false");
	//
	// // SparkTransformer.v().transform("",opt);
	// SparkOptions so = new SparkOptions(opt);
	// GeomPointsTo gpt = new GeomPointsTo(so);
	// Scene.v().setPointsToAnalysis(gpt);
	// }
}
