/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2007-2015 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.enterprise.module.maven;

import com.sun.enterprise.module.ManifestConstants;
import com.sun.enterprise.module.ModuleDefinition;
import com.sun.enterprise.module.Repository;
import com.sun.enterprise.module.common_impl.AbstractRepositoryImpl;
import com.sun.enterprise.module.common_impl.ModuleId;
import com.sun.enterprise.module.common_impl.AbstractFactory;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.model.Resource;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * {@link Repository} implementation that loads modules
 * from the transitive dependencies set in a {@link MavenProject}.
 *
 * @author Kohsuke Kawaguchi
 */
public class MavenProjectRepository extends AbstractRepositoryImpl {

    private final MavenProject project;

    // needed to resolve additional artifacts
    private final ArtifactResolver artifactResolver;
    private final ArtifactRepository localRepository;
    private final ArtifactFactory artifactFactory;


    /**
     * All {@link Artifact}s in the transitive dependency list
     * keyed by their {@link Artifact#getId() id}.
     */
    private final Map<String,Artifact> artifacts = new HashMap<String,Artifact>();
    
    public MavenProjectRepository(MavenProject project, ArtifactResolver artifactResolver, ArtifactRepository localRepository, ArtifactFactory artifactFactory) {
        super(project.getName(),project.getFile().toURI());
        this.project = project;
        this.artifactResolver = artifactResolver;
        this.localRepository = localRepository;
        this.artifactFactory = artifactFactory;

        // adds the ones that we already know of.
        Artifact ma = project.getArtifact();
        if(ma.getFile()!=null) {
            // if the 'ma' is the distribution module, it won't have its own output.
            artifacts.put(ma.getId(),ma);
        }

        for (Artifact a : (Set<Artifact>) project.getArtifacts())
            artifacts.put(a.getId(),a);
    }

    /**
     * When creating {@link MavenProjectRepository} from the current project (which is used
     * to launch mvn), and if the compile phase has run yet, then the main artifact is
     * still null.
     *
     * <p>
     * However, it's often convenient to pick up the files that were left in the file system
     * from the previous execution. This method checks this situation and updates {@link MavenProject}
     * accordingly, so that it can be then passed to the constructor of {@link MavenProjectRepository}.
     *
     * <p>
     * Think of this as a pre-processing phase to compensate for the lack of the compile phase
     * invocation.
     */
    public static void prepareProject(MavenProject project) throws IOException {
        Artifact ma = project.getArtifact();
        if(!project.getPackaging().equals("pom") && ma.getFile()==null) {
            File outdir = new File(project.getBuild().getOutputDirectory());
            if(!outdir.exists())
                logger.warning("No output directory "+outdir);
            else
                ma.setFile(outdir);
        }

        if(ma.getFile()!=null) {
            // if the 'ma' is the distribution module, it won't have its own output.
            if(ma.getFile().isDirectory()) {
                // if the main artifact is from target/classes, create META-INF.MF
                new Packager().writeManifest(project,ma.getFile());
            }
        }
    }

    @Override
    protected void loadModuleDefs(Map<ModuleId, ModuleDefinition> moduleDefs, List<URI> libraries) throws IOException {

        logger.info("Loading modules list from "+project.getFile());

        MavenModuleDefinition main = buildModule(project.getArtifact(), moduleDefs, libraries);
        if(main!=null) {
            // artifact from the main project, in case those are not compiled yet
        	logger.info("Adding classpath entry: "+project.getBuild().getOutputDirectory());
        	
            main.addClasspath(new File(project.getBuild().getOutputDirectory()));
            for (Resource res : (List<Resource>)project.getBuild().getResources()) {
            	logger.info("Adding classpath entry: "+res.getDirectory());
            	
            	main.addClasspath(new File(res.getDirectory()));
            }
        }

        for (Artifact a : (List<Artifact>) project.getAttachedArtifacts()) {
            buildModule(a, moduleDefs, libraries);
        }

        for (Artifact a : (Set<Artifact>) project.getArtifacts()) {
            buildModule(a, moduleDefs, libraries);
        }

        if(moduleDefs.isEmpty()) {
//            throw new Error("No modules found");// should this error check be done by the caller of loadModuleDefs?
            logger.warning("No modules found");            
        }

    }

    private MavenModuleDefinition buildModule(Artifact a, Map<ModuleId, ModuleDefinition> moduleDefs, List<URI> libraries)
            throws IOException {

        File jarFile = a.getFile();
        if(jarFile==null || (!jarFile.getName().endsWith(".jar") && !jarFile.isDirectory()))
            // between the compile phase and the package phase, the main artifact is
            // set to the target/classes. allow that to be used as a jar.
            return null;

        MavenModuleDefinition moduleDef = loadJar(jarFile);
        if(moduleDef.getManifest().getMainAttributes().getValue(ManifestConstants.BUNDLE_NAME)==null) {
            // project.getArtifacts() pick up all the transitive dependencies,
            // including to the normal jar files through modules.
            libraries.add(jarFile.toURI());
            return null;
        }

        if(logger.isLoggable(Level.CONFIG))
            logger.config("Adding module "+a.getId()+" trail: "+a.getDependencyTrail());

        moduleDefs.put(AbstractFactory.getInstance().createModuleId(moduleDef), moduleDef);
        return moduleDef;
    }

    /*package*/ File resolveArtifact(String id) throws IOException {
        Artifact artifact = artifacts.get(id);
        if(artifact!=null)  return artifact.getFile();
        
        try {
            Matcher m = ID_PATTERN.matcher(id);
            if(!m.matches())
                throw new IllegalArgumentException("Wrong ID: "+id);
            Artifact a = artifactFactory.createArtifactWithClassifier(
                m.group(1), m.group(2), m.group(5), m.group(3), m.group(4)
            );

            artifactResolver.resolve(a, project.getRemoteArtifactRepositories(), localRepository );
            artifacts.put(a.getId(),a);
            return a.getFile();
        } catch (ArtifactResolutionException e) {
            throw new IOException2("Failed to resolve "+id,e);
        } catch (ArtifactNotFoundException e) {
            throw new IOException2("Failed to resolve "+id,e);
        }
    }

    // the format is 'groupId:artifactId:type(:classifier)?:version
    private static final String TOKEN = "([^:]+)";
    private static final Pattern ID_PATTERN = Pattern.compile(MessageFormat.format("{0}:{0}:{0}(?:\\:{0})?:{0}",TOKEN));


    protected MavenModuleDefinition loadJar(File jar) throws IOException {
        return new MavenModuleDefinition(this,jar);
    }

    private static final Logger logger = Logger.getLogger(MavenProjectRepository.class.getName());
}
