package testjdt3;

import java.util.HashMap;
import java.util.Vector;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.window.Window;

import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;

/**
 * Experiment with JDT to parse and extract component boundaries.
 * @see IWorkbenchWindowActionDelegate
  * jroyer@emn.fr production
 */
public class ASTActionDelegate implements IWorkbenchWindowActionDelegate {

	/**
	 * We cache the parent window object to create the message dialog.
	 */
	protected IWorkbenchWindow window;
	
	/**
	 * For decoration.
	 */
	public static final String separation = "======== Copyright jroyer@emn.fr ======================================\n\n";
	
	/**
	 * Associated shell.
	 */
	public static  Shell shell;
	
	// local java project reference
	private IJavaProject javaProject;
	// project in the workspace
	private IProject project;
	// project name in the workspace
	private String repo_name;
	// package compilation units
	private Vector<ICompilationUnit> units;
	// types of interest
	private Vector<IType> typesOfInterest;
	/**
	 *  Table of information.
	 */
	public static TypesTable table;
	
	/**
	 * Table of communications.
	 */
	public static Communications communications;
	
	/**
	 * Data structure for coding the composite structure
	 */
	public static  HashMap<String, String> structure = new HashMap<String, String>();

	// look for the main method and main type
	//public static  IType maintype;
	//public static IMethod  mainmethod;

	/**
	 * To store provided interface.
	 */
	private static Provided provided;
	
	private DisplayText view;
	
	/**
	 * Default constructor.
	 */
	public ASTActionDelegate() {
		this.repo_name = "";
		this.project = null;
		this.javaProject = null;
		this.units = new Vector<ICompilationUnit>();
		this.typesOfInterest = new Vector<IType>();
	}

	/**
	 * The action has been activated. The argument of the method represents the
	 * 'real' action sitting in the workbench UI.
	 * 
	 * @see IWorkbenchWindowActionDelegate#run
	 */
	public void run(IAction action) {
		// to init the additional display
		this.view = new DisplayText(this.shell.getDisplay(), "Running Analysis TESTJDT3");
		this.view.open();
		// to input repository name
		InputDialog inputDialog = new InputDialog(this.shell,
				"Input project name must exist in workspace", "Give the name of the project", "TESTAST",
				null);
		int reponse = inputDialog.open();
		if (reponse == Window.OK) {
			this.repo_name = inputDialog.getValue();
			// find the Java project in the current workspace
			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
			//System.out.println("repo_name " + this.repo_name);
			this.project = root.getProject(this.repo_name);
			// test project existence
			if (this.project != null) {
				// open the project --------------------------------
				try {
					this.project.open(null /* IProgressMonitor */);
				} catch (CoreException e) {
					//e.printStackTrace();
					MessageDialog.openError(window.getShell(), "Problem to open it", "non existing project? " + this.repo_name);	
					return;
				}
				this.javaProject = JavaCore.create(project);
				// finding the compilation units
				if (this.javaProject != null) {
					// System.out.println("Java project " + javaProject);
					this.units = this.getUnitsOfInterest();
					// to eliminate non interesting projects
					if (this.units.size() == 0) {
						MessageDialog.openError(window.getShell(), "Empty project", "empty project " + this.repo_name);	
					} else {
						// find the types of interest
						// --------------------------------
						this.typesOfInterest = this.getTypesOfInterest();
						// initialize the table of information
						ASTActionDelegate.table = new TypesTable(this.typesOfInterest);
						this.view.append("TABLE of types of interest \n");
						this.view.append(ASTActionDelegate.table.toString());
						this.view.append(ASTActionDelegate.separation);
						// TODO add superclass information
						// memorise l'info mais IType pas defini ...
						// voir avec le default constructor
						for (IType it : this.typesOfInterest) {
							try {
								String supername = it.getSuperclassName();
								String fname = it.getFullyQualifiedName();
								System.out.print(" it " + fname + " extends " + supername);
								if (supername == null) {
									supername = "Object";
								}
								String[][] res = it.resolveType(supername);
								String sup = Utility.buildFullName(supername, it.resolveType(supername));
								System.out.println(" supername " + sup);
								// TODO
								ASTActionDelegate.table.setType(fname, new MyIType(sup));
							} catch (JavaModelException e) {
								e.printStackTrace();
							}
						}
						// look at some methods to R1
						// init table for provided
						ASTActionDelegate.provided = new Provided();
						for (IType it : this.typesOfInterest) {
							this.implementR1(it);
						}
						// implement P3 --------------------
						this.implementP3();
						this.view.append("TABLE of types of interest with information \n");
						this.view.append(ASTActionDelegate.table.toString());
						this.view.append(ASTActionDelegate.separation);
						// implement P4 -------------------------
						this.implementP4();
						this.view.append("COMMUNICATIONS \n");
						this.view.append(ASTActionDelegate.communications
								.toString());
						this.view.append(ASTActionDelegate.separation);
						// implement P5 provided -------------------------
						this.view.append("Provided interfaces \n");
						// this.implementP5provided();
						this.view.append(ASTActionDelegate.provided.toString());
						this.view.append(ASTActionDelegate.separation);
						// implement P5 required -------------------------
						this.view.append("Required interfaces  \n");
						this.implementP5required();
						MessageDialog.openWarning(window.getShell(), "FINISHED", "");
					}
				} else {
					MessageDialog.openError(window.getShell(), "No Java project", "No such Java project " + this.repo_name);	
				}
			} else {
				MessageDialog.openError(window.getShell(), "No project", "No such project " + this.repo_name);
			}
		} else {
			MessageDialog.openInformation(window.getShell(), action.getDescription(), "Aborting operation");
			return;
		}
		//this.view.dispose();
	}

