/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 */
package org.apache.tools.todo.taskdefs.metamata;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.myrmidon.api.TaskException;
import org.apache.tools.todo.taskdefs.exec.ExecuteStreamHandler;
import org.apache.myrmidon.framework.file.Path;
import org.apache.tools.todo.types.PathUtil;

/**
 * Calculates global complexity and quality metrics on Java source code. You
 * will not be able to use this task with the evaluation version since as of
 * Metamata 2.0, Metrics does not support command line :-( For more information,
 * visit the website at <a href="http://www.metamata.com">www.metamata.com</a>
 *
 * @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
 */
public class MMetrics extends AbstractMetamataTask
{
    /*
     * The command line options as of Metamata 2.0 are as follows:
     * Usage
     * mmetrics <option>... <path>...
     * Parameters
     * path              File or directory to measure.
     * Options
     * -arguments   -A   <file>      Includes command line arguments from file.
     * -classpath   -cp  <path>      Sets class path (also source path unless one
     * explicitly set). Overrides METAPATH/CLASSPATH.
     * -compilation-units            Measure compilation units.
     * -files                        Measure compilation units.
     * -format      -f   <format>    Sets output format, default output file type.
     * -help        -h               Prints help and exits.
     * -indent      -i   <string>    Sets string used to indent labels one level.
     * -methods                      Measure methods, types, and compilation units.
     * -output      -o   <file>      Sets output file name.
     * -quiet       -q               Suppresses copyright message.
     * -sourcepath       <path>      Sets source path. Overrides SOURCEPATH.
     * -types                        Measure types and compilation units.
     * -verbose     -v               Prints all messages.
     * -version     -V               Prints version and exits.
     * Format Options
     * comma csv                     Format output as comma-separated text.
     * html htm                      Format output as an HTML table.
     * tab tab-separated tsv         Format output as tab-separated text.
     * text txt                      Format output as space-aligned text.
     */
    /**
     * the granularity mode. Should be one of 'files', 'methods' and 'types'.
     */
    protected String granularity = null;

    /**
     * the XML output file
     */
    protected File outFile = null;

    /**
     * the location of the temporary txt report
     */
    protected File tmpFile = createTmpFile();

    protected Path path = null;

    //--------------------------- PUBLIC METHODS -------------------------------

    /**
     * default constructor
     */
    public MMetrics()
    {
        super( "com.metamata.sc.MMetrics" );
    }

    /**
     * set the granularity of the audit. Should be one of 'files', 'methods' or
     * 'types'.
     *
     * @param granularity the audit reporting mode.
     */
    public void setGranularity( String granularity )
    {
        this.granularity = granularity;
    }

    /**
     * Set the output XML file
     *
     * @param file the xml file to write the XML report to.
     */
    public void setTofile( File file )
    {
        this.outFile = file;
    }

    /**
     * Set a new path (directory) to measure metrics from.
     *
     * @return the path instance to use.
     */
    public Path createPath()
    {
        if( path == null )
        {
            path = new Path();
        }
        return path;
    }

    protected ArrayList getOptions()
        throws TaskException
    {
        ArrayList options = new ArrayList( 512 );

        final Path classpath = new Path();

        // there is a bug in Metamata 2.0 build 37. The sourcepath argument does
        // not work. So we will use the sourcepath prepended to classpath. (order
        // is important since Metamata looks at .class and .java)
        classpath.add( getSourcePath() );

        // don't forget to modify the pattern if you change the options reporting
        classpath.add( getClassPath() );

        final String formattedClasspath = PathUtil.formatPath( classpath, getContext() );
        if( formattedClasspath.length() > 0 )
        {
            options.add( "-classpath" );
            options.add( formattedClasspath );
        }

        options.add( "-output" );
        options.add( tmpFile.toString() );

        options.add( "-" + granularity );

        // display the metamata copyright
        // options.add( "-quiet");
        options.add( "-format" );

        // need this because that's what the handler is using, it's
        // way easier to process than any other separator
        options.add( "tab" );

        // specify a / as the indent character, used by the handler.
        options.add( "-i" );
        options.add( "/" );

        // directories
        final String[] dirs = path.listFiles( getContext() );
        for( int i = 0; i < dirs.length; i++ )
        {
            options.add( dirs[ i ] );
        }
        // files next.
        addAllArrayList( options, getIncludedFiles().keySet().iterator() );
        return options;
    }

    //------------------- PROTECTED / PRIVATE METHODS --------------------------


    // check for existing options and outfile, all other are optional
    protected void validate()
        throws TaskException
    {
        super.validate();

        if( !"files".equals( granularity ) && !"methods".equals( granularity )
            && !"types".equals( granularity ) )
        {
            throw new TaskException( "Metrics reporting granularity is invalid. Must be one of 'files', 'methods', 'types'" );
        }
        if( outFile == null )
        {
            throw new TaskException( "Output XML file must be set via 'tofile' attribute." );
        }
        if( path == null && getFileSets().size() == 0 )
        {
            throw new TaskException( "Must set either paths (path element) or files (fileset element)" );
        }
        // I don't accept dirs and files at the same time, I cannot recognize the semantic in the result
        if( path != null && getFileSets().size() > 0 )
        {
            throw new TaskException( "Cannot set paths (path element) and files (fileset element) at the same time" );
        }
    }

    /**
     * cleanup the temporary txt report
     *
     * @exception TaskException Description of Exception
     */
    protected void cleanUp()
        throws TaskException
    {
        try
        {
            super.cleanUp();
        }
        finally
        {
            if( tmpFile != null )
            {
                tmpFile.delete();
                tmpFile = null;
            }
        }
    }

    protected void execute0()
        throws TaskException
    {
        super.execute0();
        transformFile();
    }

    /**
     * transform the generated file via the handler This function can either be
     * called if the result is written to the output file via -output or we
     * could use the handler directly on stdout if not.
     *
     * @exception TaskException Description of Exception
     * @see #createStreamHandler()
     */
    protected void transformFile()
        throws TaskException
    {
        FileInputStream tmpStream = null;
        try
        {
            tmpStream = new FileInputStream( tmpFile );
        }
        catch( IOException e )
        {
            throw new TaskException( "Error reading temporary file: " + tmpFile, e );
        }
        FileOutputStream xmlStream = null;
        try
        {
            xmlStream = new FileOutputStream( outFile );
            ExecuteStreamHandler xmlHandler = new MMetricsStreamHandler( xmlStream );
            xmlHandler.setProcessOutputStream( tmpStream );
            xmlHandler.start();
            xmlHandler.stop();
        }
        catch( IOException e )
        {
            throw new TaskException( "Error creating output file: " + outFile, e );
        }
        finally
        {
            if( xmlStream != null )
            {
                try
                {
                    xmlStream.close();
                }
                catch( IOException ignored )
                {
                }
            }
            if( tmpStream != null )
            {
                try
                {
                    tmpStream.close();
                }
                catch( IOException ignored )
                {
                }
            }
        }
    }
}
