/*******************************************************************************
 * Copyright (c) 2012, 2016 Red Hat, Inc and others.
 * All rights reserved. This program and the accompanying materials
 * are 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:
 *    Mickael Istria (Red Hat). - initial API and implementation
 *******************************************************************************/
package org.eclipse.tycho.extras.tpvalidator;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
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.codehaus.plexus.logging.Logger;
import org.eclipse.sisu.equinox.EquinoxServiceFactory;
import org.eclipse.tycho.ArtifactType;
import org.eclipse.tycho.PackagingType;
import org.eclipse.tycho.core.resolver.shared.IncludeSourceMode;
import org.eclipse.tycho.core.shared.TargetEnvironment;
import org.eclipse.tycho.osgi.adapters.MavenLoggerAdapter;
import org.eclipse.tycho.p2.resolver.TargetDefinitionFile;
import org.eclipse.tycho.p2.resolver.facade.P2Resolver;
import org.eclipse.tycho.p2.resolver.facade.P2ResolverFactory;
import org.eclipse.tycho.p2.target.facade.TargetDefinition.InstallableUnitLocation;
import org.eclipse.tycho.p2.target.facade.TargetDefinition.Location;
import org.eclipse.tycho.p2.target.facade.TargetDefinition.Repository;
import org.eclipse.tycho.p2.target.facade.TargetDefinition.Unit;
import org.eclipse.tycho.p2.target.facade.TargetPlatformConfigurationStub;
import org.eclipse.tycho.p2.tools.RepositoryReferences;
import org.eclipse.tycho.p2.tools.director.shared.DirectorRuntime;

/**
 * Validates that specified target platforms (.target files) contents can be resolved.
 */
@Mojo(name = "validate-target-platform", defaultPhase = LifecyclePhase.VALIDATE)
public class TPValidationMojo extends AbstractMojo {
    @Parameter(property = "project")
    private MavenProject project;

    /**
     * The .target files to validate. If not specified and the project packaging is
     * "eclipse-target-definition", the goal will validate project's primary target file will be
     * validated.
     */
    @Parameter
    private File[] targetFiles;

    /**
     * whether to fail build or just print a warning when a validation fails
     */
    @Parameter(defaultValue = "true")
    private boolean failOnError;

    /**
     * Check that, for each artifact from the target file, the dependencies of the artifact are also
     * contained in the target file. Also check that there are no conflicting artifacts, i.e.
     * artifact which could not be installed together. When this check passes, none of the artifacts
     * should lead to dependency resolution problems when used in a Tycho project.
     * 
     * @since 0.21.0
     */
    @Parameter(defaultValue = "false")
    private boolean checkDependencies;

    /**
     * Check that the content of the target-platform can be installed together in a same
     * provisioning operation.
     *
     * @since 0.26.0
     */
    @Parameter(defaultValue = "false")
    private boolean checkProvisioning;

    @Parameter(defaultValue = "JavaSE-1.7")
    private String executionEnvironment;

    @Component
    protected EquinoxServiceFactory equinox;

    @Component
    private Logger logger;

    private P2ResolverFactory factory;

    public void execute() throws MojoExecutionException {
        this.factory = this.equinox.getService(P2ResolverFactory.class);

        List<TPError> errors = new ArrayList<>();
        File[] targetFilesToValidate;
        if (this.targetFiles != null) {
            targetFilesToValidate = this.targetFiles;
        } else if (this.project != null
                && PackagingType.TYPE_ECLIPSE_TARGET_DEFINITION.equals(this.project.getPackaging())) {
            File defaultTargetFile = new File(project.getBasedir(), project.getArtifactId() + ".target");
            targetFilesToValidate = new File[] { defaultTargetFile };
        } else {
            this.logger.info("No targetFiles configured. Skipping execution.");
            return;
        }

        for (File targetFile : targetFilesToValidate) {
            try {
                validateTarget(targetFile);
                this.logger.info("OK!");
            } catch (TPError ex) {
                this.logger.info("Failed, see Error log below");
                errors.add(ex);
            }
        }

        if (!errors.isEmpty()) {
            String message = createErrorMessage(errors);
            this.logger.error(message);
            if (this.failOnError) {
                throw new MojoExecutionException(message);
            }
        }
    }

    private String createErrorMessage(List<TPError> errors) {
        StringBuilder res = new StringBuilder();
        res.append("Validation found errors in ");
        res.append(errors.size());
        res.append(" .target files:");
        res.append("\n");
        for (TPError error : errors) {
            res.append(error.getMessage(true));
            res.append("\n");
        }
        return res.toString();
    }

    private void validateTarget(File targetFile) throws TPError {
        try {
            // create resolver
            this.logger.info("Validating " + targetFile + "...");
            RepositoryReferences ref = new RepositoryReferences();
            DirectorRuntime director = this.equinox.getService(DirectorRuntime.class);
            DirectorRuntime.Command directorCommand = director.newInstallCommand();

            TargetPlatformConfigurationStub tpConfiguration = new TargetPlatformConfigurationStub();
            tpConfiguration.setEnvironments(Collections.singletonList(TargetEnvironment.getRunningEnvironment()));

            TargetDefinitionFile targetDefinition = TargetDefinitionFile.read(targetFile, IncludeSourceMode.honor);
            tpConfiguration.addTargetDefinition(targetDefinition);

            P2Resolver resolver = this.factory.createResolver(new MavenLoggerAdapter(this.logger, false));

            for (Location location : targetDefinition.getLocations()) {
                if (location instanceof InstallableUnitLocation) {
                    InstallableUnitLocation p2Loc = (InstallableUnitLocation) location;
                    for (Repository repo : p2Loc.getRepositories()) {
                        ref.addArtifactRepository(repo.getLocation());
                        ref.addMetadataRepository(repo.getLocation());
                    }
                    for (Unit unit : p2Loc.getUnits()) {
                        if (checkDependencies) {
                            // make dependency resolver resolve everything simultaneously
                            resolver.addDependency(ArtifactType.TYPE_INSTALLABLE_UNIT, unit.getId(), unit.getVersion());
                        }
                        if (checkProvisioning) {
                            directorCommand.addUnitToInstall(unit.getId() + '/' + unit.getVersion());
                        }
                    }
                }
            }
            resolver.resolveMetadata(tpConfiguration, executionEnvironment);
            if (checkProvisioning) {
                directorCommand.addMetadataSources(ref.getMetadataRepositories());
                directorCommand.addArtifactSources(ref.getArtifactRepositories());
                directorCommand.setDestination(
                        new File(project.getBuild().getDirectory(), targetFile.getName() + ".provisioning"));
                directorCommand.setProfileName(project.getArtifactId());
                directorCommand.setVerifyOnly(true);
                directorCommand.setInstallFeatures(true);
                directorCommand.execute();
            }
        } catch (Exception e) {
            throw new TPError(targetFile, e);
        }
    }

}
