package it.mice.voila2.plugin.transformer;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator;
import org.eclipse.acceleo.engine.service.AbstractAcceleoGenerator;
import org.eclipse.core.resources.FileInfoMatcherDescription;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceFilterDescription;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceDescription;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.jdt.ui.actions.OrganizeImportsAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.team.examples.filesystem.ui.FixRecordsAction;
import org.eclipse.team.examples.filesystem.ui.SynchronizeAction;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Model;

import it.mice.voila2.acceleogenerator.core.VoilaFileWalks;
import it.mice.voila2.plugin.utils.Utility;
import it.micegroup.voila2.cartridge.library.formatters.PrettierFormatter;
import it.micegroup.voila2.cartridge.library.javaservice.ConsoleUtils;
import it.micegroup.voila2.cartridge.library.javaservice.FileCopyUtils;
import it.micegroup.voila2.cartridge.library.javaservice.GeneralUtils;
import it.micegroup.voila2.cartridge.library.javaservice.ModelUtils;
import it.micegroup.voila2.cartridge.library.javaservice.SeverityLevels;

public class BasicTransformer implements IRunnableWithProgress {

	private boolean formatFE = true;
	private IProject modelProject;
	private IProject rootProject;
	private Object selectedObject;
	private ClassLoader backupClassLoader;
	private String baseRelativeDir = "/.syncFolder/base/";
	private ClassLoader newClassLoader;
	private MessageConsoleStream out = null;
	private boolean isMainMs;
	private static Collection<IProject> generatedProjects = new ArrayList<IProject>();

	public BasicTransformer(Object selObject, IProject rootProject) {
		super();
		generatedProjects = new ArrayList<IProject>();
		modelProject = rootProject;

		try {
			if (rootProject.getReferencedProjects().length > 0) {
				rootProject = getRootProjectFromModelProject(rootProject);
			}
		} catch (CoreException e) {
			e.printStackTrace();
		}

		this.rootProject = rootProject;
		this.selectedObject = selObject;
	}

	private IProject getRootProjectFromModelProject(IProject rootProject) throws CoreException {

		int modelIndex = rootProject.getName().indexOf("-model");
		IProject result = ResourcesPlugin.getWorkspace().getRoot()
				.getProject("/" + rootProject.getName().substring(0, modelIndex));

		// VN In questo modo va in errore se ci sono altri progetti caricati nel WS.
		// IProject result = null;
//		int len = 0;
//		for (IProject prj : rootProject.getReferencedProjects()) {
//			if (prj.getLocation().toOSString().length() > len) {
//				result = prj;
//				len = prj.getLocation().toOSString().length();
//			}
//		}
		return result;
	}

	public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
		String baseDir = rootProject.getLocation() + "/";
		String remoteRelativeDir = "/.syncFolder/remote/";
		String remoteDir = baseDir + remoteRelativeDir;

		monitor.beginTask("Generating application...", IProgressMonitor.UNKNOWN);

		try {
			backupClassLoader = null;
			// rootProject.close(monitor);
//				rootProject.open(monitor);
//				enableAutoBuild(false);
			// rootProject.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
//				
//				Thread.sleep(1000);
//
//				IJobManager jobManager = Job.getJobManager();
//				jobManager.join(ResourcesPlugin.FAMILY_MANUAL_BUILD, monitor);
//				jobManager.join(ResourcesPlugin.FAMILY_AUTO_BUILD, monitor);

			runGeneration(monitor, baseDir, remoteRelativeDir, remoteDir);
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally {
			Thread.currentThread().setContextClassLoader(backupClassLoader);
		}
	}

