/*
 * Copyright 20012 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.aries.versioning.mojo;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;

import org.apache.aries.util.manifest.BundleManifest;
import org.apache.aries.versioning.check.BundleCompatibility;
import org.apache.aries.versioning.check.BundleInfo;
import org.apache.aries.versioning.check.VersionChange;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.repository.RepositorySystem;
import org.osgi.framework.Version;

/**
 * Check semantic version changes between an explicitly named old artifact and
 * the project output artifact. Optionally write packageinfo files for wrong
 * package versions.
 */
@Mojo(name = "version-check", defaultPhase = LifecyclePhase.VERIFY)
public class VersionCheckerMojo extends AbstractMojo {

    /**
     * name of old artifact in
     * groupId:artifactId[:extension[:classifier]]:version notation
     */
    @Parameter(property="aries.oldArtifact")
    private String oldArtifact;
    
    @Parameter(property="aries.skip.version.check", defaultValue = "false")
    private boolean skip;

    /**
     * Location of the file (defaults to main project artifact).
     */
    @Parameter
    private File newFile;

    /**
     * whether to write packageinfo files into source tree
     */
    @Parameter(property="writePackageInfos", defaultValue="false")
    private boolean writePackageinfos = false;

    /**
     * source tree location
     */
    @Parameter(defaultValue="${project.basedir}/src/main/java")
    private File source;

    @Parameter
    private List<String> excludes;
    
    @Inject
    private RepositorySystem repository;
    
    @Inject
    protected MavenProject project;
    
    @Inject
    private MavenSession session;
    
    public void execute() throws MojoExecutionException {
        if (skip) {
            getLog().info("Version check is skipped");
            return;
        }
        if ("pom".equals(project.getPackaging())) {
            getLog().info("Skipping version check for pom");
            return;
        }
        if (newFile == null) {
            newFile = project.getArtifact().getFile();
        }
        if (oldArtifact != null) {
            try {
                BundleInfo newBundle = getBundleInfo(newFile);
                if (newBundle == null || newBundle.getBundleManifest() == null) {
                    //not a bundle type, just return
                    getLog().info(newFile + " is not an OSGi bundle, skipping.");
                    return;
                }
                if (null == newBundle.getBundleManifest().getManifestVersion()
                    && null == newBundle.getBundleManifest().getSymbolicName()
                    && Version.emptyVersion.equals(newBundle.getBundleManifest().getVersion())) {
                    //not a bundle type, just return
                    getLog().info(newFile + " is not an OSGi bundle, skipping.");
                    return;
                }

                BundleInfo oldBundle = getBundleInfo(resolve(oldArtifact));
                String bundleSymbolicName = newBundle.getBundleManifest().getSymbolicName();
                URLClassLoader oldClassLoader = new URLClassLoader(new URL[] {oldBundle.getBundle().toURI()
                    .toURL()});
                URLClassLoader newClassLoader = new URLClassLoader(new URL[] {newBundle.getBundle().toURI()
                    .toURL()});
                BundleCompatibility bundleCompatibility = new BundleCompatibility(bundleSymbolicName,
                                                                                  newBundle, oldBundle,
                                                                                  oldClassLoader,
                                                                                  newClassLoader,
                                                                                  excludes);
                bundleCompatibility.invoke();
                String bundleElement = bundleCompatibility.getBundleElement();
                String pkgElement = bundleCompatibility.getPkgElements().toString();
                
                boolean failed = false;
                if ((bundleElement != null) && (bundleElement.trim().length() > 0)) {
                    getLog().error(bundleElement + "\r\n");
                    failed = true;
                }
                if ((pkgElement != null) && (pkgElement.trim().length() > 0)) {
                    getLog().error(pkgElement);
                    failed = true;
                }

                if (writePackageinfos) {
                    writePackageInfos(bundleCompatibility);
                }
                if (failed) {
                    throw new RuntimeException("Semantic Versioning incorrect");
                } else {
                    getLog().info("All package or bundle versions are semanticly versioned correctly.");
                }
            } catch (MalformedURLException e) {
                throw new MojoExecutionException("Problem analyzing sources");
            } catch (IOException e) {
                throw new MojoExecutionException("Problem analyzing sources");
            }
        }
    }

    private void writePackageInfos(BundleCompatibility bundleCompatibility) {
        Map<String, VersionChange> packages = bundleCompatibility.getPackageChanges();
        for (Map.Entry<String, VersionChange> packageChange : packages.entrySet()) {
            VersionChange versionChange = packageChange.getValue();
            if (!versionChange.isCorrect()) {
                String packageName = packageChange.getKey();
                String[] bits = packageName.split("\\.");
                File packageInfo = source;
                for (String bit : bits) {
                    packageInfo = new File(packageInfo, bit);
                }
                packageInfo.mkdirs();
                packageInfo = new File(packageInfo, "packageinfo");
                try {
                    FileWriter w = new FileWriter(packageInfo);
                    try {
                        w.append("# generated by Apache Aries semantic versioning tool\n");
                        w.append("version " + versionChange.getRecommendedNewVersion().toString() + "\n");
                        w.flush();
                    } finally {
                        w.close();
                    }
                } catch (IOException e) {
                    getLog().error("Could not write packageinfo for package " + packageName, e);
                }
            }
        }
    }

    private File resolve(String artifactDescriptor) {
        String[] s = artifactDescriptor.split(":");

        String type = (s.length >= 4 ? s[3] : "jar");
        Artifact artifact = repository.createArtifact(s[0], s[1], s[2], type);

        ArtifactResolutionRequest request = new ArtifactResolutionRequest();
        request.setArtifact(artifact);
        
        request.setResolveRoot(true).setResolveTransitively(false);
        request.setServers( session.getRequest().getServers() );
        request.setMirrors( session.getRequest().getMirrors() );
        request.setProxies( session.getRequest().getProxies() );
        request.setLocalRepository(session.getLocalRepository());
        request.setRemoteRepositories(session.getRequest().getRemoteRepositories());
        repository.resolve(request);
        return artifact.getFile();
    }

    private BundleInfo getBundleInfo(File f) {
        BundleManifest bundleManifest = BundleManifest.fromBundle(f);
        return new BundleInfo(bundleManifest, f);
    }
}
