/*
 * Copyright 2012 the original author or authors.
 *
 * 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.gradle.api.reporting;

import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.Incubating;
import org.gradle.api.NamedDomainObjectSet;
import org.gradle.api.Transformer;
import org.gradle.api.internal.ClosureBackedAction;
import org.gradle.api.reporting.internal.BuildDashboardGenerator;
import org.gradle.api.reporting.internal.DefaultBuildDashboardReports;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.TaskAction;
import org.gradle.internal.reflect.Instantiator;
import org.gradle.util.CollectionUtils;

import javax.inject.Inject;
import java.io.File;
import java.io.Serializable;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Generates build dashboard report.
 */
@Incubating
public class GenerateBuildDashboard extends DefaultTask implements Reporting<BuildDashboardReports> {
    private Set<Reporting<? extends ReportContainer<?>>> aggregated = new LinkedHashSet<Reporting<? extends ReportContainer<?>>>();

    private final BuildDashboardReports reports;

    public GenerateBuildDashboard() {
        reports = getInstantiator().newInstance(DefaultBuildDashboardReports.class, this);
        reports.getHtml().setEnabled(true);
    }

    @Inject
    protected Instantiator getInstantiator() {
        throw new UnsupportedOperationException();
    }

    @Input
    public Set<ReportState> getInputReports() {
        Set<ReportState> inputs = new LinkedHashSet<ReportState>();
        for (Report report : getEnabledInputReports()) {
            if (getReports().contains(report)) {
                // A report to be generated, ignore
                continue;
            }
            inputs.add(new ReportState(report.getDisplayName(), report.getDestination(), report.getDestination().exists()));
        }
        return inputs;
    }

    @Internal
    private Set<Report> getEnabledInputReports() {
        Set<NamedDomainObjectSet<? extends Report>> enabledReportSets = CollectionUtils.collect(aggregated, new Transformer<NamedDomainObjectSet<? extends Report>, Reporting<? extends ReportContainer<?>>>() {
            public NamedDomainObjectSet<? extends Report> transform(Reporting<? extends ReportContainer<?>> reporting) {
                return reporting.getReports().getEnabled();
            }
        });
        return new LinkedHashSet<Report>(CollectionUtils.flattenCollections(Report.class, enabledReportSets));
    }

    /**
     * Configures which reports are to be aggregated in the build dashboard report generated by this task.
     *
     * <pre>
     * buildDashboard {
     *   aggregate codenarcMain, checkstyleMain
     * }
     * </pre>
     *
     * @param reportings an array of {@link Reporting} instances that are to be aggregated
     */
    public void aggregate(Reporting<? extends ReportContainer<?>>... reportings) {
        aggregated.addAll(Arrays.asList(reportings));
    }

    /**
     * The reports to be generated by this task.
     *
     * @return The reports container
     */
    @Nested
    @Override
    public BuildDashboardReports getReports() {
        return reports;
    }

    /**
     * Configures the reports to be generated by this task.
     *
     * The contained reports can be configured by name and closures.
     *
     * <pre>
     * buildDashboard {
     *   reports {
     *     html {
     *       destination "build/dashboard.html"
     *     }
     *   }
     * }
     * </pre>
     *
     * @param closure The configuration
     * @return The reports container
     */
    public BuildDashboardReports reports(Closure closure) {
        return reports(new ClosureBackedAction<BuildDashboardReports>(closure));
    }

    /**
     * Configures the reports to be generated by this task.
     *
     * The contained reports can be configured by name and closures.
     *
     * <pre>
     * buildDashboard {
     *   reports {
     *     html {
     *       destination "build/dashboard.html"
     *     }
     *   }
     * }
     * </pre>
     *
     * @param configureAction The configuration
     * @return The reports container
     */
    public BuildDashboardReports reports(Action<? super BuildDashboardReports> configureAction) {
        configureAction.execute(reports);
        return reports;
    }

    @TaskAction
    void run() {
        if (getReports().getHtml().isEnabled()) {
            BuildDashboardGenerator generator = new BuildDashboardGenerator();
            generator.render(getEnabledInputReports(), reports.getHtml().getEntryPoint());
        } else {
            setDidWork(false);
        }
    }

    private static class ReportState implements Serializable {
        private final String name;
        private final File destination;
        private final boolean available;

        private ReportState(String name, File destination, boolean available) {
            this.name = name;
            this.destination = destination;
            this.available = available;
        }

        @Override
        public boolean equals(Object obj) {
            ReportState other = (ReportState) obj;
            return name.equals(other.name) && destination.equals(other.destination) && available == other.available;
        }

        @Override
        public int hashCode() {
            return name.hashCode() ^ destination.hashCode();
        }
    }
}