	/**
	 * Get all the compilation unit corresponding to Java resources in
	 * the current Java project. 
	 * That means classes and interfaces (.java)
	 * @return
	 */
	public Vector<ICompilationUnit> getUnitsOfInterest() {
		IPackageFragmentRoot[] ipfr;
		Vector<ICompilationUnit> icu = new Vector<ICompilationUnit>();
		try {
			// acces the package root for Java resources
			ipfr = javaProject.getAllPackageFragmentRoots();
			for (int i = 0; i < ipfr.length; i++) {
				// select internal resources
				if (!ipfr[i].isExternal()) {
					//System.out.println(" ipfr:  " + i + " " + ipfr[i] + " " + ipfr[i].isArchive());
					//System.out.println(" childs  " + ipfr[i].getChildren().length);
					// get the inner packages
					IJavaElement [] ije =  ipfr[i].getChildren();
					for (int j = 0; j < ije.length; j++) {
						// cast needed here 
						IPackageFragment pf = (IPackageFragment) ije[j];
						// if there are Java resources inside
						if (pf.containsJavaResources()) {
							//System.out.println(" child =   " + j + "  " + pf.getElementName());
							// search for compilation unit
							ICompilationUnit [] tmp = pf.getCompilationUnits();
							for (int k = 0; k < tmp.length; k++) {
								ICompilationUnit cu = tmp[k];
								icu.add(cu);
								// this is the type name
								//System.out.println("             contient icu " + cu.getElementName());  
								// the package name is given by pf.getElementName()
							}
						}
					}
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		return icu;
	}
	
	/**
	 * Acces to the main public types declared in the compilation units.
	 * Simplified version get the first and single one top level
	 * @return
	 */
	public Vector<IType> getTypesOfInterest() {
		Vector<IType> vt = new Vector<IType>();
		// analyse main IType
		for (int i = 0; i < this.units.size(); i++) {
			ICompilationUnit cu = this.units.get(i);
			String name = cu.getElementName();
			try {
				// get toplevel types
				IType [] alls = cu.getTypes();
				// normally there is only one like this
				// compilation unit is nammed (alls[j].getElementName() + ".java")
				IType it = alls[0];
				// TODO eliminate Exception ?
				if (it.isClass() || it.isInterface()) {
					vt.add(it);
				}
//				} else {
//					MessageDialog.openError(window.getShell(), "ASTActionDelegate.geTypeOfInterest problem with " + it, "");
//				}
			} catch (JavaModelException e1) {
				e1.printStackTrace();
			}
		}
		this.view.append(" ASTActionDelegate found " + vt.size() + " types of interest!");
		return vt;
	}
	
	// -----------------------------------------------------------------
	/**
	 * Implementation of R1.
	 * Look at non constructor and non static method (even main)
	 * Parameter types and result type are set data types
	 * TODO revoir car resolvedType pas complet cas generic
	 * TODO probleme du superdata
	 * @param it
	 */
	public void implementR1(IType it) {
		String fullname = it.getFullyQualifiedName();
		Vector<IMethod> vec = new Vector<IMethod>();
		//System.out.println(" implementR1 for " + fullname);
		try {
			IMethod [] ims = it.getMethods();
			for (int i = 0; i < ims.length; i++) {
				IMethod im = ims[i];
				// implement R5 is done here
				// capture the flags!
				int fg = ims[i].getFlags();
				// filter public and default package methods
				if (Flags.isPublic(fg) || Flags.isPackageDefault(fg)) {
					vec.add(ims[i]);
				}
				// TODO completer les tests (static ?) possible avec flags
				if (!im.isConstructor() && !im.isMainMethod()) {
					// the signature of im 
					String sig = im.getSignature();
					// get the parameter types names
					String [] ptypes = Signature.getParameterTypes(sig);
					//return type 
					String tname = Signature.toString(Signature.getReturnType(sig));
					String fname = Utility.buildFullName(tname, it.resolveType(tname));
					ASTActionDelegate.table.setDataType(fname);
					for (int j = 0; j < ptypes.length; j++) {
						tname = Signature.toString(ptypes[j]);
						fname = Utility.buildFullName(tname, it.resolveType(tname));
						// TODO add default MyIType ?
						ASTActionDelegate.table.setDataType(fname);
					}
				}
			}
			// store provided information
			ASTActionDelegate.provided.put(fullname, vec);
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
	}


	// -----------------------------------------------------------------
	/**
	 * Visit all the type of interest and try
	 * to extract the structure.
	 * TODO see inheritance management !!!!
	 */
	public void implementP3() {
		// look for the main method
		for (IType it : this.typesOfInterest) {
			//System.out.println(" implementP3 for " + it.getFullyQualifiedName());
			IMethod[] ims;
			try {
				ims = it.getMethods();
				for (int i = 0; i < ims.length; i++) {
					IMethod im = ims[i];
					if (im.isMainMethod()) {
						//  set the root type
						ASTActionDelegate.table.setRoot(it.getFullyQualifiedName());
					}
				}
			} catch (JavaModelException e) {
				e.printStackTrace();
			}
		}
		// visit  all the types
		// parse and visit the package units and the types 
		for (IType it : this.typesOfInterest) {
			String fn = it.getFullyQualifiedName();
			// only for non data type 
			if (!ASTActionDelegate.table.isDataType(fn)) {
				//System.out.println(" extract structure for " + fn);
				GenericASTParser ast = new GenericASTParser(new ExtractStructure());
				ICompilationUnit ic = it.getCompilationUnit();
				CompilationUnit cu = ast.parse(ic);
				ast.run(cu);
			}
		}
	}
	
	// -----------------------------------------------------------------
	/**
	 * Display the structure.
	 */
	public static String displayStructure() {
		String result = "---------------------- \nStructure \n";
		for (String k : ASTActionDelegate.structure.keySet()) {
			result += k + " --> " + ASTActionDelegate.structure.get(k) + "\n";
		}
		return result + "---------------------- \n";
	}
	
	// -----------------------------------------------------------------
	/**
	 * Visit the types and extract communications.
	 */
	public void implementP4() {
		ASTActionDelegate.communications = new Communications();
		for (IType it : this.typesOfInterest) {
			GenericASTParser ast = new GenericASTParser(new ExtractCommunications());
			ICompilationUnit ic = it.getCompilationUnit();
			//System.out.println(" implementP4 for " + it.getFullyQualifiedName());
			CompilationUnit cu = ast.parse(ic);
			ast.run(cu);
		}
	}
	
	// -----------------------------------------------------------------
	/**
	 * Visit the types and get required interfaces.
	 */
	public void implementP5required() {
		// it is sufficient to extract from the communications
		// the set of signature calls
		this.view.append("look in the table communications\n");
	}
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------

	/**
	 * Selection in the workbench has been changed. We can change the state of
	 * the 'real' action here if we want, but this can only happen after the
	 * delegate has been created.
	 * @see IWorkbenchWindowActionDelegate#selectionChanged
	 */
	public void selectionChanged(IAction action, ISelection selection) {
	}

	/**
	 * We can use this method to dispose of any system resources we previously
	 * allocated.
	 * 
	 * @see IWorkbenchWindowActionDelegate#dispose
	 */
	public void dispose() {
	}

	/**
	 * We will cache window object in order to be able to provide parent shell
	 * for the message dialog.
	 * @see IWorkbenchWindowActionDelegate#init
	 */
	public void init(IWorkbenchWindow window) {
		this.window = window;
		this.shell = window.getShell();
	}

	public IWorkbenchWindow getWindow() {
		return window;
	}
}
