/* Soot - a J*va Optimization Framework
 * Copyright (C) 2004 Jennifer Lhotak
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */


package ca.mcgill.sable.soot.callgraph;
import org.eclipse.ui.*;
import org.eclipse.jface.action.*;
import org.eclipse.jface.viewers.*;
import ca.mcgill.sable.graph.*;
import ca.mcgill.sable.graph.model.*;
import org.eclipse.core.runtime.*;
import java.util.*;
import java.lang.reflect.*;
import soot.jimple.toolkits.annotation.callgraph.*;
import soot.*;
import soot.tagkit.*;
import ca.mcgill.sable.soot.interaction.*;
import ca.mcgill.sable.soot.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.plugin.*;
import soot.toolkits.graph.interaction.*;
import org.eclipse.core.resources.*;
import org.eclipse.jdt.core.*;
import org.eclipse.ui.texteditor.*;
import org.eclipse.ui.part.*;

public class CallGraphGenerator {
	
	private CallGraphInfo info;
	private Graph graph;
	private InteractionController controller;
	private ArrayList centerList;
	

	public CallGraphGenerator() {
	}
	
	public void run(){
		
		IWorkbench workbench = SootPlugin.getDefault().getWorkbench();
		IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
		IWorkbenchPage page = window.getActivePage();;
		
		try{
			if (graph == null){
				setGraph(new Graph());
				graph.setName("CallGraph");
			}
			else{
				graph.removeAllChildren();
			}
			IEditorPart part = page.openEditor(graph, "ca.mcgill.sable.graph.GraphEditor", true);
			((GraphEditor)part).setPartFactory(new CallGraphPartFactory());
			addActions((GraphEditor)part);	
			((GraphEditor)part).setMenuProvider(new CGMenuProvider(((GraphEditor)part).getGraphEditorGraphicalViewer(), ((GraphEditor)part).getGraphEditorActionRegistry(), part));
			
			buildModel();
		}
		catch (PartInitException e3){
			e3.printStackTrace();
		}
		catch (Exception e2){
			e2.printStackTrace();
		}
	}
	
	public void addActions(GraphEditor part){
		ShowCodeAction showCode = new ShowCodeAction((IWorkbenchPart)part);
		part.getGraphEditorActionRegistry().registerAction(showCode);
		part.getGraphEditorSelectionActions().add(showCode.getId());
	
		ExpandAction expand = new ExpandAction((IWorkbenchPart)part);
		part.getGraphEditorActionRegistry().registerAction(expand);
		part.getGraphEditorSelectionActions().add(expand.getId());
		
		CollapseAction collapse = new CollapseAction((IWorkbenchPart)part);
		part.getGraphEditorActionRegistry().registerAction(collapse);
		part.getGraphEditorSelectionActions().add(collapse.getId());
			
	}
	
	public void buildModel(){
		CallGraphNode cgn = new CallGraphNode();
		getGraph().addChild(cgn);
		cgn.setGenerator(this);
		cgn.setData(getInfo().getCenter());

		cgn.setExpand(false);
		makeCons(getInfo(), cgn);
			
	}
	
	private CallGraphNode getNodeForMethod(SootMethod meth){
		CallGraphNode node = null;
		Iterator it = getGraph().getChildren().iterator();
		while (it.hasNext()){
			CallGraphNode next = (CallGraphNode)it.next();
			if (next.getData().equals(meth)){
				node = next;
			}
		} 
		if (node == null){
			node = new CallGraphNode();
			getGraph().addChild(node);
			node.setData(meth);
		}
		return node;
	}
	
	private void makeCons(CallGraphInfo info, CallGraphNode center){
		Iterator it1 = info.getInputs().iterator();
		while (it1.hasNext()){
			MethInfo mInfo = (MethInfo)it1.next();
			SootMethod sm = mInfo.method();
			CallGraphNode inNode = getNodeForMethod(sm);
			inNode.setGenerator(this);
			Edge inEdge = new Edge(inNode, center);
			inEdge.setLabel(mInfo.edgeKind().name());
		}
		
		Iterator it2 = info.getOutputs().iterator();
		while (it2.hasNext()){
			MethInfo mInfo = (MethInfo)it2.next();
			SootMethod sm = mInfo.method();
			CallGraphNode outNode = getNodeForMethod(sm);
			outNode.setGenerator(this);
			Edge inEdge = new Edge(center, outNode);
			inEdge.setLabel(mInfo.edgeKind().name());
		}	
	}
	
