/*
 * @(#)CoverageReportTask.java
 *
 * Copyright (C) 2002-2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the 
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software. 
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 *  DEALINGS IN THE SOFTWARE.
 */

package net.sourceforge.groboutils.codecoverage.v2.ant;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Enumeration;
import java.util.Vector;

import net.sourceforge.groboutils.codecoverage.v2.IAnalysisModule;
import net.sourceforge.groboutils.codecoverage.v2.IChannelLogReader;
import net.sourceforge.groboutils.codecoverage.v2.datastore.AnalysisModuleSet;
import net.sourceforge.groboutils.codecoverage.v2.datastore.DirMetaDataReader;
import net.sourceforge.groboutils.codecoverage.v2.datastore.IMetaDataReader;
import net.sourceforge.groboutils.codecoverage.v2.logger.DirectoryChannelLogReader;
import net.sourceforge.groboutils.codecoverage.v2.report.AnalysisModuleData;
import net.sourceforge.groboutils.codecoverage.v2.report.IReportGenerator;
import net.sourceforge.groboutils.codecoverage.v2.report.OutputXml;
import net.sourceforge.groboutils.codecoverage.v2.report.XmlCombinedReportGenerator;
import net.sourceforge.groboutils.codecoverage.v2.report.XmlReportGenerator;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.w3c.dom.Document;
import org.w3c.dom.Element;


/**
 * Ant task that creates an XML coverage report.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version   $Date: 2004/04/17 08:24:39 $
 * @since     December 18, 2002
 * @deprecated Use GroboReportTask instead of this one.
 */
public class CoverageReportTask extends Task
{
    private File datadir = null;
    private File logdir = null;
    private File outdir = null;
    private String prefix = "CoverageReport-";
    private String postfix = ".xml";
    private boolean failonerror = false;
    private Vector singleReports = new Vector();
    private Vector comboReports = new Vector();
    
    
    public void setDataDir( File f )
    {
        this.datadir = f;
    }
    
    
    public void setLogDir( File f )
    {
        this.logdir = f;
    }
    
    
    public void setOutDir( File f )
    {
        this.outdir = f;
    }
    
    
    public void setReportFilePrefix( String s )
    {
        this.prefix = s;
    }
    
    
    public void setReportFileExtension( String s )
    {
        this.postfix = s;
    }
    
    
    public void setFailOnError( boolean val )
    {
        this.failonerror = val;
    }
    
    
    public SimpleHtmlReportStyle createSimpleStyle()
    {
        SimpleHtmlReportStyle style = new SimpleHtmlReportStyle();
        this.singleReports.addElement( style );
        return style;
    }
    
    
    public SourceHtmlReportStyle createSourceStyle()
    {
        SourceHtmlReportStyle style = new SourceHtmlReportStyle();
        this.comboReports.addElement( style );
        return style;
    }
    
    
    
