package emn.fr.ascola.extractor;

import java.io.File;
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.IField;
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.jdt.core.dom.IVariableBinding;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;

import org.eclipse.swt.widgets.DirectoryDialog;
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
  * 
  * TODO restructurer ce caca !
 */
public class ASTActionDelegate implements IWorkbenchWindowActionDelegate {

	/**
	 * We cache the parent window object to create the message dialog.
	 */
	protected IWorkbenchWindow window;
	
	public static final String subtyping = "Subtyping";
	public static final String composition = "Composition";
	public static final String communication = "Communication";
	
	/**
	 * For decoration.
	 */
	public static final String separation = "======== Copyright jroyer@emn.fr ======================================\n\n";
	
	/**
	 * Associated shell.
	 */
	public static  Shell shell;
	
	// local java project reference
	protected IJavaProject javaProject;
	// project in the workspace
	protected IProject project;
	// project name in the workspace
	protected String repo_name;
	// package compilation units
	protected Vector<ICompilationUnit> units;
	// types of interest
	protected 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>();

	/**
	 * To store provided interface.
	 */
	public static Provided provided;
	
	/**
	 * To store static final constants.
	 */
	public static ProvidedConstant providedconstant;
	
	/**
	 * Simple display.
	 */
	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(ASTActionDelegate.shell.getDisplay(), "Running Analysis ... ");
		this.view.open();
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		// mieux choix d'un rep a la racine
		// TODO et sur une partie du projet ?
		//TODO voir le cancel controler racine du projet !
		DirectoryDialog dialog = new DirectoryDialog(shell);
		dialog.setMessage("Choose a project name in the current workspace!");
	    dialog.setFilterPath(""); 
	    this.repo_name = dialog.open();
	    // find the Java project in the current workspace
	    // need the last segment
	    //System.out.println("repo_name " + this.repo_name );
	    int ind = this.repo_name.lastIndexOf(File.separator);
	    if (ind != -1) {
	    	this.repo_name = this.repo_name.substring(ind + 1);
	    }
	    //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);
					// --------------------------------
					// add superclass information in the MyIType instance
					// and subinterfaces information
					this.addSubSub();
					// --------------------------------
					// look at some methods to R1
					for (IType it : this.typesOfInterest) {
						this.implementR1(it);
					}
					// --------------------------------
					// propagate the DATA indicator in subclasses of the project
					ASTActionDelegate.table.propagateDATA();
					this.view
							.append("TABLE of types of interest after propagate \n");
					this.view.append(ASTActionDelegate.table.toString());
					this.view.append(ASTActionDelegate.separation);
					// implement P3 extract structure --------------------
					this.implementP3();
					this.view.append("TABLE of types of interest after P3 \n");
					this.view.append(ASTActionDelegate.table.toString());
					this.view.append(ASTActionDelegate.separation);
					// -------------------------
					// add parents composition in the table
					this.addParentLinks();
					// -------------------------
					// implement P4 extract communications
					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 and constants\n");
					this.implementP5provided();
					this.view.append(ASTActionDelegate.provided.toString());
					this.view.append(ASTActionDelegate.providedconstant
							.toString());
					this.view.append(ASTActionDelegate.separation);
					// implement P5 required -------------------------
					this.view.append("Required interfaces  \n");
					// this.implementP5required();
					this.view.append(ASTActionDelegate.separation);
					// control public and default-package fields
					// -------------------------
					this.view.append(" have public fields  \n");
					this.view.append(ASTActionDelegate.table
							.checkPublicFields().toString()
							+ "\n");
					this.view.append(ASTActionDelegate.separation);
					// control cyclic structure -------------------------
					this.view.append(" have cyclic structure  \n");
					this.view.append(ASTActionDelegate.table.checkCycle()
							.toString()
							+ "\n");
					this.view.append(ASTActionDelegate.separation);
					// check required provided -----------------------
					this.view.append(" check that required are provided  \n");
					ASTActionDelegate.communications.checkRequiredProvided();
					this.view.append(ASTActionDelegate.separation);
					// check structure communications ------------
					this.view.append(ASTActionDelegate.separation);
					// resolve undefined interfaces --------------
					ASTActionDelegate.table.setUndefinedInterfaces();
					// analyse the boundaries --------------------
					this.boundaryAnalysis();
				}
			} 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);
		}
		// final presentation with additional infos
		this.displayFinalTable();
		//MessageDialog.openWarning(window.getShell(), "FINISHED", "");
	}

	/**
	 * Get all the compilation unit corresponding to Java resources in
	 * the current Java project. 
	 * That means classes and interfaces (.java)
	 * @return a vector of compilation unit
	 */
	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()) {
					// 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()) {
							// search for compilation unit
							ICompilationUnit [] tmp = pf.getCompilationUnits();
							for (int k = 0; k < tmp.length; k++) {
								ICompilationUnit cu = tmp[k];
								icu.add(cu);
							}
						}
					}
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		return icu;
	}
	//--------------------------------------------------------------
	/**
	 * Acces to the main public types declared in the compilation units.
	 * Get all the inner types
	 * @return a vector of IType
	 */
	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 all types inside the CU
				//IType [] alls = cu.getAllTypes();
				// only the top-level ones
				IType [] alls = cu.getTypes();
				// normally there is only one main type
				// compilation unit is nammed (alls[j].getElementName() + ".java")
				for (IType ity : alls) {
					//System.out.println(" vu ity " + ity.getFullyQualifiedName());
					// TODO eliminate Exception or other strange things ?
					// consider only Interfaces and classes
					if (ity.isClass() || ity.isInterface()) {
						vt.add(ity);
					}
				}
			} 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
	 * @param it
	 */
	public void implementR1(IType it) {
		//String fullname = it.getFullyQualifiedName();
		//Vector<IMethod> vec = new Vector<IMethod>();
		//System.out.println(" implementR1 for " + fullname);
		try {
			//------------------------
			// look at  the methods,constructors and static
			IMethod [] ims = it.getMethods();
			for (IMethod im : ims) {
				// not a constructor AND not a main
				if (!im.isConstructor() && !im.isMainMethod()) {
					// the signature of im 
					String sig = im.getSignature();
					//return type  
					String tname = Signature.toString(Signature.getReturnType(sig));
					String fname = Utility.buildFullName(tname, it.resolveType(tname));
					ASTActionDelegate.table.setDataType(fname);
					// get the parameter types names
					String [] ptypes = Signature.getParameterTypes(sig);
					for (int j = 0; j < ptypes.length; j++) {
						tname = Signature.toString(ptypes[j]);
						fname = Utility.buildFullName(tname, it.resolveType(tname));
						//System.out.println(" R1:  " + fname + " is a data type since " + im);
						// add data type flag
						ASTActionDelegate.table.setDataType(fname);
					}
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
	}
	//----------------------------------------
	/**
	 * Analyse types and add subclasses and subinterfaces information.
	 */
	public void addSubSub() {
		for (IType it : this.typesOfInterest) {
			// System.out.println("Analyse du itype " + it);
			String fname = it.getFullyQualifiedName();
			MyIType mit = ASTActionDelegate.table.get(fname).getIbind();
			try {
				// set the nature of the itype
				mit.setNature(it.isClass());
				// only one super class
				String supername = it.getSuperclassName();
				// if no explicit extends it is "Object"
				if (supername == null) {
					supername = "Object";
				}
				// String[][] res = it.resolveType(supername);
				String sup = Utility.buildFullName(supername, it
						.resolveType(supername));
				// add sup in the table
				mit.setSupername(sup);
				// add references to subclasses if sup is of
				// interest
				if (ASTActionDelegate.table.isOfInterest(sup)) {
					MyIType mits = ASTActionDelegate.table.get(sup).getIbind();
					mits.addSubclass(fname);
				}
				// ---------------------------------
				// may be several super interfaces
				// store only from it to sub interfaces
				String[] lsup = it.getSuperInterfaceNames();
				for (String iname : lsup) {
					// System.out.println("vu subinterface " +
					// iname);
					String sub = Utility.buildFullName(iname, it
							.resolveType(iname));
					// System.out.println(" sub " + sub);
					if (ASTActionDelegate.table.isOfInterest(sub)) {
						MyIType mits = ASTActionDelegate.table.get(sub)
								.getIbind();
						mits.addSubinterface(fname);
					}
				}
			} catch (JavaModelException e) {
				e.printStackTrace();
			}
		}
	}

	// -----------------------------------------------------------------
	/**
	 * Visit all the type of interest and try
	 * to extract the structure from classes.
	 */
	public void implementP3() {
		// visit  all the types but interfaces
		// parse and visit the package units and the types 
		for (IType it : this.typesOfInterest) {
			try {
				// do not consider interface as defining a concrete structure
				if (!it.isInterface()) {
					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);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	// -----------------------------------------------------------------
	/**
	 * After structure computation add the parent component link
	 * to each component type (not needed for interface)
	 */
	public void addParentLinks() {
		for (IType it : this.typesOfInterest) {
			String fullname = it.getFullyQualifiedName();
			Information info = ASTActionDelegate.table.get(fullname);
			MyIType ity = info.getIbind();
			if (info.isComponentType()) {
				//System.out.println("look fullname " + fullname);
				Fields struc = ity.getStructure();
				for (IVariableBinding iv : struc) {
					String subname = iv.getType().getBinaryName();
					//System.out.println("add " + subname);
					// add fullname as parent of subname
					ASTActionDelegate.table.get(subname).addParent(fullname);
				}
			}
		}
	}

	// -----------------------------------------------------------------
	/**
	 * Find the main methods and tagged ROOT in table.
	 */
	public void findRoots() {
		// look for the main method and tagged ROOT
		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 the types and extract communications.
	 */
	public void implementP4() {
		ASTActionDelegate.communications = new Communications();
		for (IType it : this.typesOfInterest) {
			//System.out.println("implementP4 for " + it.getFullyQualifiedName());
			GenericASTParser ast = new GenericASTParser(new ExtractCommunications());
			ICompilationUnit ic = it.getCompilationUnit();
			CompilationUnit cu = ast.parse(ic);
			ast.run(cu);
		}
	}
	// -----------------------------------------------------------------
	public void implementP5required() {
		//TODO look at communications
		// and build union for each types
	}
	// -----------------------------------------------------------------
	/**
	 * Visit the types and get provided interfaces.
	 * add static final public and default package
	 */
	public void implementP5provided() {
		// init table for provided and constants
		ASTActionDelegate.provided = new Provided();
		ASTActionDelegate.providedconstant = new ProvidedConstant();
		// it is sufficient to extract from the communications
		// the set of signature calls
		for (IType it : this.typesOfInterest) {
			String fullname = it.getFullyQualifiedName();
			try {
				Vector<IMethod> vec = new Vector<IMethod>();
				IMethod [] ims = it.getMethods();
				for (IMethod im: ims) {
					// discard main methods from provided
					if (!im.isMainMethod()) {
						// --------------------------
						// implement R5 is done here for all public or default package
						// capture the flags!
						int fg = im.getFlags();
						// filter public and default package methods
						//System.out.println("implementR1 im ? " + im);
						if (!Flags.isPrivate(fg) || !Flags.isProtected(fg)) {
							vec.add(im);
						}
					}
				}
				// store provided information
				ASTActionDelegate.provided.put(fullname, vec);
				// --------------------------
				// look for static final (public or default-package) fields
				Vector<IField> fields = new Vector<IField>();
				IField [] ifs = it.getFields();
				for (IField f : ifs) {
					int flag = f.getFlags();
					if (Flags.isStatic(flag) && Flags.isFinal(flag) &&
							(Flags.isPublic(flag) || Flags.isPackageDefault(flag))) {
						//System.out.println("implementR1 f ? " + f);
						fields.add(f);
					}
				}
				// store provided information
				ASTActionDelegate.providedconstant.put(fullname, fields);
			}
			catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	// -----------------------------------------------------------------
	/**
	 * Analyse instanciation in man and constructors
	 * to reveal new connections between types.
	 * TODO
	 */
	public void analyseMainAndConstructors () {
		
	}
	// -----------------------------------------------------------------
	
	/**
	 * Analyse component type to check boundary infraction.
	 * Necessary condition but not sufficient
	 * TODO sous-typage ?
	 */
	public void boundaryAnalysis () {
		// view for information
		DisplayText anotherview = new DisplayText(ASTActionDelegate.shell.getDisplay(), "Boundary Analysis");
		anotherview.setLocation(200, 200);
		anotherview.open();
		// search the component types
		for (IType it : this.typesOfInterest) {
			String fullname = it.getFullyQualifiedName();
			Information info = ASTActionDelegate.table.get(fullname);
			// if not already viewed
			MyIType ity = info.getIbind();
			if (info.isComponentType()) {
				anotherview.append(ASTActionDelegate.separation);
				anotherview.append(" boundaryAnalysis of " + fullname + "\n\n");
				// look at the composite structure of this node
				Fields struc = ity.getStructure();
				// root composite communications
				InfoCom msgs = ASTActionDelegate.communications.getCom(fullname);
				// could be null if no com
				if (msgs != null) {
					// either to inner subcomponents or inside the parent
					// composite
					for (String rec : msgs.keySet()) {
						// should forget self messages
						if (!rec.equals(fullname)) {
							Information inforec = ASTActionDelegate.table.get(rec);
							// rec is a component type
							if (inforec.isComponentType()) {
								// either message to inner or to super
								// composite
								if (!(struc.isInner(rec))
										|| this.superComposite(rec, inforec.getParents())) {
										// we could list the messages
									anotherview.append(" Communications: to " + rec
											+ " not in boundary of " + fullname + "\n");
								}
							}
						}
					}
				}
			}
		}
	} //-- end boundary analysis
	
	/**
	 * Check if the receiver is in all the parent composites.
	 * @param rec
	 * @return
	 */
	public boolean superComposite(String rec, Vector<String> parents) {
		boolean result = true; 
		for (String p : parents) {
			//System.out.println("superComposite for p " + p);
			// rec is either p or isInner to p
			if (p.equals(rec)) {
				// get the structure, p is a component type
				Fields struc = ASTActionDelegate.table.getStructure(p);
				result = result && struc.isInner(rec);
			} else {
				return false;
			}
		}
		return result;
	}
	
	// -----------------------------------------------------------------

	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	
	// -----------------------------------------------------------------
	public void displayFinalTable () {
	DisplayText anotherview = new DisplayText(ASTActionDelegate.shell.getDisplay(), "Final table");
		anotherview.setLocation(100, 100);
		anotherview.open();
		// final status of the table -----------------
		anotherview.append(ASTActionDelegate.table.toString());
		anotherview.append(ASTActionDelegate.separation);
		// -----
		anotherview.append(" Nombre de types : " + this.typesOfInterest.size()
				+ " Nombre de composants : "
				+ ASTActionDelegate.table.getComponentNumber()
				+ " Nombre de data type : "
				+ ASTActionDelegate.table.getDatatypeNumber() + "\n");
		anotherview.append(ASTActionDelegate.separation);
		anotherview.append(" Liste avec cycle "
				+ ASTActionDelegate.table.getCycles() + "\n");
		anotherview.append(ASTActionDelegate.separation);
		anotherview.append(" Liste undefined "
				+ ASTActionDelegate.table.getUndefined() + "\n");
		anotherview.append(ASTActionDelegate.separation);
	}
	// -----------------------------------------------------------------

	/**
	 * 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;
	}

}