	private void runGeneration(IProgressMonitor monitor, String baseDir, String remoteRelativeDir, String remoteDir)
			throws IOException, Exception, PartInitException, CoreException {
		File remoteDirFile = new File(remoteDir);

		Collection<IProject> existingProjects = new ArrayList<IProject>();

		System.gc();
		FileUtils.deleteDirectory(remoteDirFile);

		FileUtils.forceMkdir(new File(remoteDir));

		System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.Log4JLogger");
		backupClassLoader = instrumentClassLoader(rootProject);

		LogManager.resetConfiguration();
//		PropertyConfigurator.configure(modelProject.getLocation() + "/target/classes/voila2.log4j.properties");
		PropertyConfigurator.configure(modelProject.getLocation() + "/src/main/resources/voila2.log4j.properties");
		out = Utility.setupConsole(null);

		Utility.showSystemProperties();

		monitor.subTask("Voilà model transformation in progress...");

		Model model = ((Element) selectedObject).getModel();
		isMainMs = ModelUtils.isMainMS(model);
		Thread.currentThread().setContextClassLoader(newClassLoader);
		List<String> arguments = new ArrayList<String>();
		AbstractAcceleoGenerator mainTransformer = null;
		mainTransformer = createMainGenerator(rootProject, remoteDir, model, arguments, false, monitor);

		try {
			GeneralUtils.setupProps(mainTransformer.getProperties(), remoteDir, baseDir);
		} catch (NullPointerException ne) {
			ne.printStackTrace();
			buildModelProject(monitor);
			GeneralUtils.setupProps(mainTransformer.getProperties(), remoteDir, baseDir);
		}

		mainTransformer.doGenerate(new BasicMonitor());

		refreshProjects(monitor, out, remoteDir, remoteRelativeDir, false);

		List<IProject> allProjects = new ArrayList<>();
		allProjects.add(rootProject);

		existingProjects.addAll(Arrays.asList(rootProject.getReferencingProjects()));

		// ---//
		// ---//

		// Front-End GENERATION
		if (isMainMs) {
			ConsoleUtils.consoleMessage(SeverityLevels.INFO, "GENERAZIONE FRONT-END");

			if (rootProject.getReferencedProjects().length <= 0) {
				ConsoleUtils.consoleMessage(SeverityLevels.ERROR, "Non ci sono progetti referenziati per "
						+ rootProject.getName()
						+ ". Probabilmente c'è qualche problema con i file .project del microservizio root e/o model");
				return;
			}

			String mainRootProject;
			try {
				mainRootProject = rootProject.getReferencedProjects()[0].getLocation().toOSString();
			} catch (NullPointerException ne) {
				ConsoleUtils.consoleMessage(SeverityLevels.ERROR,
						"Problema nel recupero della location. Verrà effettuato un nuovo tentativo");
				mainRootProject = rootProject.getReferencedProjects()[0].getRawLocation().toOSString();
			}

			String mainRootProjectName = rootProject.getReferencedProjects()[0].getName();
			remoteDir = mainRootProject + "/modules/" + mainRootProjectName + "-fe/src/app/pages/"
					+ rootProject.getName() + "-fe" + remoteRelativeDir;
			remoteDirFile = new File(remoteDir);
			baseDir = mainRootProject + "/modules/" + mainRootProjectName + "-fe/src/app/pages/" + rootProject.getName()
					+ "-fe";

			System.gc();
			FileUtils.deleteDirectory(remoteDirFile);

			FileUtils.forceMkdir(new File(remoteDir));
			System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.Log4JLogger");
			backupClassLoader = instrumentClassLoader(rootProject);

			LogManager.resetConfiguration();
//			PropertyConfigurator.configure(modelProject.getLocation() + "/target/classes/voila2.log4j.properties");
			PropertyConfigurator.configure(modelProject.getLocation() + "/src/main/resources/voila2.log4j.properties");
			out = Utility.setupConsole(null);

			Utility.showSystemProperties();

			monitor.subTask("Voilà model transformation in progress...");

			AbstractAcceleoGenerator mainTransformer_fe = createMainGenerator(rootProject, remoteDir, model, arguments,
					true, monitor);
			GeneralUtils.setupProps(mainTransformer_fe.getProperties(), remoteDir, baseDir);

			// Generazione
			mainTransformer_fe.doGenerate(new BasicMonitor());

			// Refresh Project
			refreshProjects(monitor, out, remoteDir, remoteRelativeDir, true);

			IProject feProj = ResourcesPlugin.getWorkspace().getRoot().getProject("/" + rootProject.getName() + "-fe");
			allProjects.add(feProj);

			// Remove project removed by the last generation since the sync does not
			// act on entirely deleted projects.

			existingProjects.addAll(Arrays.asList(feProj.getReferencingProjects()));
		}

		// VN Rimozione Filtri x sincronizzazione
		Collection<IProject> projectsToFilter = new ArrayList<IProject>();
		projectsToFilter.add(rootProject);
		projectsToFilter.add(ResourcesPlugin.getWorkspace().getRoot().getProject("/" + rootProject.getName() + "-be"));
		for (IProject project : generatedProjects) {
			if (!project.getName().endsWith("-core") && !project.getName().endsWith("-impl")
					&& !project.getName().endsWith("-web") && !project.getName().endsWith("-model")) {
				projectsToFilter.add(project);
			}
		}

		for (IProject project : projectsToFilter) {
			for (IResourceFilterDescription filter : project.getFilters()) {
				filter.delete(IResource.BACKGROUND_REFRESH, new NullProgressMonitor());
			}
		}

		// VN Riabilito Filtro node_modules
		enableNodeModulesFilter(monitor);

		// Salvataggio progetti da filtrare post-sync
		StaticResources.setProjectsToFilter(projectsToFilter);

		Display.getDefault().syncExec(new Runnable() {
			@Override
			public void run() {
				try {
					deleteProjectFromLastGeneration(monitor, existingProjects.toArray(IProject[]::new),
							generatedProjects);
				} catch (CoreException e) {
					e.printStackTrace();
				}
			}
		});
		doSynchronization(monitor, out, allProjects);

	}

	private void buildModelProject(IProgressMonitor monitor) throws CoreException {
		ConsoleUtils.consoleMessage(SeverityLevels.WARNING,
				"Model Project seems to be not built. A new Build will start now.");
		// Nel caso in cui non vengano trovate le properties rebuildo il progetto
		getModelProject().build(IncrementalProjectBuilder.FULL_BUILD, monitor);
	}

