package org.apache.maven.plugins.invoker;

/*
 * 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.
 */

import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.plugins.invoker.model.BuildJob;
import org.apache.maven.plugins.invoker.model.io.xpp3.BuildJobXpp3Reader;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * Generate a report based on the results of the Maven invocations. <strong>Note:</strong> This mojo doesn't fork any
 * lifecycle, if you have a clean working copy, you have to use a command like
 * <code>mvn clean integration-test site</code> to ensure the build results are present when this goal is invoked.
 *
 * @author Olivier Lamy
 * @since 1.4
 */
@Mojo( name = "report", threadSafe = true )
public class InvokerReport
    extends AbstractMavenReport
{

    /**
     * The Maven Project.
     */
    @Parameter( defaultValue = "${project}", readonly = true, required = true )
    protected MavenProject project;

    /**
     * Doxia Site Renderer component.
     */
    @Component
    protected Renderer siteRenderer;

    /**
     * Internationalization component.
     */
    @Component
    protected I18N i18n;

    /**
     * The output directory for the report. Note that this parameter is only evaluated if the goal is run directly from
     * the command line. If the goal is run indirectly as part of a site generation, the output directory configured in
     * the Maven Site Plugin is used instead.
     */
    @Parameter( defaultValue = "${project.reporting.outputDirectory}", required = true )
    protected File outputDirectory;

    /**
     * Base directory where all build reports have been written to.
     */
    @Parameter( defaultValue = "${project.build.directory}/invoker-reports", property = "invoker.reportsDirectory" )
    private File reportsDirectory;

    /**
     * The number format used to print percent values in the report locale.
     */
    private NumberFormat percentFormat;

    /**
     * The number format used to print time values in the report locale.
     */
    private NumberFormat secondsFormat;

    protected void executeReport( Locale locale )
        throws MavenReportException
    {
        DecimalFormatSymbols symbols = new DecimalFormatSymbols( locale );
        percentFormat = new DecimalFormat( getText( locale, "report.invoker.format.percent" ), symbols );
        secondsFormat = new DecimalFormat( getText( locale, "report.invoker.format.seconds" ), symbols );

        Sink sink = getSink();

        sink.head();

        sink.title();
        sink.text( getText( locale, "report.invoker.result.title" ) );
        sink.title_();

        sink.head_();

        sink.body();

        sink.section1();
        sink.sectionTitle1();
        sink.text( getText( locale, "report.invoker.result.title" ) );
        sink.sectionTitle1_();
        sink.paragraph();
        sink.text( getText( locale, "report.invoker.result.description" ) );
        sink.paragraph_();
        sink.section1_();

        // ----------------------------------
        // build buildJob beans
        // ----------------------------------
        File[] reportFiles = ReportUtils.getReportFiles( reportsDirectory );
        if ( reportFiles.length <= 0 )
        {
            getLog().info( "no invoker report files found, skip report generation" );
            return;
        }

        List<BuildJob> buildJobs = new ArrayList<BuildJob>( reportFiles.length );
        for ( File reportFile : reportFiles )
        {
            try
            {
                BuildJobXpp3Reader reader = new BuildJobXpp3Reader();
                buildJobs.add( reader.read( ReaderFactory.newXmlReader( reportFile ) ) );
            }
            catch ( XmlPullParserException e )
            {
                throw new MavenReportException( "Failed to parse report file: " + reportFile, e );
            }
            catch ( IOException e )
            {
                throw new MavenReportException( "Failed to read report file: " + reportFile, e );
            }
        }

        // ----------------------------------
        // summary
        // ----------------------------------

        constructSummarySection( buildJobs, locale );

        // ----------------------------------
        // per file/it detail
        // ----------------------------------

        sink.section2();
        sink.sectionTitle2();

        sink.text( getText( locale, "report.invoker.detail.title" ) );

        sink.sectionTitle2_();

        sink.section2_();

        // detail tests table header
        sink.table();

        sink.tableRow();
        // -------------------------------------------
        // name | Result | time | message
        // -------------------------------------------
        sinkTableHeader( sink, getText( locale, "report.invoker.detail.name" ) );
        sinkTableHeader( sink, getText( locale, "report.invoker.detail.result" ) );
        sinkTableHeader( sink, getText( locale, "report.invoker.detail.time" ) );
        sinkTableHeader( sink, getText( locale, "report.invoker.detail.message" ) );

        sink.tableRow_();

        for ( BuildJob buildJob : buildJobs )
        {
            renderBuildJob( buildJob, locale );
        }

        sink.table_();

        sink.body_();

        sink.flush();
        sink.close();
    }

    private void constructSummarySection( List<? extends BuildJob> buildJobs, Locale locale )
    {
        Sink sink = getSink();

        sink.section2();
        sink.sectionTitle2();

        sink.text( getText( locale, "report.invoker.summary.title" ) );

        sink.sectionTitle2_();
        sink.section2_();

        // ------------------------------------------------------------------------
        // Building a table with
        // it number | succes nb | failed nb | Success rate | total time | avg time
        // ------------------------------------------------------------------------

        sink.table();
        sink.tableRow();

        sinkTableHeader( sink, getText( locale, "report.invoker.summary.number" ) );
        sinkTableHeader( sink, getText( locale, "report.invoker.summary.success" ) );
        sinkTableHeader( sink, getText( locale, "report.invoker.summary.failed" ) );
        sinkTableHeader( sink, getText( locale, "report.invoker.summary.skipped" ) );
        sinkTableHeader( sink, getText( locale, "report.invoker.summary.success.rate" ) );
        sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.total" ) );
        sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.avg" ) );

        int number = buildJobs.size();
        int success = 0;
        int failed = 0;
        int skipped = 0;
        double totalTime = 0;

        for ( BuildJob buildJob : buildJobs )
        {
            if ( BuildJob.Result.SUCCESS.equals( buildJob.getResult() ) )
            {
                success++;
            }
            else if ( BuildJob.Result.SKIPPED.equals( buildJob.getResult() ) )
            {
                skipped++;
            }
            else
            {
                failed++;
            }
            totalTime += buildJob.getTime();
        }

        sink.tableRow_();
        sink.tableRow();

        sinkCell( sink, Integer.toString( number ) );
        sinkCell( sink, Integer.toString( success ) );
        sinkCell( sink, Integer.toString( failed ) );
        sinkCell( sink, Integer.toString( skipped ) );

        if ( success + failed > 0 )
        {
            sinkCell( sink, percentFormat.format( (double) success / ( success + failed ) ) );
        }
        else
        {
            sinkCell( sink, "" );
        }

        sinkCell( sink, secondsFormat.format( totalTime ) );

        sinkCell( sink, secondsFormat.format( totalTime / number ) );

        sink.tableRow_();
        sink.table_();

    }

    private void renderBuildJob( BuildJob buildJob, Locale locale )
    {
        Sink sink = getSink();
        sink.tableRow();
        StringBuilder buffer = new StringBuilder();
        if ( !StringUtils.isEmpty( buildJob.getName() ) && !StringUtils.isEmpty( buildJob.getDescription() ) )
        {
            buffer.append( buildJob.getName() );
            buffer.append( " : " );
            buffer.append( buildJob.getDescription() );
        }
        else
        {
            buffer.append( buildJob.getProject() );
        }
        sinkCell( sink, buffer.toString() );
        // FIXME image
        sinkCell( sink, buildJob.getResult() );
        sinkCell( sink, secondsFormat.format( buildJob.getTime() ) );
        sinkCell( sink, buildJob.getFailureMessage() );
        sink.tableRow_();
    }

    protected String getOutputDirectory()
    {
        return outputDirectory.getAbsolutePath();
    }

    protected MavenProject getProject()
    {
        return project;
    }

    protected Renderer getSiteRenderer()
    {
        return siteRenderer;
    }

    public String getDescription( Locale locale )
    {
        return getText( locale, "report.invoker.result.description" );
    }

    public String getName( Locale locale )
    {
        return getText( locale, "report.invoker.result.name" );
    }

    public String getOutputName()
    {
        return "invoker-report";
    }

    public boolean canGenerateReport()
    {
        return ReportUtils.getReportFiles( reportsDirectory ).length > 0;
    }

    private String getText( Locale locale, String key )
    {
        return i18n.getString( "invoker-report", locale, key );
    }

    private void sinkTableHeader( Sink sink, String header )
    {
        sink.tableHeaderCell();
        sink.text( header );
        sink.tableHeaderCell_();
    }

    private void sinkCell( Sink sink, String text )
    {
        sink.tableCell();
        sink.text( text );
        sink.tableCell_();
    }

}
