package com.jpmorgan.tracer;

import com.sun.jdi.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
import java.util.*;

/**
 * Main class for tracing
 */
public class Tracer {

	/** Implementation as a singleton */
	protected static Tracer singleton = null;

	/** get the only instance of the singleton
	 * @return get the only instance of TraceProvider
	 */    
	public static Tracer getInstance() {
		if (singleton == null) {
			singleton = new Tracer();
		}
		return singleton;
	}

	/** resets the singleton
	 */    
	public static void resetTraceProvider() {
		singleton = new Tracer();
	}

	/** make a new Trace provider
	 * (only called by getInstance())
	 */    
	protected Tracer() {}

	/** The target virtual machine */
	protected VirtualMachine vm = null;

	/** Classes we do not want to trace */
	private String[] excludes = {"java.*", "javax.*", "sun.*", "com.sun.*"};

	/** Classes we want to trace */
	private String[] includes = {"test.*"};

	/** Are we using exclusion of specified classes or not */
	protected boolean exclusionMode = false;

	/** name of the thread to trace */
	protected String threadToTrace = "main";

	/** the last trace computed */
	protected CoverageEventHandler handler;

	/** do we want full step trace to trace */
	protected boolean stepTrace = true;

	/** Do we want to trace every step
	 * @param v Do we want to trace every step
	 */
	public void setStepTrace(boolean v) {
		stepTrace = v;	
	}

	/** Is step trace enabled
	 * @return Is step trace enabled
	 */
	public boolean getStepTrace() {
		return stepTrace;
	}
	
	/**
	 * Set the target VM to be traced
	 * @param vm the target VM
	 */
	public void setTargetVM(VirtualMachine vm) {
		this.vm = vm;
	}
	
	/**
	 * Get the target VM to be traced
	 * @return vm the target VM
	 */
	public VirtualMachine getTargetVM() {
		return vm;
	}
	
	/** Do we exclude from trace classes that are in the exclutes variable
	 * @return is exclusion mode enabled
	 */
	public boolean isExclusionMode() {
		return exclusionMode;
	}

	/** set classes to exclude from trace (if exclusionMode)
	 * @param l Vector of class names to exclude from trace
	 */
	public void setExcludes(Vector<String> l) {
		excludes = new String[l.size()];
		for (int i = 0; i<l.size(); i++) {
			excludes[i] = l.get(i);	
		}
	}
	/** set classes to include in trace (if !exclusionMode)
	 * @param l vector of class names to trace
	 */
	public void setIncludes(Vector<String> l) {
		includes = new String[l.size()];
		for (int i = 0; i<l.size(); i++) {
			includes[i] = l.get(i);	
		}
	}
	/** get classes to exclude from trace (if exclusionMode)
	 * @return classes to exclude from trace
	 */
	public Vector<String> getExcludes() {
		Vector<String> result = new Vector<String>();
		for (int i=0; i<excludes.length; ++i) 
			result.add(excludes[i]);
		return result;
	}

	/** get classes to include in trace (if !exclusionMode)
	 * @return classes to include in trace
	 */
	public Vector<String> getIncludes() {
		Vector<String> result = new Vector<String>();
		for (int i = 0; i < includes.length; ++i) 
			result.add(includes[i]);
		return result;
	}

	/** Setter for property exclusionMode.
	 * @param exclusionMode New value of property exclusionMode.
	 */
	public void setExclusionMode(boolean exclusionMode) {
		this.exclusionMode = exclusionMode;
	}

	/** Getter for property threadToTrace.
	 * @return Value of property threadToTrace.
	 */
	public java.lang.String getThreadToTrace() {
		return threadToTrace;
	}

	/** Get the list of threads names (it needs to run system)
	 * @return A vector that contains every threads names executed in the VirtualMachine to trace
	 */
	public ArrayList<String> getThreadList() {
		ArrayList<String> result = new ArrayList<String>();
		vm.setDebugTraceMode(0);
		EventRequestManager mgr = vm.eventRequestManager();
		ThreadStartRequest tdr = mgr.createThreadStartRequest();
		tdr.setSuspendPolicy(EventRequest.SUSPEND_ALL);
		tdr.enable();
		EventQueue queue = vm.eventQueue();
		vm.resume();
		while(true) {
			try {
				EventSet eventSet = queue.remove();
				EventIterator it = eventSet.eventIterator();
				Event ev;
				while (it.hasNext()) {
					ev = it.nextEvent();
					if (ev instanceof ThreadStartEvent)
						result.add(((ThreadStartEvent)ev).thread().name());
				}
				eventSet.resume();
			}
			catch (VMDisconnectedException discExc) {
				break;
			}
			catch (Exception e) {
				System.out.println("Something is going wrong ! : "+e+"\n");
				e.printStackTrace();
			}
		}
		vm = null;
		return result;
	}

