/*
 * 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.felix.bundleplugin.baseline;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;

import org.apache.maven.doxia.sink.Sink;
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.reporting.MavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.codehaus.plexus.util.IOUtil;

import aQute.bnd.version.Version;

/**
 * BND Baseline report.
 *
 * @since 2.4.1
 */
@Mojo( name = "baseline-report", threadSafe = true,
       defaultPhase = LifecyclePhase.SITE)
public final class BaselineReport
    extends AbstractBaselinePlugin
    implements MavenReport
{

    /**
     * Specifies the directory where the report will be generated.
     */
    @Parameter(defaultValue = "${project.reporting.outputDirectory}")
    private File outputDirectory;

    private static final class Context {
        public Sink sink;

        public Locale locale;

        public int currentDepth = 0;
    }

    // AbstractBaselinePlugin events

    @Override
    protected Object init(final Object context)
    {
        failOnError = false;
        failOnWarning = false;

        final File baselineImagesDirectory = new File( outputDirectory, "images/baseline" );
        baselineImagesDirectory.mkdirs();

        for ( String resourceName : new String[]{ "access.gif",
                                                  "annotated.gif",
                                                  "annotation.gif",
                                                  "bundle.gif",
                                                  "class.gif",
                                                  "constant.gif",
                                                  "enum.gif",
                                                  "error.gif",
                                                  "extends.gif",
                                                  "field.gif",
                                                  "implements.gif",
                                                  "info.gif",
                                                  "interface.gif",
                                                  "method.gif",
                                                  "package.gif",
                                                  "resource.gif",
                                                  "return.gif",
                                                  "version.gif",
                                                  "warning.gif" } )
        {
            InputStream source = getClass().getResourceAsStream( resourceName );
            OutputStream target = null;
            File targetFile = new File( baselineImagesDirectory, resourceName );

            try
            {
                target = buildContext.newFileOutputStream( targetFile );
                IOUtil.copy( source, target );
            }
            catch ( IOException e )
            {
                getLog().warn( "Impossible to copy " + resourceName + " image, maybe the site won't be properly rendered." );
            }
            finally
            {
                IOUtil.close( source );
                IOUtil.close( target );
            }
        }
        return context;
    }

    @Override
    protected void close(final Object context)
    {
        // nothing to do
    }

    @Override
    protected void startBaseline( final Object context, String generationDate, String bundleName,
            String currentVersion, String previousVersion )
    {
        final Context ctx = (Context)context;
        final Sink sink = ctx.sink;

        sink.head();
        sink.title();

        String title = getBundle( ctx.locale ).getString( "report.baseline.title" );
        sink.text( title );
        sink.title_();
        sink.head_();

        sink.body();

        sink.section1();
        sink.sectionTitle1();
        sink.text( title );
        sink.sectionTitle1_();

        sink.paragraph();
        sink.text( getBundle( ctx.locale ).getString( "report.baseline.bndlink" ) + " " );
        sink.link( "http://www.aqute.biz/Bnd/Bnd" );
        sink.text( "Bnd" );
        sink.link_();
        sink.text( "." );
        sink.paragraph_();

        sink.paragraph();
        sink.text( getBundle( ctx.locale ).getString( "report.baseline.bundle" ) + " " );
        sink.figure();
        sink.figureGraphics( "images/baseline/bundle.gif" );
        sink.figure_();
        sink.text( " " );
        sink.bold();
        sink.text( bundleName );
        sink.bold_();
        sink.listItem_();

        sink.paragraph();
        sink.text( getBundle( ctx.locale ).getString( "report.baseline.version.current" ) + " " );
        sink.bold();
        sink.text( currentVersion );
        sink.bold_();
        sink.paragraph_();

        sink.paragraph();
        sink.text( getBundle( ctx.locale ).getString( "report.baseline.version.comparison" ) + " " );
        sink.bold();
        sink.text( comparisonVersion );
        sink.bold_();
        sink.paragraph_();

        sink.paragraph();
        sink.text( getBundle( ctx.locale ).getString( "report.baseline.generationdate" ) + " " );
        sink.bold();
        sink.text( generationDate );
        sink.bold_();
        sink.paragraph_();

        sink.section1_();
    }

    @Override
    protected void startPackage( final Object context,
                                 boolean mismatch,
                                 String packageName,
                                 String shortDelta,
                                 String delta,
                                 Version newerVersion,
                                 Version olderVersion,
                                 Version suggestedVersion,
                                 DiffMessage diffMessage,
                                 Map<String,String> attributes )
    {
        final Context ctx = (Context)context;
        final Sink sink = ctx.sink;

        sink.list();

        sink.listItem();

        sink.figure();
        sink.figureGraphics( "./images/baseline/package.gif" );
        sink.figure_();
        sink.text( " " );
        sink.monospaced();
        sink.text( packageName );
        sink.monospaced_();

        if ( diffMessage != null )
        {
            sink.text( " " );
            sink.figure();
            sink.figureGraphics( "./images/baseline/" + diffMessage.getType().name() + ".gif" );
            sink.figure_();
            sink.text( " " );
            sink.italic();
            sink.text( diffMessage.getMessage() );
            sink.italic_();
            sink.text( " (newer version: " );
            sink.monospaced();
            sink.text( newerVersion.toString() );
            sink.monospaced_();
            sink.text( ", older version: " );
            sink.monospaced();
            sink.text( olderVersion.toString() );
            if ( suggestedVersion != null )
            {
                sink.monospaced_();
                sink.text( ", suggested version: " );
                sink.monospaced();
                sink.text( suggestedVersion.toString() );
            }
            sink.monospaced_();
            sink.text( ")" );
        }
    }

    @Override
    protected void startDiff( final Object context,
                              int depth,
                              String type,
                              String name,
                              String delta,
                              String shortDelta )
    {
        final Context ctx = (Context)context;
        final Sink sink = ctx.sink;

        if ( ctx.currentDepth < depth )
        {
            sink.list();
        }

        ctx.currentDepth = depth;

        sink.listItem();
        sink.figure();
        sink.figureGraphics( "images/baseline/" + type + ".gif" );
        sink.figure_();
        sink.text( " " );

        sink.monospaced();
        sink.text( name );
        sink.monospaced_();

        sink.text( " " );

        sink.italic();
        sink.text( delta );
        sink.italic_();
    }

    @Override
    protected void endDiff( final Object context, int depth )
    {
        final Context ctx = (Context)context;
        final Sink sink = ctx.sink;

        sink.listItem_();

        if ( ctx.currentDepth > depth )
        {
            sink.list_();
        }

        ctx.currentDepth = depth;
    }

    @Override
    protected void endPackage(final Object context)
    {
        final Context ctx = (Context)context;
        final Sink sink = ctx.sink;
        if ( ctx.currentDepth > 0 )
        {
            sink.list_();
            ctx.currentDepth = 0;
        }

        sink.listItem_();
        sink.list_();
    }

    @Override
    protected void endBaseline(final Object context)
    {
        final Context ctx = (Context)context;
        ctx.sink.body_();
        ctx.sink.flush();
        ctx.sink.close();
    }

    // MavenReport methods

    public boolean canGenerateReport()
    {
        return !skip && outputDirectory != null;
    }

    public String getCategoryName()
    {
        return MavenReport.CATEGORY_PROJECT_REPORTS;
    }

    public String getDescription( Locale locale )
    {
        return getBundle( locale ).getString( "report.baseline.description" );
    }

    public String getName( Locale locale )
    {
        return getBundle( locale ).getString( "report.baseline.name" );
    }

    private ResourceBundle getBundle( Locale locale )
    {
        return ResourceBundle.getBundle( "baseline-report", locale, getClass().getClassLoader() );
    }

    @Override
    public void generate(Sink sink, Locale locale) throws MavenReportException {
        final Context ctx = new Context();
        ctx.sink = sink;
        ctx.locale = locale;

        try
        {
            execute(ctx);
        }
        catch ( Exception e )
        {
            getLog().warn( "An error occurred while producing the report page, see nested exceptions", e );
        }
    }

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

    public File getReportOutputDirectory()
    {
        return outputDirectory;
    }

    public boolean isExternalReport()
    {
        return false;
    }

    public void setReportOutputDirectory( File outputDirectory )
    {
        this.outputDirectory = outputDirectory;
    }

}
