/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.maven.reporting.exec;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.apache.maven.lifecycle.LifecycleExecutor;
import org.apache.maven.model.Build;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MavenPluginManager;
import org.apache.maven.plugin.Mojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoNotFoundException;
import org.apache.maven.plugin.PluginConfigurationException;
import org.apache.maven.plugin.PluginContainerException;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionResolutionException;
import org.apache.maven.plugin.version.PluginVersionResolver;
import org.apache.maven.plugin.version.PluginVersionResult;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.MavenReport;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.Objects.requireNonNull;

/**
 * <p>
 * This component will build some {@link MavenReportExecution} from {@link MavenReportExecutorRequest}. If a
 * {@link MavenReport} needs to fork a lifecycle, this fork is executed here. It will ask the core to get some
 * informations in order to correctly setup {@link MavenReport}.
 * </p>
 * <p>
 * <b>Note</b> if no version is defined in the report plugin, the version will be searched with
 * {@link #resolvePluginVersion(ReportPlugin, MavenReportExecutorRequest) resolvePluginVersion(...)} method:
 * </p>
 * <ol>
 * <li>use the one defined in the reportPlugin configuration,</li>
 * <li>search similar (same groupId and artifactId) plugin in the build/plugins section of the pom,</li>
 * <li>search similar (same groupId and artifactId) plugin in the build/pluginManagement section of the pom,</li>
 * <li>ask {@link PluginVersionResolver} to get a fallback version (display a warning as it's not a recommended use).
 * </li>
 * </ol>
 * <p>
 * Following steps are done:
 * </p>
 * <ul>
 * <li>get {@link PluginDescriptor} from the {@link MavenPluginManager} (through
 * {@link MavenPluginManagerHelper#getPluginDescriptor(Plugin, org.apache.maven.execution.MavenSession)
 * MavenPluginManagerHelper.getPluginDescriptor(...)} to protect from core API change)</li>
 * <li>setup a {@link ClassLoader}, with the Site plugin classloader as parent for the report execution. <br>
 * Notice that some classes are imported from the current Site plugin ClassRealm: see {@link #IMPORTS}. Corresponding
 * artifacts are excluded from the artifact resolution: <code>doxia-site-renderer</code>, <code>doxia-sink-api</code>
 *  and <code>maven-reporting-api</code>.<br>
 * Work is done using {@link MavenPluginManager} (through
 * {@link MavenPluginManagerHelper#setupPluginRealm(PluginDescriptor, MavenSession, ClassLoader, List, List)
 * MavenPluginManagerHelper.setupPluginRealm(...)} to protect from core API change)</li>
 * <li>setup the mojo using {@link MavenPluginManager#getConfiguredMojo(Class, MavenSession, MojoExecution)
 * MavenPluginManager.getConfiguredMojo(...)}</li>
 * <li>verify with {@link LifecycleExecutor#calculateForkedExecutions(MojoExecution, MavenSession)
 * LifecycleExecutor.calculateForkedExecutions(...)} if any forked execution is needed: if yes, execute the forked
 * execution here</li>
 * </ul>
 *
 * @author Olivier Lamy
 */
