/*******************************************************************************
 * Copyright (c) 2011 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.jboss.tools.maven.jbosspackaging.configurators;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.project.MavenProject;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jst.j2ee.classpathdep.IClasspathDependencyConstants;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.MavenProjectChangedEvent;
import org.eclipse.m2e.core.project.MavenProjectUtils;
import org.eclipse.m2e.core.project.configurator.AbstractProjectConfigurator;
import org.eclipse.m2e.core.project.configurator.ProjectConfigurationRequest;
import org.eclipse.m2e.jdt.internal.MavenClasspathHelpers;
import org.eclipse.m2e.wtp.ArtifactHelper;
import org.eclipse.m2e.wtp.ResourceCleaner;
import org.eclipse.m2e.wtp.WTPProjectsUtil;
import org.eclipse.wst.common.componentcore.ComponentCore;
import org.eclipse.wst.common.componentcore.ModuleCoreNature;
import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualFolder;
import org.eclipse.wst.common.componentcore.resources.IVirtualReference;
import org.eclipse.wst.common.frameworks.datamodel.IDataModel;
import org.eclipse.wst.common.project.facet.core.IFacetedProject;
import org.eclipse.wst.common.project.facet.core.IFacetedProject.Action;
import org.eclipse.wst.common.project.facet.core.IProjectFacet;
import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion;
import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager;
import org.jboss.ide.eclipse.as.ui.mbeans.project.IJBossSARFacetDataModelProperties;
import org.jboss.ide.eclipse.as.ui.mbeans.project.JBossSARFacetDataModelProvider;
import org.jboss.tools.maven.core.IJBossMavenConstants;
import org.jboss.tools.maven.core.internal.project.facet.MavenFacetInstallDataModelProvider;

/**
 * 
 * @author Fred Bricon
 * 
 */
public class SarProjectConfigurator extends AbstractProjectConfigurator {