	/** set the name of the thread to trace
	 * @param name the name of the thread to trace
	 */
	public void setThreadToTrace(String name) {
		threadToTrace = name;
	}

	/** generate the execution trace
	 * @return the trace object after execution of the system
	 */
	public CoverageEventHandler generateTrace() {
		handler = new CoverageEventHandler();
		vm.setDebugTraceMode(0);
		setEventRequests();
		EventQueue queue = vm.eventQueue();
		vm.resume();

		while(true) {
			try {
				EventSet eventSet = queue.remove();
				EventIterator it = eventSet.eventIterator();
				Event ev;
				while (it.hasNext()) {
					ev = it.nextEvent();
					System.out.println("DEBUG : " + ev);

					if (ev instanceof ThreadStartEvent)
						handleThreadStartEvent((ThreadStartEvent) ev);
					else if (ev instanceof MethodEntryEvent)
						handleMethodEntryEvent((MethodEntryEvent)ev);
					else if (ev instanceof MethodExitEvent) 
						handleMethodExitEvent((MethodExitEvent)ev);
					else if (ev instanceof StepEvent)
						handleStepEvent((StepEvent)ev);
					else if (ev instanceof VMDeathEvent)
						handleVMDeathEvent((VMDeathEvent)ev);
				}
				eventSet.resume();
			}
			catch (VMDisconnectedException discExc) {
				break;
			}
			catch (Exception e) {
				System.out.println("Something is going wrong ! : "+e+"\n");
				e.printStackTrace();
			}
		}
		return handler;
	}

	protected void handleVMDeathEvent(VMDeathEvent e) {
		handler.onEnd();
	}

	protected void handleMethodEntryEvent(MethodEntryEvent e) {
		if (threadToTrace.equals(e.thread().name()))
			handler.onCall(e.method());
	}

	protected void handleMethodExitEvent(MethodExitEvent e) {
		if (threadToTrace.equals(e.thread().name()))
			handler.onReturn();
	}

	protected void handleThreadStartEvent(ThreadStartEvent e) {
		if (threadToTrace.equals(e.thread().name()) && stepTrace) {
			EventRequestManager mgr = vm.eventRequestManager();
			StepRequest req = mgr.createStepRequest(e.thread(), 
					StepRequest.STEP_LINE, 
					StepRequest.STEP_INTO);
			if (exclusionMode) {
				for (int i=0; i<excludes.length; ++i) {
					req.addClassExclusionFilter(excludes[i]);
				}
			}
			else {
				for (int i=0; i<includes.length; ++i) {
					req.addClassFilter(includes[i]);
				}
			}
			req.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
			req.enable();
		}
	}

	protected void handleStepEvent(StepEvent e) {
		try {
			if (threadToTrace.equals(e.thread().name()))
				handler.onStep(e.location().sourceName(),e.location().lineNumber());
		} catch(Exception ex) { ex.printStackTrace(); }    
	}

	/**
	 * Set the requested events
	 */
	protected void setEventRequests() {
		EventRequestManager mgr = vm.eventRequestManager();

		ThreadStartRequest tdr = mgr.createThreadStartRequest();
		tdr.setSuspendPolicy(EventRequest.SUSPEND_ALL);
		tdr.enable();

		// trace method calls
		MethodEntryRequest menr = mgr.createMethodEntryRequest();
		if (exclusionMode) {
			for (int i=0; i<excludes.length; ++i) {
				menr.addClassExclusionFilter(excludes[i]);
			}
		}
		else {
			for (int i=0; i<includes.length; ++i) {
				menr.addClassFilter(includes[i]);
			}
		}
		menr.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
		menr.enable();

		// trace method exit
		MethodExitRequest mexr = mgr.createMethodExitRequest();
		if (exclusionMode) {
			for (int i=0; i<excludes.length; ++i) {
				mexr.addClassExclusionFilter(excludes[i]);
			}
		}
		else {
			for (int i=0; i<includes.length; ++i) {
				mexr.addClassFilter(includes[i]);
			}
		}
		mexr.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
		mexr.enable();
	}
}
