package lu.uni.snt.droidra.booster;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import soot.ArrayType;
import soot.RefType;
import soot.Scene;
import soot.SootClass;
import soot.SootField;
import soot.SootMethod;
import soot.Type;
import soot.Unit;
import soot.VoidType;
import soot.jimple.Jimple;
import soot.jimple.JimpleBody;

public class Mocker {

	public static SootClass mockSootClass(String clsName)
	{
		SootClass sc = null;
		
		if (Scene.v().containsClass(clsName))
		{
			sc = Scene.v().getSootClass(clsName);
			
			if (sc.isPhantom())
			{
				//sc.setPhantom(false);
				sc.setApplicationClass();
				sc.setInScene(true);
				
				try {
					for (Field field : sc.getClass().getFields())
					{
						if (field.getName().equals("isPhantom"))
						{
							field.setAccessible(true);
							field.setBoolean(sc, false);
						}
					}
				} 
				catch (Exception e) 
				{
					e.printStackTrace();
				}
			}
		}
		else
		{
			sc = new SootClass(clsName);
			sc.setSuperclass(Scene.v().getSootClass("java.lang.Object"));
			
			sc.setPhantom(false);
			sc.setApplicationClass();
			sc.setInScene(true);
		}
		
		mockConstructor(sc);

		return sc;
	}
	
	public static SootField mockSootField(String clsName, String fieldName, boolean isStatic)
	{
		SootClass sc = mockSootClass(clsName);
		
		SootField sf = null;
		
		try
		{
			sf = sc.getFieldByName(fieldName);
		}
		catch (Exception ex)
		{
			sf = null;
		}
		
		if (null == sf)
		{
			int m = Modifier.PUBLIC;
			if (isStatic)
			{
				m = m | Modifier.STATIC;
			}
			
			sf = new SootField(fieldName, InstrumentationUtils.toSootTypeByName(fieldName), m);
			sc.addField(sf);
		}
		
		
		return sf;
	}
	
	public static SootMethod mockSootMethod(String clsName, String methodSubSignature, boolean isStatic)
	{
		SootClass sc = mockSootClass(clsName);
		
		SootMethod sm = null;

		try
		{
			sm = sc.getMethod(methodSubSignature);
		}
		catch (Exception ex)
		{
			sm = null;
		}

		if (null == sm)
		{
			int m = Modifier.PUBLIC;
			if (isStatic)
			{
				m = m | Modifier.STATIC;
			}
			
			List<Type> paramTypes = new ArrayList<Type>();
			paramTypes.add(ArrayType.v(RefType.v("java.lang.Object"), 1));
			
			String[] strs = methodSubSignature.split(" ");
			String methodName = strs[1].trim().substring(0, strs[1].trim().indexOf("("));
			
			if (null == methodName || methodName.isEmpty())
			{
				return null;
			}
			
			sm = new SootMethod(methodName, paramTypes, RefType.v("java.lang.Object"), m);
			sc.addMethod(sm);
			
			//Add body of sm
			JimpleBody b = Jimple.v().newBody(sm);
	        sm.setActiveBody(b);
	        //LocalGenerator lg = new LocalGenerator(b);
			{
				b.insertIdentityStmts();
				
				
				
				//Local rtLoc = lg.generateLocal(RefType.v("java.lang.Object"));
				
				//Local param0 = lg.generateLocal(ArrayType.v(RefType.v("java.lang.Object"), 1));
				//Unit param0U = Jimple.v().newIdentityStmt(rtLoc, Jimple.v().newParameterRef(ArrayType.v(RefType.v("java.lang.Object"), 1), 0));
				
				
				//Unit rtLocAssignU = Jimple.v().newAssignStmt(rtLoc, param0);
				
				Unit returnU = Jimple.v().newReturnStmt(b.getParameterLocal(0));
				
				//b.getUnits().add(param0U);
				b.getUnits().add(returnU);
			}
			
			System.out.println("validation:" + b);
			b.validate();
		}	
		
		return sm;
	}
	
	public static void mockConstructor(SootClass sc)
	{
		SootMethod sm = null;
		
		//Without parameters
		String methodSubSignature = "void <init>()";
		
		try
		{
			sm = sc.getMethod(methodSubSignature);
		}
		catch (Exception ex)
		{
			sm = null;
		}
		
		int m = Modifier.PUBLIC;
		
		if (null == sm)
		{
			sm = new SootMethod("<init>", new ArrayList<Type>(), VoidType.v(), m);
			sc.addMethod(sm);
			
			//Add body
			JimpleBody b = Jimple.v().newBody(sm);
	        sm.setActiveBody(b);
			{
				b.insertIdentityStmts();
				b.getUnits().add(Jimple.v().newReturnVoidStmt());
			}
			
			System.out.println("validation:" + b);
			b.validate();
		}
		
		//Static init
		/*
		methodSubSignature = "void <clinit>()";
		
		try
		{
			sm = sc.getMethod(methodSubSignature);
		}
		catch (Exception ex)
		{
			sm = null;
		}
		
		if (null == sm)
		{
			sm = new SootMethod("<clinit>", new ArrayList<Type>(), VoidType.v(), m | Modifier.STATIC);
			sc.addMethod(sm);
			
			//Add body
			JimpleBody b = Jimple.v().newBody(sm);
	        sm.setActiveBody(b);
			{
				b.getUnits().add(Jimple.v().newReturnVoidStmt());
			}
			
			b.validate();
			System.out.println("validation:" + b);
		}
		*/
		
		//With parameter
		methodSubSignature = "void <init>(java.lang.Object[])";
		
		try
		{
			sm = sc.getMethod(methodSubSignature);
		}
		catch (Exception ex)
		{
			sm = null;
		}
		
		
		if (null == sm)
		{
			List<Type> paramTypes = new ArrayList<Type>();
			paramTypes.add(ArrayType.v(RefType.v("java.lang.Object"), 1));
			
			sm = new SootMethod("<init>", paramTypes, VoidType.v(), m);
			sc.addMethod(sm);
			
			//Add body
			JimpleBody b = Jimple.v().newBody(sm);
	        sm.setActiveBody(b);
	        //LocalGenerator lg = new LocalGenerator(b);
			{
				b.insertIdentityStmts();
				
				//Local rtLoc = lg.generateLocal(RefType.v("java.lang.Object"));
				//Local param0Loc = lg.generateLocal(ArrayType.v(RefType.v("java.lang.Object"), 1));
				//Unit param0U = Jimple.v().newIdentityStmt(rtLoc, Jimple.v().newParameterRef(ArrayType.v(RefType.v("java.lang.Object"), 1), 0));
				//Unit assignU = Jimple.v().newAssignStmt(rtLoc, Jimple.v().newCastExpr(param0Loc, RefType.v("java.lang.Object")));
				//b.getUnits().add(param0U);
				//b.getUnits().add(assignU);
				b.getUnits().add(Jimple.v().newReturnVoidStmt());
			}
			
			System.out.println("validation:" + b);
			b.validate();
		}
	}
}