	/**
	 * Aggiunge dei filtri necessari ad evitare lo scanning/build dei file contenuti
	 * nella cartella node_modules del Front End
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param monitor
	 */
	private void enableNodeModulesFilter(IProgressMonitor monitor) {
		try {
			IProject feProj = ResourcesPlugin.getWorkspace().getRoot().getProject("/" + rootProject.getName() + "-fe");
			if (feProj.exists()) {
				feProj.createFilter(26, new FileInfoMatcherDescription("org.eclipse.ui.ide.multiFilter",
						"1.0-name-matches-false-false-node_modules"), IResource.BACKGROUND_REFRESH, monitor);
			}
			rootProject.createFilter(26, new FileInfoMatcherDescription("org.eclipse.ui.ide.multiFilter",
					"1.0-name-matches-false-false-node_modules"), IResource.BACKGROUND_REFRESH, monitor);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static boolean enableAutoBuild(boolean enable) throws CoreException {
		IWorkspace workspace = ResourcesPlugin.getWorkspace();
		IWorkspaceDescription desc = workspace.getDescription();
		boolean isAutoBuilding = desc.isAutoBuilding();
		if (isAutoBuilding != enable) {
			desc.setAutoBuilding(enable);
			workspace.setDescription(desc);
		}
		return isAutoBuilding;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private AbstractAcceleoGenerator createMainGenerator(IProject rootProject, String remoteDir, Model model,
			List<String> arguments, boolean feGeneration, IProgressMonitor monitor) throws IOException {
		File f = new File(remoteDir);
		try {
			Properties p = new Properties();
			p.load(modelProject.getFile("src/main/resources/properties/voila2-default.properties").getContents());
			String basePackageName = p.getProperty("base.package.name");
			try {
				formatFE = Boolean.valueOf(p.getProperty("fe.formatting"));
			} catch (Exception e) {
				ConsoleUtils.consoleMessage(SeverityLevels.ERROR, e.getStackTrace().toString());
			}

			// ComponentDiagram Generation / Backend Generation

			Class clazz = null;
			try {
				clazz = Thread.currentThread().getContextClassLoader()
						.loadClass(basePackageName + ".model.MainGenerator");
			}
			// Build model project to load MainTransformer
			catch (ClassNotFoundException cne) {

				cne.printStackTrace();
				buildModelProject(monitor);
				clazz = Thread.currentThread().getContextClassLoader()
						.loadClass(basePackageName + ".model.MainGenerator");
			}

			// Frontend Generation
			if (isMainMs && feGeneration) {
				clazz = Thread.currentThread().getContextClassLoader()
						.loadClass(basePackageName + ".model.MainGeneratorFE");
			}

			Constructor c = clazz.getConstructor(EObject.class, File.class, List.class);
			// clazz.getMethod("getProperties").invoke(c.newInstance(model, f, arguments));
			return (AbstractAcceleoGenerator) c.newInstance(model, f, arguments);
		} catch (Exception e) {
			// TODO: Raise a meaningful error diagnostic
			e.printStackTrace();
			return null; // new Main(model, f, arguments);
		}
	}

	private void raiseErrorDialog(final Exception e) {
		ConsoleUtils.consoleMessage(SeverityLevels.ERROR, e.getLocalizedMessage());

		e.printStackTrace();
		Display.getDefault().syncExec(new Runnable() {
			@Override
			public void run() {
				MessageDialog.openError(null, "JF generation error", e.getLocalizedMessage());
			}
		});
	}

	private ClassLoader instrumentClassLoader(IProject rootProject) throws Exception {
		ClassLoader backupClassLoader = Thread.currentThread().getContextClassLoader();
		ArrayList<URL> urlList = new ArrayList<URL>();

		addRootProjectToClassLoader(urlList, rootProject);
		// addStackAndArchetypeToClassLoader(urlList);
		// addProfilesToClassLoader(urlList, rootProject);

		URL[] url = urlList.toArray(new URL[] {});
		ClassLoader myClassLoader = new URLClassLoader(url, backupClassLoader);
		Thread.currentThread().setContextClassLoader(myClassLoader);

		System.out.println("Final class loader:" + myClassLoader);
		newClassLoader = Thread.currentThread().getContextClassLoader();
		return backupClassLoader;
	}

	private void addRootProjectToClassLoader(ArrayList<URL> urlList, IProject rootProject) throws Exception {
		String rootSourceDir = rootProject.getLocation() + "/target/classes/";
		addToUrlList(urlList, rootSourceDir);
		rootSourceDir = modelProject.getLocation() + "/target/classes/";
		addToUrlList(urlList, rootSourceDir);
	}

	private static void addToUrlList(ArrayList<URL> urlList, String path) throws MalformedURLException {
		URL url = new URL("file:///" + path);
		urlList.add(url);
		System.out.println("Added url " + url);
	}

	/**
	 * method to create the Thread for running the doRefresh
	 * 
	 * @param monitor
	 * @param out
	 * @param remoteDir
	 * @param remoteRelativeDir
	 * @throws CoreException
	 * @throws IOException
	 */
	private void refreshProjects(IProgressMonitor monitor, MessageConsoleStream out, String remoteDir,
			String remoteRelativeDir, boolean isFrontEnd) throws CoreException, IOException {
		Display.getDefault().syncExec(new Runnable() {
			@Override
			public void run() {
				try {
					doRefreshProjects(monitor, out, remoteDir, remoteRelativeDir, isFrontEnd);
				} catch (CoreException | IOException | URISyntaxException e) {
					throw new RuntimeException(e);
				}
			}
		});
	}

//	private List<IProject> doRefreshProjects_(IProgressMonitor monitor, MessageConsoleStream out, String remoteDir,
//			String remoteRelativeDir) throws CoreException, FileNotFoundException, IOException, URISyntaxException {
//
//		IProject[] existingProjects = /* (!remoteDir.contains(rootProject.getName()+"-fe")) ? */ rootProject
//				.getReferencingProjects() /* : new IProject[0] */;
//
//		String rootProjectName = /* (!remoteDir.contains(rootProject.getName()+"-fe")) ? */ rootProject.getName();// :
//																													// rootProject.getName()+"-fe";
//		String projectName = rootProjectName;
//
//		out.println(
//				"[" + calcDate(System.currentTimeMillis()) + "] [INFO] - Refreshing/Creating Project " + projectName);
//
//		monitor.subTask("Refreshing/Creating Project " + projectName);
//
//		out.println(
//				"[" + calcDate(System.currentTimeMillis()) + "] [INFO] - Refreshing Root-Project " + rootProjectName);
//		rootProject.refreshLocal(IResource.DEPTH_INFINITE, monitor);
//
//		List<IProject> allProjects = new ArrayList<IProject>();
//		List<IProject> createdProjects = new ArrayList<IProject>();
//		String modulesDir = getModulesDir();
//		// path cartella modules in remote .sync
//		File remotePath = new File(remoteDir + "/" + rootProjectName + "/" + modulesDir);
//		File[] children = remotePath.listFiles();
//
//		allProjects.add(rootProject);
//
//		// Chiamata alla funzione ricorsiva per generare tutti i progetti necessari
//		// dentro la cartella sync remote modules
//
//		recursive_project_generation(monitor, out, projectName, allProjects, createdProjects, modulesDir, children, 0,
//				remotePath);
//
//		monitor.worked(10);
//
//		monitor.subTask("Formatting code...");
//		PrettierFormatter formatter = new PrettierFormatter();
//		formatter.formatAll(new File(remoteDir));
//		monitor.worked(10);
//
//		// Remove .project files from newly created projects to avoid sync
//		// conflict.
//		syncBaseFolderAssets(monitor, rootProjectName, createdProjects, modulesDir);
//
//		// ####### UNIQUE #####
//		monitor.worked(10);
//
//		// TODO - POC per la sincronizzazione di più progetti contemporaneamente
//
//		/*
//		 * IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
//		 * IProject testProject = workspaceRoot.getProject("test"); // IFolder
//		 * testFolder = testProject.getFolder(new Path("ms-order"));
//		 * allProjects.add(testProject);
//		 */
//		generatedProjects.addAll(allProjects);
//		return allProjects;
//	}

	/**
	 * Method that given a root Project find all the voilaProjects and create the
	 * respective Eclipse Project and perform all syncronization actions
	 *
	 * @param monitor
	 * @param out
	 * @param remoteDir
	 * @param remoteRelativeDir
	 * @return
	 * @throws CoreException
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @throws URISyntaxException
	 */

	private List<IProject> doRefreshProjects(IProgressMonitor monitor, MessageConsoleStream out, String remoteDir,
			String remoteRelativeDir, boolean isFrontEnd)
			throws CoreException, FileNotFoundException, IOException, URISyntaxException {
		long startTime = System.currentTimeMillis();
		String rootProjectName = /* (!remoteDir.contains(rootProject.getName()+"-fe")) ? */ rootProject.getName();// :
																													// rootProject.getName()+"-fe";
		String projectName = rootProjectName;
		String projectLocation = rootProject.getLocation().toOSString();
		ConsoleUtils.consoleMessage(SeverityLevels.INFO, "Refreshing/Creating Project " + projectName);

		monitor.subTask("Refreshing/Creating Project " + projectName);

		ConsoleUtils.consoleMessage(SeverityLevels.INFO, "Refreshing Root-Project " + rootProjectName);
		rootProject.refreshLocal(IResource.DEPTH_INFINITE, monitor);

		List<IProject> allProjects = new ArrayList<IProject>();
		List<IProject> createdProjects = new ArrayList<IProject>();

		List<java.nio.file.Path> voilaPaths = VoilaFileWalks.findByFileExtension(projectLocation, "voilaProject");

		List<String> voilaProjects = new ArrayList<String>();

		for (java.nio.file.Path path : voilaPaths) {
			String voilaProj = path.normalize().toAbsolutePath().toString();
			voilaProjects.add(voilaProj);
		}

		List<String> voilaProjFolders = new ArrayList<String>();
		// Obtain the dir(s) from the file path(s) => voilaDir = project to be created
		for (String voilaFilePath : voilaProjects) {

			String voilaFolder = voilaFilePath.substring(0, voilaFilePath.lastIndexOf('\\'));

			// First generation : voilaproj found only in the syncFolder/remote , we change
			// the paths
			// so they match the actual output location (that is the rootProject Location
			// plus the relative path from the root the actual project)
			// ALSO we ignore the voilaFolder that refers the syncFolder Base
			if (!voilaFolder.contains(FilenameUtils.separatorsToSystem(baseRelativeDir))
					&& voilaFolder.contains(FilenameUtils.separatorsToSystem(remoteRelativeDir))) {
				voilaFolder = voilaFolder.replace(FilenameUtils.separatorsToSystem(projectName + remoteRelativeDir),
						"");
				voilaProjFolders.add(voilaFolder);
			}
		}

		if (!isFrontEnd) {
			for (String voilaFolder : voilaProjFolders) {
				createProjects(monitor, out, allProjects, createdProjects, voilaFolder);
			}
		}
		monitor.worked(10);

		// Formattazione Prettier Front End
		if (isFrontEnd) {
			monitor.subTask("Formatting Front end code...");
			if (formatFE) {
				PrettierFormatter formatter = new PrettierFormatter();
				formatter.formatAll(new File(remoteDir));
			}
			monitor.worked(10);
		}

		// Remove .project files from newly created projects to avoid sync
		// conflict.
		String modulesDir = getModulesDir();
		syncBaseFolderAssets(monitor, rootProjectName, createdProjects, modulesDir);

		// ####### UNIQUE #####
		monitor.worked(10);

		/*
		 * IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
		 * IProject testProject = workspaceRoot.getProject("test"); // IFolder
		 * testFolder = testProject.getFolder(new Path("ms-order"));
		 * allProjects.add(testProject);
		 */
		// Condizionato altrimenti riempi il vettore col doppio degli elementi uguali
		if (!isFrontEnd) {
			generatedProjects.addAll(allProjects);
		}
		System.out.println("Tempo per la refreshProj: " + (System.currentTimeMillis() - startTime));
		return allProjects;
	}

	/**
	 * Method extracted by doRefresh to syncronize
	 * 
	 * @param monitor
	 * @param out
	 * @param allProjects
	 * @throws IOException
	 */

	private void doSynchronization(IProgressMonitor monitor, MessageConsoleStream out, List<IProject> allProjects)
			throws IOException {
		long startTime = System.currentTimeMillis();
		monitor.subTask("Refresh Base Bytes...");
		Display.getDefault().syncExec(new Runnable() {
			@Override
			public void run() {
				try {
					refreshBaseBytes(monitor, out, allProjects);

//					monitor.worked(10);

//					out.println("[" + calcDate(System.currentTimeMillis()) + "] [INFO] - All done !!!");
					System.out.println("Tempo per la sync: " + (System.currentTimeMillis() - startTime));
					ConsoleUtils.consoleMessage(SeverityLevels.INFO, "All done !!!");
//					out.println("------------------------------------------------------------------------");

					// Run the MDD generation synchronizer merger action.
					SynchronizeAction action = new SynchronizeAction();
					IStructuredSelection selProjects = new StructuredSelection(allProjects);
					action.run(null, selProjects);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});

	}

	/**
	 * funzione estratta dalla doRefreshProjects to remove .project to avoid sync
	 * conflicts
	 * 
	 * @author Simone Punzo <simone.punzo@micegroup.it>
	 * @param monitor
	 * @param rootProjectName
	 * @param createdProjects
	 * @param modulesDir
	 * @throws CoreException
	 * @throws FileNotFoundException
	 */

	private void syncBaseFolderAssets(IProgressMonitor monitor, String rootProjectName, List<IProject> createdProjects,
			String modulesDir) throws CoreException, IOException {

		String rootBaseDir = rootProject.getLocation().toString();
		String syncFolderBaseDir = rootBaseDir + "/.syncFolder/base/" + rootProjectName;
		File baseFolderFile = FileUtils.getFile(syncFolderBaseDir);
//		IFolder baseDir = rootProject.getFolder(syncFolderBaseDir);
		if (!baseFolderFile.exists()) {
			FileFilter ff = new FileFilter() {
				@Override
				public boolean accept(File pathname) {
					if (pathname.getAbsolutePath().contains(".syncFolder")) {
						return false;
					}
					File remoteFile = FileUtils
							.getFile(pathname.getAbsolutePath().replace("\\" + rootProjectName + "\\",
									"\\" + rootProjectName + "\\.syncFolder\\remote\\" + rootProjectName + "\\"));
					if (!remoteFile.exists()) {
						return false;
					}
					return true;
				}
			};
			FileUtils.copyDirectory(new File(rootBaseDir), baseFolderFile, ff, true);
		} else if (createdProjects.size() > 0) {
			// try {
			// Thread.sleep(3000);
			// } catch (InterruptedException e) {
			// }
			Iterator<IProject> i = createdProjects.iterator();
			while (i.hasNext()) {
				IProject currProject = i.next();
				IResource localDotProjectResource = currProject.findMember(".project");
				String basePathStr = localDotProjectResource.getLocation().toString()
						.replace(rootProject.getLocation().toString(), syncFolderBaseDir);
				IPath basePath = new Path(basePathStr);
				IFile baseProjectFile = rootProject.getFile(basePath.makeRelativeTo(rootProject.getLocation()));
				prepare((IFolder) baseProjectFile.getParent());
				File f = localDotProjectResource.getLocation().toFile();
				if (f.exists()) {
					baseProjectFile.delete(true, monitor);
				}
				FileInputStream fis = new FileInputStream(f);
				FileOutputStream fos = new FileOutputStream(baseProjectFile.getLocation().toOSString());
				fos.write(fis.readAllBytes());
				fos.close();
//				baseProjectFile.create(fis, true, monitor);
				// localDotProjectResource.delete(true, monitor);
			}
		}
//		for (IProject iProject : rootProject.getReferencingProjects()) {
//			IFile dotProject = iProject.getFile(".project");
//			dotProject.touch(monitor);
//			iProject.close(monitor);
//			iProject.open(monitor);
//			iProject.refreshLocal( IResource.DEPTH_INFINITE, null );
//		}
	}

	/**
	 * Funzione estratta dalla doRefreshProjects per cancellare progetti visto che
	 * il sync non agisce su progetti completamente cancellati
	 * 
	 * @author Simone Punzo <simone.punzo@micegroup.it>
	 * @param monitor
	 * @param existingProjects Progetti che fanno riferimento al rootProject cioè
	 *                         che hanno il rootProject tra i projects
	 * @param rootProjectName
	 * @param allProjects      Progetti generati
	 * @param modulesDir
	 * @throws CoreException
	 */
	private void deleteProjectFromLastGeneration(IProgressMonitor monitor, IProject[] existingProjects,
			/* String rootProjectName, */ Collection<IProject> allProjects) throws CoreException {

		try {
			for (int i = 0; i < existingProjects.length; i++) {
				Iterator<IProject> iter = allProjects.iterator();
				boolean projectFound = false;
				while (iter.hasNext()) {
					IProject element = iter.next();
					if (existingProjects[i] == element) {
						projectFound = true;
					}
				}
				if (!projectFound && existingProjects[i].findMember(".voilaIgnore") == null) {
					boolean confirm = MessageDialog.openConfirm(null, "Delete project confirmation",
							"The generation established to delete the entire sub-project "
									+ existingProjects[i].getName()
									+ ". Since entire project deletion is not supported by the sync merge facility, press OK to confirm the deletion now. Remember that you can disable this request and never delete project by adding a fle named .voilaIgnore in its root folder.");
					if (confirm) {
						String rootPath = rootProject.getLocation().toOSString();
						if (existingProjects[i].getLocation() != null) {
							String basePathStr = existingProjects[i].getLocation().toOSString();
							// FIXME Va ripulita la base anche per generazioni con piu' rootProjectName
//							basePathStr = basePathStr.replace(rootPath,
//									rootPath + "\\.syncFolder\\base\\" + rootProjectName);

							FileUtils.deleteQuietly(new File(basePathStr));
//							IPath basePath = new Path(basePathStr);
//							IContainer cont = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocation(basePath)[0];
//							cont.delete(true, monitor);
							existingProjects[i].delete(true, monitor);
						}
					}
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

//	/**
//	 * Metodo estratto da doRefreshProject per creare i progetti
//	 * 
//	 * @author Simone Punzo <simone.punzo@micegroup.it>
//	 * @param monitor
//	 * @param out
//	 * @param projectName
//	 * @param allProjects     lista su cui aggiungere i progetti
//	 * @param createdProjects lista su cui aggiungere i progetti creati per la prima
//	 *                        volta
//	 * @param modulesDir
//	 * @param children
//	 * @param j               , l'intero del ciclo esterno più vicino
//	 * @throws CoreException
//	 */
//
//	private void createProjects_(IProgressMonitor monitor, MessageConsoleStream out, String projectName,
//			List<IProject> allProjects, List<IProject> createdProjects, String modulesDir, File[] children, int j)
//			throws CoreException {
//
//		// FIXME findfiles x trovare direttamente i .voilaproject
//		File voilaProj = new File(children[j] + "/" + ".voilaProject");
//
//		if (!voilaProj.exists()) {
//			return;
//		}
//
//		if (children[j].isDirectory()) {
//			IProject currProject = null;
//			File outputLocation = new File(rootProject.getLocation() + "/" + modulesDir + "/" + children[j].getName());
//			IContainer[] mappedContainers = ResourcesPlugin.getWorkspace().getRoot()
//					.findContainersForLocation(new Path(outputLocation.getAbsolutePath()));
//			for (IContainer c : mappedContainers) {
//				if (c.getType() == IResource.PROJECT) {
//					currProject = (IProject) c;
//				}
//			}
//			if (currProject != null) {
//				// currProject = (IProject) mappedContainers[0];
//				out.println("[" + calcDate(System.currentTimeMillis()) + "] [INFO] - Refreshing Sub-Project "
//						+ currProject.getName());
//			//	currProject.refreshLocal(IResource.DEPTH_INFINITE, monitor);
//			} else {
//				if (!outputLocation.exists()) {
//					outputLocation.mkdirs();
//				}
//
//				String projectId = null;
//				if (children[j].getName().startsWith(projectName)) {
//					projectId = children[j].getName();
//				} else {
//					projectId = projectName + "-" + children[j].getName();
//				}
//
//				currProject = ResourcesPlugin.getWorkspace().getRoot().getProject(projectId);
//				out.println("Creating Sub-Project " + currProject.getName());
//
//				IProjectDescription newProjectDesc = ResourcesPlugin.getWorkspace()
//						.newProjectDescription(currProject.getName());
//				newProjectDesc.setLocation(new Path(outputLocation.getAbsolutePath()));
//				try {
//					currProject.create(newProjectDesc, monitor);
//					currProject.open(monitor);
//
//					currProject.refreshLocal(IResource.DEPTH_INFINITE, monitor);
//
//					createdProjects.add(currProject);
//				} catch (Exception e) {
//					e.printStackTrace();
//				}
//			}
//
//			allProjects.add(currProject);
//		}
//	}

	/**
	 * FUNZIONE CREATE TEST PER RESOURCE VISITOR
	 * 
	 * @param monitor
	 * @param out
	 * @param projectName
	 * @param allProjects
	 * @param createdProjects
	 * @param modulesDir
	 * @param children
	 * @param j
	 * @throws CoreException
	 * @throws URISyntaxException
	 */

	private void createProjects(IProgressMonitor monitor, MessageConsoleStream out, List<IProject> allProjects,
			List<IProject> createdProjects, String voilaFolder) throws CoreException {

		IProject currProject = null;
		File outputLocation = new File(voilaFolder);
		IContainer[] mappedContainers;
		try {
			mappedContainers = ResourcesPlugin.getWorkspace().getRoot()
					.findContainersForLocationURI(outputLocation.toURI());
			for (IContainer c : mappedContainers) {
				if (c.getType() == IResource.PROJECT) {
					currProject = (IProject) c;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (currProject != null) {
			// currProject = (IProject) mappedContainers[0];
			ConsoleUtils.consoleMessage(SeverityLevels.INFO, "Refreshing Sub-Project " + currProject.getName());
//			out.println("[" + calcDate(System.currentTimeMillis()) + "] [INFO] - Refreshing Sub-Project "
//					+ currProject.getName());
			currProject.refreshLocal(IResource.DEPTH_INFINITE, monitor);
		} else {
			if (!outputLocation.exists()) {
				outputLocation.mkdirs();
			}

			String projectId = null;
			// Prendiamo il nome del progetto che è il nome della cartella dentro la quale
			// troviamo il voilaproject
			projectId = voilaFolder.substring(voilaFolder.lastIndexOf('\\') + 1);

			currProject = ResourcesPlugin.getWorkspace().getRoot().getProject(projectId);
			ConsoleUtils.consoleMessage(SeverityLevels.INFO, "Creating Sub-Project " + currProject.getName());

			IProjectDescription newProjectDesc = ResourcesPlugin.getWorkspace()
					.newProjectDescription(currProject.getName());
			newProjectDesc.setLocation(new Path(outputLocation.getAbsolutePath()));
			try {
				// TODO evitare che vada qui in caso la risorsa già esista
				if (!currProject.exists()) {
					currProject.create(newProjectDesc, monitor);
					currProject.open(monitor);

					currProject.refreshLocal(IResource.DEPTH_INFINITE, monitor);

					createdProjects.add(currProject);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		allProjects.add(currProject);
	}

//	/**
//	 * Funzione ricorsiva per la generazione dei progetti contenuti dentro la
//	 * cartella sync remote modules Parti da questa e per ogni figlio crei un
//	 * progetto(condizionato alla presenza di un file .voilaProject nella cartella)
//	 * Per ogni figlio guardi i nipoti , richiamata e per ogni nipote guardi i
//	 * pro-nipoti etc.
//	 * 
//	 * @author Simone Punzo <simone.punzo@micegroup.it>
//	 * @param monitor
//	 * @param out
//	 * @param projectName
//	 * @param allProjects
//	 * @param createdProjects
//	 * @param modulesDir
//	 * @param children
//	 * @param j
//	 * @param remotePath
//	 * @throws CoreException
//	 */
//	private void recursive_project_generation(IProgressMonitor monitor, MessageConsoleStream out, String projectName,
//			List<IProject> allProjects, List<IProject> createdProjects, String modulesDir, File[] children, int j,
//			File remotePath) throws CoreException {
//
//		if (children == null || !(j < children.length))
//			return;
//
//		for (int k = 0; k < children.length; k++) {
//			// Cartella in cui sono, per ogni figlio della cartella modules creo un progetto
//			createProjects_(monitor, out, projectName, allProjects, createdProjects, modulesDir, children, k);
//
//			File remoteChildrenPath = new File(remotePath + "/" + children[k].getName());
//			File[] granChildren = remoteChildrenPath.listFiles();
//			String msDir = modulesDir + "/" + children[k].getName();
//			// Figli
//			recursive_project_generation(monitor, out, projectName, allProjects, createdProjects, msDir, granChildren,
//					0, remoteChildrenPath);
//		}
//
//		// Fratelli
////		recursive_func_int(monitor, out, projectName, allProjects, createdProjects, modulesDir, children, j + 1,
////				remotePath);
//
//	}

	private void formatRemoteSourceCode(IProgressMonitor monitor, String remoteRelativeDir)
			throws MalformedURLException, CoreException, IOException, URISyntaxException {
		IJavaProject javaProject = JavaCore.create(rootProject);
		IClasspathEntry[] backupClasspathEntries = javaProject.getRawClasspath();

		IClasspathEntry[] newClasspathEntries = addClasspathEntries(javaProject, remoteRelativeDir, monitor);
		javaProject.setRawClasspath(newClasspathEntries, monitor);
		rootProject.refreshLocal(IResource.DEPTH_INFINITE, monitor);
		formatCode(javaProject, monitor);

		javaProject.setRawClasspath(backupClasspathEntries, monitor);
	}

	private IClasspathEntry[] addClasspathEntries(IJavaProject javaProject, String remoteRelativeDir,
			IProgressMonitor monitor) throws MalformedURLException, CoreException, IOException, URISyntaxException {
		return processContainer(javaProject, rootProject.getFolder(remoteRelativeDir), javaProject.getRawClasspath(),
				monitor);
	}

	private IClasspathEntry[] processContainer(IJavaProject javaProject, IContainer container,
			IClasspathEntry[] classpathEntries, IProgressMonitor monitor)
			throws CoreException, MalformedURLException, IOException, URISyntaxException {
		IResource[] members = container.members();
		for (IResource member : members) {
			if (member instanceof IContainer) {
				if (member.getName().equals("java") || member.getName().equals("resources")) {
					classpathEntries = addProjectLibrary(javaProject, member.getFullPath(), classpathEntries);
				}
				classpathEntries = processContainer(javaProject, (IContainer) member, classpathEntries, monitor);
			}
		}
		return classpathEntries;
	}

	private static IClasspathEntry[] addProjectLibrary(IJavaProject jProject, IPath sourceFolder,
			IClasspathEntry[] oldEntries) throws IOException, URISyntaxException, MalformedURLException, CoreException {
		IClasspathEntry relativeLibraryEntry = JavaCore.newSourceEntry(sourceFolder);

		// add the new classpath entry to the project's existing entries
		IClasspathEntry[] newEntries = new IClasspathEntry[oldEntries.length + 1];
		System.arraycopy(oldEntries, 0, newEntries, 0, oldEntries.length);
		newEntries[oldEntries.length] = relativeLibraryEntry;

		return newEntries;
	}

	private void formatCode(IJavaProject javaProject, IProgressMonitor monitor) {
		final IWorkbenchPartSite targetSite = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getPartService()
				.getActivePart().getSite();
		IStructuredSelection selection = new StructuredSelection(javaProject);

//        FormatAllAction format = new FormatAllAction(targetSite);
//        format.run(selection);

		OrganizeImportsAction org = new OrganizeImportsAction(targetSite);
		org.run(selection);
	}

	private void processFile(IFile member, IProgressMonitor monitor) {
		if (Util.isJavaLikeFileName(member.getName())) {
			processJavaFile(member, monitor);
		}
	}

	private void processJavaFile(IFile member, IProgressMonitor monitor) {
		ICompilationUnit compilationUnit = JavaCore.createCompilationUnitFrom(member);
		ImportRewrite ir;
		try {
			ir = ImportRewrite.create(compilationUnit, true);
			TextEdit editOi = ir.rewriteImports(monitor);
			compilationUnit.applyTextEdit(editOi, monitor);
			compilationUnit.save(monitor, true);
		} catch (JavaModelException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (CoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	protected CompilationUnit parse(ICompilationUnit unit, IProgressMonitor monitor) {
		ASTParser parser = ASTParser.newParser(AST.JLS12);
		parser.setKind(ASTParser.K_COMPILATION_UNIT);
		parser.setSource(unit); // set source
		parser.setResolveBindings(true); // we need bindings later on
		return (CompilationUnit) parser.createAST(monitor); // parse
	}

	private String getModulesDir() {
		String result = null; // projectProperties.getProperty("modules.dir");
		return result == null ? "modules" : result;
	}

	private boolean isMultiModuleProject() {
		return true;
	}

	public void prepare(IFolder folder) throws CoreException {
		if (!FileCopyUtils.exists(folder)) {
			if (folder.getParent() instanceof IFolder) {
				prepare((IFolder) folder.getParent());
			}
			File folderFile = new File(folder.getLocation().toOSString());
			folderFile.mkdir();
//			if (!folder.exists()) {
//				prepare((IFolder) folder.getParent());
//				folder.create(false, false, null);
//			}
		}
	}

	private void refreshBaseBytes(IProgressMonitor monitor, MessageConsoleStream out, List<IProject> allProjects)
			throws IOException {
		// Run the MDD generation synchronizer merger action.
		FixRecordsAction action = new FixRecordsAction();
		IStructuredSelection selProjects = new StructuredSelection(allProjects);
		action.run(null, selProjects);
	}

	private String calcDate(long millisecs) {
		SimpleDateFormat date_format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Date resultdate = new Date(millisecs);
		return date_format.format(resultdate);
	}

	public IProject getModelProject() {
		IProject result = ResourcesPlugin.getWorkspace().getRoot().getProject("/" + rootProject.getName() + "-model");
		return result;
	}

}