	public static final IProjectFacet JBOSS_SAR_FACET;
	public static final IProjectFacetVersion JBOSS_SAR_FACET_VERSION_1_0;
	public static final ArtifactFilter SCOPE_FILTER_RUNTIME = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME);
	protected static final IProjectFacet m2Facet;
	protected static final IProjectFacetVersion m2Version;
	private static final IClasspathAttribute NONDEPENDENCY_ATTRIBUTE = JavaCore.newClasspathAttribute(
			IClasspathDependencyConstants.CLASSPATH_COMPONENT_NON_DEPENDENCY, "");
	static {
		JBOSS_SAR_FACET = ProjectFacetsManager.getProjectFacet("jst.jboss.sar");
		JBOSS_SAR_FACET_VERSION_1_0 = JBOSS_SAR_FACET.getVersion("1.0");//$NON-NLS-1$
		m2Facet = ProjectFacetsManager.getProjectFacet("jboss.m2"); //$NON-NLS-1$
		m2Version = m2Facet.getVersion("1.0"); //$NON-NLS-1$
	}

	@Override
	public void configure(ProjectConfigurationRequest request, IProgressMonitor monitor) throws CoreException {
		
		MavenProject mavenProject = request.getMavenProject();
		IProject project = request.getProject();
		
		markerManager.deleteMarkers(project, MavenSarConstants.SAR_CONFIGURATION_ERROR_MARKER_ID);
		
		if (!getExpectedPackage().equals(mavenProject.getPackaging())) {
			return;
		}
		
		IFacetedProject facetedProject = ProjectFacetsManager.create(project, true, monitor);

		Set<Action> actions = new LinkedHashSet<Action>();
		
		IMavenProjectFacade facade = request.getMavenProjectFacade();
		
		ResourceCleaner fileCleaner = new ResourceCleaner(project);
		addFoldersToClean(fileCleaner, facade);
		
		IPath source = facade.getResourceLocations()[0];
		
		WTPProjectsUtil.installJavaFacet(actions, project, facetedProject);
		if (!actions.isEmpty()) {
			facetedProject.modify(actions, monitor);
		}
		installSarFacet(facetedProject, source, monitor);

		installM2Facet(facetedProject, monitor);

		ModuleCoreNature.addModuleCoreNatureIfNecessary(project, monitor);

		removeTestFolderLinks(project, mavenProject, monitor, "/");

		WTPProjectsUtil.setNonDependencyAttributeToContainer(project, monitor);

		WTPProjectsUtil.removeWTPClasspathContainer(project);
		
		fileCleaner.cleanUp();
	}

	//TODO Fix/refactor that method from WTPProjectUtils in m2e-wtp 0.14.x
   public static void removeTestFolderLinks(IProject project, MavenProject mavenProject, IProgressMonitor monitor,
	      String folder) throws CoreException {
	    IVirtualComponent component = ComponentCore.createComponent(project);
	    if (component != null){
	      IVirtualFolder jsrc = component.getRootFolder().getFolder(folder);
	      for(IPath location : MavenProjectUtils.getSourceLocations(project, mavenProject.getTestCompileSourceRoots())) {
	        if (location == null) continue;
	        jsrc.removeLink(location, 0, monitor);
	      }
	      for(IPath location : MavenProjectUtils.getResourceLocations(project, mavenProject.getTestResources())) {
	        if (location == null) continue;
	        jsrc.removeLink(location, 0, monitor);
	      }
	    }
    }

	private void installM2Facet(IFacetedProject fproj, IProgressMonitor monitor) throws CoreException {
		if (!fproj.hasProjectFacet(m2Facet)) {
			IDataModel config = (IDataModel) new MavenFacetInstallDataModelProvider().create();
			config.setBooleanProperty(IJBossMavenConstants.MAVEN_PROJECT_EXISTS, true);
			fproj.installProjectFacet(m2Version, config, monitor);
		}
	}
	
	private void installSarFacet(IFacetedProject fproj, IPath source, IProgressMonitor monitor) throws CoreException {
		if (!fproj.hasProjectFacet(JBOSS_SAR_FACET)) {
			IProjectFacetVersion facetVersion = JBOSS_SAR_FACET_VERSION_1_0;
			IStatus status = facetVersion.getConstraint().check(fproj.getProjectFacets());
			if (status.isOK()) {
				IDataModel config = (IDataModel) new JBossSARFacetDataModelProvider().create();
				config.setProperty(IJBossSARFacetDataModelProperties.SAR_CONTENT_FOLDER, source.toPortableString());
				fproj.installProjectFacet(facetVersion, config, monitor);
			} else {
		        addErrorMarker(fproj.getProject(), facetVersion + " can not be installed : "+ status.getMessage());
				for (IStatus st : status.getChildren()) {
			        addErrorMarker(fproj.getProject(), st.getMessage());
				}
			}
		}
	}

	private void addErrorMarker(IProject project, String message) {
	    markerManager.addMarker(project, 
	    		MavenSarConstants.SAR_CONFIGURATION_ERROR_MARKER_ID, 
	    		message
	    		,-1,  IMarker.SEVERITY_ERROR);
	}
	
	protected String getExpectedPackage() {
		return "jboss-sar";
	}


	public void setModuleDependencies(IProject project, MavenProject mavenProject, IProgressMonitor monitor)
			throws CoreException {

		IVirtualComponent sarComponent = ComponentCore.createComponent(project);
		if (sarComponent == null) {
			return;
		}

		Set<IVirtualReference> newRefs = new LinkedHashSet<IVirtualReference>();

		JBossPackagingPluginConfiguration config = new JBossPackagingPluginConfiguration(mavenProject);

		if (!config.isExcludeAll()) {
			Set<Artifact> artifacts = mavenProject.getArtifacts();
			
			for (Artifact artifact : artifacts) {
				// Don't deploy pom, non runtime or optional dependencies
				if ("pom".equals(artifact.getType()) || !SCOPE_FILTER_RUNTIME.include(artifact) 
					|| artifact.isOptional() || config.isExcluded(artifact)) {
					continue;
				}
	
				IMavenProjectFacade workspaceDependency = projectManager.getMavenProject(artifact.getGroupId(),
						artifact.getArtifactId(), artifact.getVersion());
	
				IVirtualComponent depComponent;
				if (workspaceDependency != null && !workspaceDependency.getProject().equals(project)
						&& workspaceDependency.getFullPath(artifact.getFile()) != null) {
					// artifact dependency is a workspace project
					IProject depProject = workspaceDependency.getProject();
					configureWtpUtil(workspaceDependency, monitor);
					depComponent = ComponentCore.createComponent(depProject);
				} else {
					// artifact dependency should be added as a JEE module,
					// referenced with M2_REPO variable
					String artifactPath = ArtifactHelper.getM2REPOVarPath(artifact);
					depComponent = ComponentCore.createArchiveComponent(sarComponent.getProject(), artifactPath);
				}
				IVirtualReference reference = ComponentCore.createReference(sarComponent, depComponent);
				reference.setArchiveName(config.mapFileName(artifact));
				reference.setRuntimePath(new Path(config.getLibDirectory()));
				newRefs.add(reference);
			}
		}
		IVirtualReference[] newRefsArray = new IVirtualReference[newRefs.size()];
		newRefs.toArray(newRefsArray);

		// Only change the project references if they've changed
		if (WTPProjectsUtil.hasChanged(sarComponent.getReferences(), newRefsArray)) {
			sarComponent.setReferences(newRefsArray);
		}
	}

	protected void updateContainerAttributes(IProject project, IClasspathAttribute attributeToAdd,
			String attributeToDelete, IProgressMonitor monitor) throws JavaModelException {
		IJavaProject javaProject = JavaCore.create(project);
		if (javaProject == null)
			return;
		IClasspathEntry[] cp = javaProject.getRawClasspath();
		for (int i = 0; i < cp.length; i++) {
			if (IClasspathEntry.CPE_CONTAINER == cp[i].getEntryKind()
					&& MavenClasspathHelpers.isMaven2ClasspathContainer(cp[i].getPath())) {
				LinkedHashMap<String, IClasspathAttribute> attrs = new LinkedHashMap<String, IClasspathAttribute>();
				for (IClasspathAttribute attr : cp[i].getExtraAttributes()) {
					if (!attr.getName().equals(attributeToDelete)) {
						attrs.put(attr.getName(), attr);
					}
				}
				attrs.put(attributeToAdd.getName(), attributeToAdd);
				IClasspathAttribute[] newAttrs = attrs.values().toArray(new IClasspathAttribute[attrs.size()]);
				cp[i] = JavaCore.newContainerEntry(cp[i].getPath(), cp[i].getAccessRules(), newAttrs,
						cp[i].isExported());
				break;
			}
		}
		javaProject.setRawClasspath(cp, monitor);
	}

	@Override
	public void mavenProjectChanged(MavenProjectChangedEvent event, IProgressMonitor monitor) throws CoreException {
		setModuleDependencies(event.getMavenProject().getProject(), event.getMavenProject().getMavenProject(), monitor);
	}
	
	protected void configureWtpUtil(IMavenProjectFacade facade, IProgressMonitor monitor) throws CoreException {
		    // Adding utility facet on JEE projects is not allowed
		    IProject project = facade.getProject();
		    MavenProject mavenProject = facade.getMavenProject();
		    if(  !WTPProjectsUtil.isJavaProject(facade)
		       || WTPProjectsUtil.isJavaEEProject(project) 
		       || WTPProjectsUtil.isQualifiedAsWebFragment(facade)) {
		      return;
		    }
		    
		    IPath[] sourceRoots = MavenProjectUtils.getSourceLocations(project, mavenProject.getCompileSourceRoots());
		    IPath[] resourceRoots = MavenProjectUtils.getResourceLocations(project, mavenProject.getResources());
		    
		    if (!checkJavaConfiguration(project, sourceRoots, resourceRoots)) {
		      return;
		    }

		    IFacetedProject facetedProject = ProjectFacetsManager.create(project, true, monitor);
		    Set<Action> actions = new LinkedHashSet<Action>();

		    WTPProjectsUtil.installJavaFacet(actions, project, facetedProject);

			if(!facetedProject.hasProjectFacet(WTPProjectsUtil.UTILITY_FACET)) {
				actions.add(new IFacetedProject.Action(IFacetedProject.Action.Type.INSTALL, WTPProjectsUtil.UTILITY_10, null));
			} else if(!facetedProject.hasProjectFacet(WTPProjectsUtil.UTILITY_10)) {
				actions.add(new IFacetedProject.Action(IFacetedProject.Action.Type.VERSION_CHANGE, WTPProjectsUtil.UTILITY_10,
	          null));
			}
		    
		    if (!actions.isEmpty()) {
		      ResourceCleaner fileCleaner = new ResourceCleaner(project);
		      try {
		        addFoldersToClean(fileCleaner, facade);
		        facetedProject.modify(actions, monitor);      
		      } finally {
		        //Remove any unwanted MANIFEST.MF the Facet installation has created
		        fileCleaner.cleanUp();
		      } 
		    }
		    
		    WTPProjectsUtil.fixMissingModuleCoreNature(project, monitor);
		    
		    //MNGECLIPSE-904 remove tests folder links for utility jars
		    removeTestFolderLinks(project, mavenProject, monitor, "/");
		    
		    //Remove "library unavailable at runtime" warning.
		    WTPProjectsUtil.setNonDependencyAttributeToContainer(project, monitor);
		    
		    WTPProjectsUtil.removeWTPClasspathContainer(project);
	  }

	  /**
	   * Checks the maven source folders are correctly added to the project classpath
	   */
	  private boolean checkJavaConfiguration(IProject project, IPath[] sourceRoots, IPath[] resourceRoots) throws JavaModelException {
	    IJavaProject javaProject = JavaCore.create(project);
	    if (javaProject == null) {
	      return false;
	    }
	    IClasspathEntry[] cpEntries = javaProject.getRawClasspath();
	    if (cpEntries == null) {
	      return false;
	    }
	    Set<IPath> currentPaths = new HashSet<IPath>();
	    for (IClasspathEntry entry  : cpEntries) {
	      if (IClasspathEntry.CPE_SOURCE == entry.getEntryKind()){
	        currentPaths.add(entry.getPath().makeRelativeTo(project.getFullPath()));
	      }
	    }
	    for(IPath mavenSource : sourceRoots) {
	        if (mavenSource != null && !mavenSource.isEmpty()) {
	          IFolder sourceFolder = project.getFolder(mavenSource);
	          if (sourceFolder.exists() && !currentPaths.contains(mavenSource)) {
	            return false;
	          }
	        }
	    }
	    for(IPath mavenSource : resourceRoots) {
	      if (mavenSource != null && !mavenSource.isEmpty()) {
	        IFolder resourceFolder = project.getFolder(mavenSource);
	        if (resourceFolder.exists() && !currentPaths.contains(mavenSource)) {
	          return false;
	        }
	      }
	  }
	    return true;
	  }

	  
	  
	  protected void addFoldersToClean(ResourceCleaner fileCleaner, IMavenProjectFacade facade) {
	    for (IPath p : facade.getCompileSourceLocations()) {
	      if (p != null) {
	        fileCleaner.addFiles(p.append("META-INF/MANIFEST.MF"));
	        fileCleaner.addFolder(p);
	      }
	    }
	    for (IPath p : facade.getResourceLocations()) {
	      if (p != null) {
	        fileCleaner.addFiles(p.append("META-INF/MANIFEST.MF"));
	        fileCleaner.addFolder(p);
	      }
	    }
	    for (IPath p : facade.getTestCompileSourceLocations()) {
	      if (p != null) fileCleaner.addFolder(p);
	    }
	    for (IPath p : facade.getTestResourceLocations()) {
	      if (p != null) fileCleaner.addFolder(p);
	    }
	  }
	  	  
}