@Singleton
@Named
public class DefaultMavenReportExecutor implements MavenReportExecutor {
    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultMavenReportExecutor.class);

    private final MavenPluginManager mavenPluginManager;

    private final MavenPluginManagerHelper mavenPluginManagerHelper;

    private final LifecycleExecutor lifecycleExecutor;

    private final PluginVersionResolver pluginVersionResolver;

    private static final List<String> IMPORTS = Arrays.asList(
            "org.apache.maven.reporting.MavenReport",
            "org.apache.maven.reporting.MavenMultiPageReport",
            // TODO Will be removed after Doxia 2.0.0
            "org.apache.maven.doxia.siterenderer.Renderer",
            "org.apache.maven.doxia.siterenderer.SiteRenderer",
            "org.apache.maven.doxia.sink.SinkFactory",
            // TODO Will be removed after Doxia 2.0.0
            "org.codehaus.doxia.sink.Sink",
            "org.apache.maven.doxia.sink.Sink",
            "org.apache.maven.doxia.sink.SinkEventAttributes",
            // TODO Will be removed with Doxia 2.0.0
            "org.apache.maven.doxia.logging.LogEnabled",
            // TODO Will be removed with Doxia 2.0.0
            "org.apache.maven.doxia.logging.Log");

    private static final List<String> EXCLUDES =
            Arrays.asList("doxia-site-renderer", "doxia-sink-api", "maven-reporting-api");

    @Inject
    public DefaultMavenReportExecutor(
            MavenPluginManager mavenPluginManager,
            MavenPluginManagerHelper mavenPluginManagerHelper,
            LifecycleExecutor lifecycleExecutor,
            PluginVersionResolver pluginVersionResolver) {
        this.mavenPluginManager = requireNonNull(mavenPluginManager);
        this.mavenPluginManagerHelper = requireNonNull(mavenPluginManagerHelper);
        this.lifecycleExecutor = requireNonNull(lifecycleExecutor);
        this.pluginVersionResolver = requireNonNull(pluginVersionResolver);
    }

    @Override
    public List<MavenReportExecution> buildMavenReports(MavenReportExecutorRequest mavenReportExecutorRequest)
            throws MojoExecutionException {
        if (mavenReportExecutorRequest.getReportPlugins() == null) {
            return Collections.emptyList();
        }

        Set<String> reportPluginKeys = new HashSet<>();
        List<MavenReportExecution> reportExecutions = new ArrayList<>();

        String pluginKey = "";
        try {
            for (ReportPlugin reportPlugin : mavenReportExecutorRequest.getReportPlugins()) {
                pluginKey = reportPlugin.getGroupId() + ':' + reportPlugin.getArtifactId();

                if (!reportPluginKeys.add(pluginKey)) {
                    LOGGER.info("Plugin {} will be executed more than one time", pluginKey);
                }

                reportExecutions.addAll(buildReportPlugin(mavenReportExecutorRequest, reportPlugin));
            }
        } catch (Exception e) {
            throw new MojoExecutionException("Failed to get report for " + pluginKey, e);
        }

        return reportExecutions;
    }

    protected List<MavenReportExecution> buildReportPlugin(
            MavenReportExecutorRequest mavenReportExecutorRequest, ReportPlugin reportPlugin) throws Exception {
        // step 1: prepare the plugin
        Plugin plugin = new Plugin();
        plugin.setGroupId(reportPlugin.getGroupId());
        plugin.setArtifactId(reportPlugin.getArtifactId());
        plugin.setVersion(resolvePluginVersion(reportPlugin, mavenReportExecutorRequest));
        LOGGER.info("Configuring report plugin {}:{}", plugin.getArtifactId(), plugin.getVersion());

        mergePluginToReportPlugin(mavenReportExecutorRequest, plugin, reportPlugin);

        PluginDescriptor pluginDescriptor =
                mavenPluginManagerHelper.getPluginDescriptor(plugin, mavenReportExecutorRequest.getMavenSession());

        // step 2: prepare the goals
        List<GoalWithConf> goalsWithConfiguration = new ArrayList<>();
        boolean hasUserDefinedReports = prepareGoals(reportPlugin, pluginDescriptor, goalsWithConfiguration);

        // step 3: prepare the reports
        List<MavenReportExecution> reports = new ArrayList<>(goalsWithConfiguration.size());
        for (GoalWithConf report : goalsWithConfiguration) {
            MavenReportExecution mavenReportExecution =
                    prepareReportExecution(mavenReportExecutorRequest, report, hasUserDefinedReports);

            if (mavenReportExecution != null) {
                // ok, report is ready to generate
                reports.add(mavenReportExecution);
            }
        }

        if (!reports.isEmpty()) {
            // log reports, either configured or detected
            StringBuilder buff = new StringBuilder();
            for (MavenReportExecution mre : reports) {
                if (buff.length() > 0) {
                    buff.append(", ");
                }
                buff.append(mre.getGoal());
            }
            LOGGER.info(
                    "{} {} report{} for {}:{}: {}",
                    (hasUserDefinedReports ? "Configured" : "Detected"),
                    reports.size(),
                    (reports.size() > 1 ? "s" : ""),
                    plugin.getArtifactId(),
                    plugin.getVersion(),
                    buff);
        } else if (!hasUserDefinedReports) {
            LOGGER.warn(
                    "Ignoring report plugin {}:{},"
                            + " it does not contain any report goals: should be removed from reporting configuration in POM",
                    plugin.getArtifactId(),
                    plugin.getVersion());
        }

        return reports;
    }

    private boolean prepareGoals(
            ReportPlugin reportPlugin, PluginDescriptor pluginDescriptor, List<GoalWithConf> goalsWithConfiguration) {
        if (reportPlugin.getReportSets().isEmpty() && reportPlugin.getReports().isEmpty()) {
            // by default, use every goal which will be filtered later to only keep reporting goals
            List<MojoDescriptor> mojoDescriptors = pluginDescriptor.getMojos();
            for (MojoDescriptor mojoDescriptor : mojoDescriptors) {
                goalsWithConfiguration.add(new GoalWithConf(
                        reportPlugin, pluginDescriptor, mojoDescriptor.getGoal(), mojoDescriptor.getConfiguration()));
            }

            return false;
        }

        Set<String> goals = new HashSet<>();
        for (String report : reportPlugin.getReports()) {
            if (goals.add(report)) {
                goalsWithConfiguration.add(
                        new GoalWithConf(reportPlugin, pluginDescriptor, report, reportPlugin.getConfiguration()));
            } else {
                LOGGER.warn("{} report is declared twice in default reports", report);
            }
        }

        for (ReportSet reportSet : reportPlugin.getReportSets()) {
            goals = new HashSet<>();
            for (String report : reportSet.getReports()) {
                if (goals.add(report)) {
                    goalsWithConfiguration.add(
                            new GoalWithConf(reportPlugin, pluginDescriptor, report, reportSet.getConfiguration()));
                } else {
                    LOGGER.warn("{} report is declared twice in {} reportSet", report, reportSet.getId());
                }
            }
        }

        return true;
    }

    private MavenReportExecution prepareReportExecution(
            MavenReportExecutorRequest mavenReportExecutorRequest, GoalWithConf report, boolean userDefined)
            throws Exception {
        ReportPlugin reportPlugin = report.getReportPlugin();
        PluginDescriptor pluginDescriptor = report.getPluginDescriptor();

        MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(report.getGoal());
        if (mojoDescriptor == null) {
            throw new MojoNotFoundException(report.getGoal(), pluginDescriptor);
        }

        MavenProject project = mavenReportExecutorRequest.getProject();
        if (!userDefined && mojoDescriptor.isAggregator() && !canAggregate(project)) {
            // aggregator mojos automatically added from plugin are only run at execution root
            return null;
        }

        MojoExecution mojoExecution = new MojoExecution(
                pluginDescriptor.getPlugin(), report.getGoal(), mavenReportExecutorRequest.getExecutionId());

        mojoExecution.setMojoDescriptor(mojoDescriptor);

        mavenPluginManagerHelper.setupPluginRealm(
                pluginDescriptor,
                mavenReportExecutorRequest.getMavenSession(),
                Thread.currentThread().getContextClassLoader(),
                IMPORTS,
                EXCLUDES);

        if (!isMavenReport(mojoExecution, pluginDescriptor)) {
            if (userDefined) {
                // reports were explicitly written in the POM
                LOGGER.warn(
                        "Ignoring {}:{}"
                                + " goal since it is not a report: should be removed from reporting configuration in POM",
                        mojoExecution.getPlugin().getId(),
                        report.getGoal());
            } else {
                LOGGER.debug(
                        "Ignoring {}:{} goal since it is not a report",
                        mojoExecution.getPlugin().getId(),
                        report.getGoal());
            }
            return null;
        }

        Xpp3Dom pluginMgmtConfiguration = null;
        if (project.getBuild() != null && project.getBuild().getPluginManagement() != null) {
            Plugin pluginMgmt =
                    find(reportPlugin, project.getBuild().getPluginManagement().getPlugins());

            if (pluginMgmt != null) {
                pluginMgmtConfiguration = (Xpp3Dom) pluginMgmt.getConfiguration();
            }
        }

        mojoExecution.setConfiguration(mergeConfiguration(
                mojoDescriptor.getMojoConfiguration(),
                pluginMgmtConfiguration,
                reportPlugin.getConfiguration(),
                report.getConfiguration(),
                mojoDescriptor.getParameterMap().keySet()));

        MavenReport mavenReport = getConfiguredMavenReport(mojoExecution, pluginDescriptor, mavenReportExecutorRequest);

        MavenReportExecution mavenReportExecution = new MavenReportExecution(
                report.getGoal(),
                mojoExecution.getPlugin(),
                mavenReport,
                pluginDescriptor.getClassRealm(),
                userDefined);

        lifecycleExecutor.calculateForkedExecutions(mojoExecution, mavenReportExecutorRequest.getMavenSession());

        if (!mojoExecution.getForkedExecutions().isEmpty()) {
            String reportDescription = pluginDescriptor.getArtifactId() + ":" + report.getGoal() + " report";

            String execution;
            if (StringUtils.isNotEmpty(mojoDescriptor.getExecutePhase())) {
                // forked phase
                execution = "'"
                        + (StringUtils.isEmpty(mojoDescriptor.getExecuteLifecycle())
                                ? ""
                                : ('[' + mojoDescriptor.getExecuteLifecycle() + ']'))
                        + mojoDescriptor.getExecutePhase() + "' forked phase execution";
            } else {
                // forked goal
                execution = "'" + mojoDescriptor.getExecuteGoal() + "' forked goal execution";
            }

            LOGGER.info("Preparing {} requires {}", reportDescription, execution);

            lifecycleExecutor.executeForkedExecutions(mojoExecution, mavenReportExecutorRequest.getMavenSession());

            LOGGER.info("{} for {} preparation done", execution, reportDescription);
        }

        return mavenReportExecution;
    }

    private boolean canAggregate(MavenProject project) {
        return project.isExecutionRoot()
                && "pom".equals(project.getPackaging())
                && (project.getModules() != null)
                && !project.getModules().isEmpty();
    }

    private MavenReport getConfiguredMavenReport(
            MojoExecution mojoExecution,
            PluginDescriptor pluginDescriptor,
            MavenReportExecutorRequest mavenReportExecutorRequest)
            throws PluginContainerException, PluginConfigurationException {
        try {
            Mojo mojo = mavenPluginManager.getConfiguredMojo(
                    Mojo.class, mavenReportExecutorRequest.getMavenSession(), mojoExecution);

            return (MavenReport) mojo;
        } catch (ClassCastException e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.warn("Skipping ClassCastException", e);
            } else {
                LOGGER.warn("Skipping ClassCastException");
            }
            return null;
        }
    }

    private boolean isMavenReport(MojoExecution mojoExecution, PluginDescriptor pluginDescriptor) {
        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();

        // get the plugin's goal Mojo class
        Class<?> mojoClass;
        try {
            Thread.currentThread()
                    .setContextClassLoader(mojoExecution.getMojoDescriptor().getRealm());

            mojoClass = pluginDescriptor
                    .getClassRealm()
                    .loadClass(mojoExecution.getMojoDescriptor().getImplementation());
        } catch (ClassNotFoundException e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.warn("Skipping ClassNotFoundException mojoExecution.goal {}", mojoExecution.getGoal(), e);
            } else {
                LOGGER.warn("Skipping ClassNotFoundException mojoExecution.goal {}", mojoExecution.getGoal());
            }
            return false;
        } finally {
            Thread.currentThread().setContextClassLoader(originalClassLoader);
        }

        // check if it is a report
        try {
            Thread.currentThread()
                    .setContextClassLoader(mojoExecution.getMojoDescriptor().getRealm());
            MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(mojoExecution.getGoal());

            boolean isMavenReport = MavenReport.class.isAssignableFrom(mojoClass);

            if (LOGGER.isDebugEnabled()) {
                if (mojoDescriptor != null && mojoDescriptor.getImplementationClass() != null) {
                    LOGGER.debug(
                            "Class {} is {}a MavenReport",
                            mojoDescriptor.getImplementationClass().getName(),
                            isMavenReport ? "" : "NOT ");
                }

                if (!isMavenReport) {
                    LOGGER.debug(
                            "Skipping non-MavenReport {}",
                            mojoExecution.getMojoDescriptor().getId());
                }
            }

            return isMavenReport;
        } catch (LinkageError e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.warn("Skipping LinkageError mojoExecution.goal {}", mojoExecution.getGoal(), e);
            } else {
                LOGGER.warn("Skipping LinkageError mojoExecution.goal {}", mojoExecution.getGoal());
            }
            return false;
        } finally {
            Thread.currentThread().setContextClassLoader(originalClassLoader);
        }
    }

    /**
     * Merge plugin configuration and reportset configuration to mojo configuration to get effective
     * mojo configuration.
     *
     * @param mojoConf configuration done at mojo descriptor level
     * @param pluginMgmtConfig configuration done at build.pluginManagement level
     * @param pluginConf configuration done at reporting plugin level
     * @param reportSetConf configuration done at reportSet level
     * @param parameters set of supported parameters: any other parameter will be removed
     * @return the effective configuration to be used
     */
    private Xpp3Dom mergeConfiguration(
            PlexusConfiguration mojoConf,
            Xpp3Dom pluginMgmtConfig,
            PlexusConfiguration pluginConf,
            PlexusConfiguration reportSetConf,
            Set<String> parameters) {
        Xpp3Dom mojoConfig = (mojoConf != null) ? convert(mojoConf) : new Xpp3Dom("configuration");

        if (pluginMgmtConfig != null || pluginConf != null || reportSetConf != null) {
            Xpp3Dom pluginConfig = (pluginConf == null) ? new Xpp3Dom("fake") : convert(pluginConf);

            // merge pluginConf into reportSetConf
            Xpp3Dom mergedConfig = Xpp3DomUtils.mergeXpp3Dom(convert(reportSetConf), pluginConfig);
            // then merge pluginMgmtConfig
            mergedConfig = Xpp3DomUtils.mergeXpp3Dom(mergedConfig, pluginMgmtConfig);
            // then merge mojoConf
            mergedConfig = Xpp3DomUtils.mergeXpp3Dom(mergedConfig, mojoConfig);

            // clean result
            Xpp3Dom cleanedConfig = new Xpp3Dom("configuration");
            if (mergedConfig.getChildren() != null) {
                for (Xpp3Dom parameter : mergedConfig.getChildren()) {
                    if (parameters.contains(parameter.getName())) {
                        cleanedConfig.addChild(parameter);
                    }
                }
            }

            mojoConfig = cleanedConfig;
        }

        return mojoConfig;
    }

    private Xpp3Dom convert(PlexusConfiguration config) {
        if (config == null) {
            return null;
        }

        Xpp3Dom dom = new Xpp3Dom(config.getName());
        dom.setValue(config.getValue(null));

        for (String attrib : config.getAttributeNames()) {
            dom.setAttribute(attrib, config.getAttribute(attrib, null));
        }

        for (int n = config.getChildCount(), i = 0; i < n; i++) {
            dom.addChild(convert(config.getChild(i)));
        }

        return dom;
    }

    /**
     * Resolve report plugin version. Steps to find a plugin version stop after each step if a non <code>null</code>
     * value has been found:
     * <ol>
     * <li>use the one defined in the reportPlugin configuration,</li>
     * <li>search similar (same groupId and artifactId) mojo in the build/plugins section of the pom,</li>
     * <li>search similar (same groupId and artifactId) mojo in the build/pluginManagement section of the pom,</li>
     * <li>ask {@link PluginVersionResolver} to get a fallback version and display a warning as it's not a recommended
     * use.</li>
     * </ol>
     *
     * @param reportPlugin the report plugin to resolve the version
     * @param mavenReportExecutorRequest the current report execution context
     * @return the report plugin version
     * @throws PluginVersionResolutionException on plugin version resolution issue
     */
    protected String resolvePluginVersion(
            ReportPlugin reportPlugin, MavenReportExecutorRequest mavenReportExecutorRequest)
            throws PluginVersionResolutionException {
        String reportPluginKey = reportPlugin.getGroupId() + ':' + reportPlugin.getArtifactId();
        LOGGER.debug("Resolving version for {}", reportPluginKey);

        // look for version defined in the reportPlugin configuration
        if (reportPlugin.getVersion() != null) {
            LOGGER.debug(
                    "Resolved {} version from the reporting.plugins section: {}",
                    reportPluginKey,
                    reportPlugin.getVersion());
            return reportPlugin.getVersion();
        }

        MavenProject project = mavenReportExecutorRequest.getProject();

        // search in the build section
        if (project.getBuild() != null) {
            Plugin plugin = find(reportPlugin, project.getBuild().getPlugins());

            if (plugin != null && plugin.getVersion() != null) {
                LOGGER.debug(
                        "Resolved {} version from the build.plugins section: {}", reportPluginKey, plugin.getVersion());
                return plugin.getVersion();
            }
        }

        // search in pluginManagement section
        if (project.getBuild() != null && project.getBuild().getPluginManagement() != null) {
            Plugin plugin =
                    find(reportPlugin, project.getBuild().getPluginManagement().getPlugins());

            if (plugin != null && plugin.getVersion() != null) {
                LOGGER.debug(
                        "Resolved {} version from the build.pluginManagement.plugins section: {}",
                        reportPluginKey,
                        plugin.getVersion());
                return plugin.getVersion();
            }
        }

        LOGGER.warn("Report plugin {} has an empty version.", reportPluginKey);
        LOGGER.warn("");
        LOGGER.warn("It is highly recommended to fix these problems"
                + " because they threaten the stability of your build.");
        LOGGER.warn("");
        LOGGER.warn("For this reason, future Maven versions might no"
                + " longer support building such malformed projects.");

        Plugin plugin = new Plugin();
        plugin.setGroupId(reportPlugin.getGroupId());
        plugin.setArtifactId(reportPlugin.getArtifactId());

        PluginVersionRequest pluginVersionRequest =
                new DefaultPluginVersionRequest(plugin, mavenReportExecutorRequest.getMavenSession());

        PluginVersionResult result = pluginVersionResolver.resolve(pluginVersionRequest);
        LOGGER.debug("Resolved {} version from repository: {}", reportPluginKey, result.getVersion());
        return result.getVersion();
    }

    /**
     * Search similar (same groupId and artifactId) plugin as a given report plugin.
     *
     * @param reportPlugin the report plugin to search for a similar plugin
     * @param plugins the candidate plugins
     * @return the first similar plugin
     */
    private Plugin find(ReportPlugin reportPlugin, List<Plugin> plugins) {
        if (plugins == null) {
            return null;
        }
        for (Plugin plugin : plugins) {
            if (Objects.equals(plugin.getArtifactId(), reportPlugin.getArtifactId())
                    && Objects.equals(plugin.getGroupId(), reportPlugin.getGroupId())) {
                return plugin;
            }
        }
        return null;
    }

    /**
     * TODO other stuff to merge ?
     * <p>
     * this method will "merge" some part of the plugin declaration existing in the build section to the fake plugin
     * build for report execution:
     * <ul>
     * <li>dependencies</li>
     * </ul>
     * </p>
     * The plugin could only be present in the dependency management section.
     *
     * @param mavenReportExecutorRequest
     * @param buildPlugin
     * @param reportPlugin
     */
    private void mergePluginToReportPlugin(
            MavenReportExecutorRequest mavenReportExecutorRequest, Plugin buildPlugin, ReportPlugin reportPlugin) {
        Build build = mavenReportExecutorRequest.getProject().getBuild();
        Plugin configuredPlugin = find(reportPlugin, build.getPlugins());
        if (configuredPlugin == null && build.getPluginManagement() != null) {
            configuredPlugin = find(reportPlugin, build.getPluginManagement().getPlugins());
        }
        if (configuredPlugin != null) {
            if (!configuredPlugin.getDependencies().isEmpty()) {
                buildPlugin.getDependencies().addAll(configuredPlugin.getDependencies());
            }
        }
    }

    private static class GoalWithConf {
        private final String goal;

        private final PlexusConfiguration configuration;

        private final ReportPlugin reportPlugin;

        private final PluginDescriptor pluginDescriptor;

        GoalWithConf(
                ReportPlugin reportPlugin,
                PluginDescriptor pluginDescriptor,
                String goal,
                PlexusConfiguration configuration) {
            this.reportPlugin = reportPlugin;
            this.pluginDescriptor = pluginDescriptor;
            this.goal = goal;
            this.configuration = configuration;
        }

        public ReportPlugin getReportPlugin() {
            return reportPlugin;
        }

        public PluginDescriptor getPluginDescriptor() {
            return pluginDescriptor;
        }

        public String getGoal() {
            return goal;
        }

        public PlexusConfiguration getConfiguration() {
            return configuration;
        }
    }
}