    public void execute()
            throws BuildException
    {
        log( "Use of the CoverageReport task is deprecated; please "+
            "use the 'grobo-report' task instead.  You may need to "+
            "change the taskdef to reference the new resource file, located "+
            "at [ant-grobocoverage.properties], instead of "+
            "[net/sourceforge/groboutils/codecoverage/grobocoverage.properties"+
            "].", Project.MSG_WARN );
        
        // check specifications
        if (this.datadir == null)
        {
            throw new BuildException( "Did not specify attribute 'datadir'." );
        }
        if (this.logdir == null)
        {
            throw new BuildException( "Did not specify attribute 'logdir'." );
        }
        if (this.outdir == null)
        {
            throw new BuildException( "Did not specify attribute 'outdir'." );
        }
        if (this.prefix == null || this.postfix == null)
        {
            throw new BuildException( "Output report name format is null." );
        }
        
        
        try
        {
            setupDirectories();
        }
        catch (IOException ioe)
        {
            throw new BuildException( "Error setting up the directories.", ioe );
        }
        
        
        IMetaDataReader mdr = createMetaDataReader();
        IReportGenerator rg = new XmlReportGenerator();
        boolean errors = false;
        try
        {
            Vector reports = new Vector();
            AnalysisModuleSet ams = mdr.getAnalysisModuleSet();
            IAnalysisModule amL[] = ams.getAnalysisModules();
            for (int i = 0; i < amL.length; ++i)
            {
                try
                {
                    IChannelLogReader clr =
                        createChannelLogReader( amL[i], ams );
                    AnalysisModuleData amd =
                        new AnalysisModuleData( amL[i], mdr, clr );
                    Element rootEl = createReport( amL[i], amd, rg );
                    if (rootEl == null)
                    {
                        errors = true;
                    }
                    else
                    {
                        Document doc = rootEl.getOwnerDocument();
                        reports.addElement( doc );
                        processStyles( doc, amL[i].getMeasureName() );
                    }
                }
                catch (IllegalArgumentException iae)
                {
                    iae.printStackTrace();
                    log( iae.getMessage(), Project.MSG_WARN );
                    errors = true;
                }
            }
            finishStyles( reports );
        }
        catch (IOException e)
        {
            throw new BuildException(
                "I/O Exception while creating a report.", e, getLocation() );
        }
        finally
        {
            try
            {
                mdr.close();
            }
            catch (IOException e)
            {
                throw new BuildException(
                    "I/O Exception while closing meta-data reader.", e,
                    getLocation() );
            }
        }
        
        
        if (errors && this.failonerror)
        {
            throw new BuildException(
                "No coverage logs were generated, or the logs "+
                "are not located under '"+this.logdir+"'.",
                getLocation() );
        }
    }
    
    
    /**
     * setup the directories in the logdir - ensure we have adequate
     * setup protections.  This saves some head banging in figuring out
     * why bad exceptions are thrown.
     */
    private void setupDirectories()
            throws IOException, BuildException
    {
        if (!this.datadir.exists() ||
            !this.datadir.isDirectory())
        {
            throw new BuildException("Data directory setting ("+
                this.datadir+") does not exist or is not a directory.");
        }
        if (!this.logdir.exists())
        {
            this.logdir.mkdirs();
        }
        if (!this.logdir.isDirectory())
        {
            throw new BuildException("Log directory setting ("+
                this.logdir+") is not a directory.");
        }
        String modules[] = this.datadir.list();
        if (modules == null || modules.length <= 0)
        {
            throw new BuildException("There are no module data directories in "+
                this.datadir+".");
        }
        
        String indicies[] = this.logdir.list();
        if (indicies == null)
        {
            indicies = new String[0];
        }
        int count = modules.length;
        
        for (int i = 0; i <= count; ++i)
        {
            String dirname = Integer.toString(i);
            boolean found = false;
            for (int j = 0; j < indicies.length; ++j)
            {
                if (indicies[j].equals( dirname ))
                {
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                File f = new File( this.logdir, dirname );
                f.mkdirs();
            }
        }
    }
    
    
    private Element createReport( IAnalysisModule am,
            AnalysisModuleData amd, IReportGenerator rg )
            throws IOException, BuildException
    {
        log( "Creating coverage report for module "+am.getMeasureName(),
            Project.MSG_INFO );
        File outfile = createReportFile( am );
        FileOutputStream fos = new FileOutputStream( outfile );
        Element ret = null;
        try
        {
            ret = rg.createReport( am, amd );
            OutputStreamWriter osw = new OutputStreamWriter( fos, "UTF8" );
            (new OutputXml()).write( ret, osw, null );
        }
        catch (IllegalArgumentException iae)
        {
            // throw new BuildException( iae );
            
            // this is a hack: really, the underlying code should be made
            // more robust.
            iae.printStackTrace();
            log( iae.getMessage(), Project.MSG_WARN );
            ret = null;
        }
        finally
        {
            fos.close();
        }
        
        return ret;
    }
    
    
    private File createReportFile( IAnalysisModule am )
    {
        File f = new File( this.outdir, this.prefix + am.getMeasureName() +
            this.postfix );
        log( "Creating report file '"+f+"' for measure '"+am.getMeasureName()+
            "'.", Project.MSG_VERBOSE );
        return f;
    }
    
    
    private IChannelLogReader createChannelLogReader( IAnalysisModule am,
            AnalysisModuleSet ams )
            throws IOException
    {
        short mi = ams.getAnalysisModuleIndex( am );
        IChannelLogReader clr = new DirectoryChannelLogReader( this.logdir,
            mi );
        return clr;
    }
    
    
    private IMetaDataReader createMetaDataReader()
            throws BuildException
    {
        try
        {
            return new DirMetaDataReader( this.datadir );
        }
        catch (IOException e)
        {
            throw new BuildException( "I/O error creating meta-data reader.",
                e, getLocation() );
        }
    }
    
    
    private void processStyles( Document doc, String moduleName )
            throws BuildException, IOException
    {
        Enumeration e = singleReports.elements();
        while (e.hasMoreElements())
        {
            IReportStyle rs = (IReportStyle)e.nextElement();
            rs.generateReport( getProject(), doc, moduleName );
        }
    }
    
    
    private void finishStyles( Vector reports )
            throws BuildException, IOException
    {
        Enumeration e = singleReports.elements();
        while (e.hasMoreElements())
        {
            IReportStyle rs = (IReportStyle)e.nextElement();
            rs.reportComplete( getProject(), new Vector() );
        }
        e = null;
        
        // Create the uber document
        Document docs[] = new Document[ reports.size() ];
        reports.copyInto( docs );
        XmlCombinedReportGenerator gen = new XmlCombinedReportGenerator();
        log( "Creating combined coverage report", Project.MSG_INFO );
        File outfile = new File( this.outdir, this.prefix + "all" +
            this.postfix );
        log( "Creating report file '"+outfile+"' for all measures.",
            Project.MSG_VERBOSE );
        FileOutputStream fos = new FileOutputStream( outfile );
        Element ret = null;
        try
        {
            ret = gen.createReport( docs );
            OutputStreamWriter osw = new OutputStreamWriter( fos, "UTF8" );
            (new OutputXml()).write( ret, osw, null );
        }
        catch (IllegalArgumentException iae)
        {
            // throw new BuildException( iae );
            
            // this is a hack: really, the underlying code should be made
            // more robust.
            iae.printStackTrace();
            log( iae.getMessage(), Project.MSG_WARN );
            ret = null;
        }
        finally
        {
            fos.close();
        }
        docs = null;
        reports.removeAllElements();
        reports = null;
        
        // process all the combination report styles
        if (ret != null)
        {
            Document doc = ret.getOwnerDocument();
            e = comboReports.elements();
            while (e.hasMoreElements())
            {
                IReportStyle rs = (IReportStyle)e.nextElement();
                rs.generateReport( getProject(), doc, "all" );
                // rs.reportComplete( getProject(), new Vector() );
            }
        }
        e = comboReports.elements();
        while (e.hasMoreElements())
        {
            IReportStyle rs = (IReportStyle)e.nextElement();
            rs.reportComplete( getProject(), new Vector() );
        }
    }
}