	public void collapseGraph(CallGraphNode node){
		// need to undo (remove in and out nodes
		// who are not in center list)
		ArrayList inputsToRemove = new ArrayList();
		ArrayList outputsToRemove = new ArrayList();
		ArrayList nodesToRemove = new ArrayList();
		
		if (node.getInputs() != null){
			Iterator inIt = node.getInputs().iterator();
			while (inIt.hasNext()){
				Edge next = (Edge)inIt.next();
				CallGraphNode src = (CallGraphNode)next.getSrc();
				if (src.isLeaf()){
					inputsToRemove.add(next);
					nodesToRemove.add(src);
				}
			}
		}
		
		if (node.getOutputs() != null){
			Iterator outIt = node.getOutputs().iterator();
			while (outIt.hasNext()){
				Edge next = (Edge)outIt.next();
				CallGraphNode tgt = (CallGraphNode)next.getTgt();
				if (tgt.isLeaf()){
					outputsToRemove.add(next);
					nodesToRemove.add(tgt);
				}
			}
		}
		
		Iterator inRIt = inputsToRemove.iterator();
		while (inRIt.hasNext()){
			Edge temp = (Edge)inRIt.next();
			node.removeInput(temp);
		}
		
		Iterator outRIt = outputsToRemove.iterator();
		while (outRIt.hasNext()){
			Edge temp = (Edge)outRIt.next();
			node.removeInput(temp);
		}
		
		Iterator nodeRIt = nodesToRemove.iterator();
		while (nodeRIt.hasNext()){
			CallGraphNode temp = (CallGraphNode)nodeRIt.next();
			temp.removeAllInputs();
			temp.removeAllOutputs();
			getGraph().removeChild(temp);
		}
		
		node.setExpand(true);
	}
	
	public void expandGraph(CallGraphNode node){
		getController().setEvent(new InteractionEvent(IInteractionConstants.CALL_GRAPH_NEXT_METHOD, node.getData()));
		getController().handleEvent();
	
	}
	
	public void showInCode(CallGraphNode node){
		SootMethod meth = (SootMethod)node.getData();
		String sootClassName = meth.getDeclaringClass().getName();
		sootClassName = sootClassName.replaceAll("\\.", System.getProperty("file.separator"));
		sootClassName = sootClassName + ".java";
		String sootMethName = meth.getName();
		
		IProject [] progs = SootPlugin.getWorkspace().getRoot().getProjects();
		IResource fileToOpen = null;
		for (int i = 0; i < progs.length; i++){
			IProject project = progs[i];
			
			IJavaProject jProj = JavaCore.create(project);
			try {
	
				IPackageFragmentRoot [] roots = jProj.getAllPackageFragmentRoots();
				for (int j = 0; j < roots.length; j++){
					if (!(roots[j].getResource() instanceof IContainer)) continue;
					fileToOpen = ((IContainer)roots[j].getResource()).findMember(sootClassName);
					if (fileToOpen == null) continue;
					else break;		
				}
			}
			catch(Exception e){
			}
		
			if (fileToOpen != null) break;
		}
		
		
		
		IWorkbench workbench = SootPlugin.getDefault().getWorkbench();
		IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
		IWorkbenchPage page = window.getActivePage();;
		
		try{
			IEditorPart part = page.openEditor(new FileEditorInput((IFile)fileToOpen), org.eclipse.jdt.ui.JavaUI.ID_CU_EDITOR);
			SourceLnPosTag methTag = (SourceLnPosTag)meth.getTag(SourceLnPosTag.NAME);
			if (methTag != null){
				
				int selOffset = ((AbstractTextEditor)part).getDocumentProvider().getDocument(part.getEditorInput()).getLineOffset(methTag.startLn()-1);
			
				((AbstractTextEditor)SootPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor()).selectAndReveal(selOffset, 0);
			}
					
		}
		catch (PartInitException e3){
			e3.printStackTrace();
		}
		
		catch (Exception e2){
			e2.printStackTrace();
		}
	}
	
	public void addToGraph(Object info){
		CallGraphInfo cgInfo = (CallGraphInfo)info;
		
		SootMethod center = cgInfo.getCenter();
		
		// find the center who is already in the graph
		CallGraphNode centerNode = getNodeForMethod(cgInfo.getCenter());
		//addToCenterList(cgInfo.getCenter());
		centerNode.setExpand(false);
		// make connections to all the children
		makeCons(cgInfo, centerNode);
	}

	/**
	 * @return
	 */
	public Graph getGraph() {
		return graph;
	}

	/**
	 * @param graph
	 */
	public void setGraph(Graph graph) {
		this.graph = graph;
	}

	/**
	 * @return
	 */
	public CallGraphInfo getInfo() {
		return info;
	}

	/**
	 * @param info
	 */
	public void setInfo(CallGraphInfo info) {
		this.info = info;
	}

	/**
	 * @return
	 */
	public InteractionController getController() {
		return controller;
	}

	/**
	 * @param controller
	 */
	public void setController(InteractionController controller) {
		this.controller = controller;
	}
	
	public void addToCenterList(Object obj){
		if (getCenterList() == null){
			setCenterList(new ArrayList());
		}
		getCenterList().add(obj);
	}

	/**
	 * @return
	 */
	public ArrayList getCenterList() {
		return centerList;
	}

	/**
	 * @param list
	 */
	public void setCenterList(ArrayList list) {
		centerList = list;
	}

}
