/*
 * Copyright 2011 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.plugins.quality

import org.gradle.api.GradleException
import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.quality.internal.FindBugsReportsImpl
import org.gradle.api.plugins.quality.internal.findbugs.FindBugsWorkerManager
import org.gradle.api.plugins.quality.internal.findbugs.FindBugsResult
import org.gradle.api.plugins.quality.internal.findbugs.FindBugsSpec
import org.gradle.api.plugins.quality.internal.findbugs.FindBugsSpecBuilder
import org.gradle.api.reporting.Reporting
import org.gradle.api.tasks.*
import org.gradle.api.logging.LogLevel
import org.gradle.internal.reflect.Instantiator
import org.gradle.logging.ConsoleRenderer

/**
 * Analyzes code with <a href="http://findbugs.sourceforge.net">FindBugs</a>.
 */
class FindBugs extends SourceTask implements VerificationTask, Reporting<FindBugsReports> {
    /**
     * The classes to be analyzed.
     */
    @SkipWhenEmpty
    @InputFiles
    FileCollection classes

    /**
     * Compile class path for the classes to be analyzed.
     * The classes on this class path are used during analysis
     * but aren't analyzed themselves.
     */
    @InputFiles
    FileCollection classpath

    /**
     * Class path holding the FindBugs library.
     */
    @InputFiles
    FileCollection findbugsClasspath

    /**
     * Class path holding any additional FindBugs plugins.
     */
    @InputFiles
    FileCollection pluginClasspath

    /**
     * Whether or not to allow the build to continue if there are warnings.
     */
    boolean ignoreFailures

    @Nested
    private final FindBugsReportsImpl reports = services.get(Instantiator).newInstance(FindBugsReportsImpl, this)

    /**
     * The reports to be generated by this task.
     *
     * @return The reports container
     */
    FindBugsReports getReports() {
        reports
    }

    /**
     * Configures the reports to be generated by this task.
     *
     * The contained reports can be configured by name and closures. Example:
     *
     * <pre>
     * findbugsTask {
     *   reports {
     *     xml {
     *       destination "build/findbugs.xml"
     *     }
     *   }
     * }
     * </pre>
     *
     * @param closure The configuration
     * @return The reports container
     */
    FindBugsReports reports(Closure closure) {
        reports.configure(closure)
    }

    @TaskAction
    void run() {
        FindBugsSpecBuilder argumentBuilder = new FindBugsSpecBuilder(getClasses())
                .withPluginsList(getPluginClasspath())
                .withSources(getSource())
                .withClasspath(getClasspath())
                .withDebugging(logger.isDebugEnabled())
                .configureReports(reports)

        FindBugsSpec spec = argumentBuilder.build()
        FindBugsWorkerManager manager = new FindBugsWorkerManager();

        logging.captureStandardOutput(LogLevel.DEBUG)
        logging.captureStandardError(LogLevel.DEBUG)

        FindBugsResult findbugsResult = manager.runWorker(getProject(), getFindbugsClasspath(), spec)
        evaluateResult(findbugsResult);
    }

    void evaluateResult(FindBugsResult findbugsResult) {
        if (findbugsResult.exception){
            throw new GradleException("FindBugs encountered an error. Run with --debug to get more information.", findbugsResult.exception)
        }
        if (findbugsResult.errorCount){
            throw new GradleException("FindBugs encountered an error. Run with --debug to get more information.")
        }
        if (findbugsResult.bugCount) {
            def message = "FindBugs rule violations were found."
            def report = reports.firstEnabled
            if (report) {
                def reportUrl = new ConsoleRenderer().asClickableFileUrl(report.destination)
                message += " See the report at: $reportUrl"
            }
            if (getIgnoreFailures()) {
                logger.warn(message)
            } else {
                throw new GradleException(message)
            }
        }
    }
}