package org.apache.maven.changelog;

/* ====================================================================
 *   Copyright 2001-2004 The Apache Software Foundation.
 *
 *   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.
 * ====================================================================
 */

// java imports
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
// commons imports
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
// maven imports
import org.apache.maven.project.Developer;

/**
 * Change log task. It uses a ChangeLogGenerator and ChangeLogParser to create
 * a Collection of ChangeLogEntry objects, which are used to produce an XML
 * output that represents the list of changes.
 *
 *
 * @author <a href="mailto:glenn@somanetworks.com">Glenn McAllister</a>
 * @author <a href="mailto:jeff.martin@synamic.co.uk">Jeff Martin</a>
 * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
 * @author <a href="mailto:dion@multitask.com.au">dIon Gillard</a>
 * @author <a href="mailto:bodewig@apache.org">Stefan Bodewig</a>
 * @author <a href="mailto:peter@apache.org">Peter Donald</a>
 * @version $Id: ChangeLog.java,v 1.5 2004/03/02 15:00:17 evenisse Exp $
 */
public class ChangeLog
{
    /**
     * Used to specify the range of log entries to retrieve.
     */
    private String range;

     /**
     * Used to specify the date format of log entries to retrieve.
     */
    private String dateFormat;

    /**
     * Input dir. Working directory for running CVS executable
     */
    private File base;

    /**
     * The classname of our ChangeLogFactory, defaulting to Maven's built in
     * CVS factory.
     */
    private String clFactoryClass = 
        "org.apache.maven.cvslib.CvsChangeLogFactory";

    /** the connection string used to access the SCM */
    private String connection;
    
    /** the list of developers on the project */
    private List developers;
    
    /** change log entries parsed */
    private Collection entries;

    /** Log */
    private static final Log LOG = LogFactory.getLog(ChangeLog.class);

    /**
     * Output file for xml document
     */
    private File output;
    
    /** output encoding for the xml document */
    private String outputEncoding;

    /**
     * Set the ChangeLogFactory class name.  If this isn't set, the factory
     * defaults to Maven's build in CVS factory.
     *
     * @param factoryClassName the fully qualified factory class name
     */
    public void setFactory(String factoryClassName)
    {
        clFactoryClass = factoryClassName;
    }
   
    /**
     * Set the range of log entries to process; the interpretation of this
     * parameter depends on the generator.
     *
     * @param range the range of log entries.
     */
    public void setRange(String range)
    {
        this.range = range;
    }

    /**
     * Get the range of log entries to process; the interpretation of the range
     * depends on the generator implementation.
     *
     * @return the range of log entries.
     */
    public String getRange()
    {
        return range;
    }

    /**
     * Set the date format of log entries to process; the
     * interpretation of this parameter depends on the generator.
     *
     * @param dateFormat the dateFormat of log entries.
     */
    public void setDateFormat(String dateFormat)
    {
        this.dateFormat = dateFormat;
    }

    /**
     * Get the date format of log entries to process; the
     * interpretation of this parameter depends on the generator.
     *
     * @return the dateFormat of log entries.
     */
    public String getDateFormat()
    {
        return dateFormat;
    }
 
    /**
     * Set the base directory for the change log generator.
     * @param base the base directory
     */
    public void setBasedir(File base)
    {
        this.base = base;
    }

    /**
     * Get the base directory for the change log generator.
     *
     * @return the base directory
     */
    public File getBasedir()
    {
        return base;
    }

    /**
     * Set the output file for the log.
     * @param output the output file
     */
    public void setOutput(File output)
    {
        this.output = output;
    }

    /**
     * Return connection string declared in project.xml
     * @return connection string
     */
    public String getRepositoryConnection()
    {
        return connection;
    }

    /**
     * Change SCM connection string
     * @param aString a string containing the project's repository
     *      connection
     */
    public void setRepositoryConnection(String aString)
    {
        connection = aString;
    }
    
    /**
     * Execute task.
     * @throws FileNotFoundException if {@link ChangeLog#base} doesn't exist
     * @throws IOException if there are problems running CVS
     * @throws UnsupportedEncodingException if the underlying platform doesn't
     *      support ISO-8859-1 encoding
     */
    public void doExecute() throws FileNotFoundException, IOException, 
        UnsupportedEncodingException
    {
        if (output == null)
        {
            throw new NullPointerException("output must be set");
        }

        generateEntries();
        replaceAuthorIdWithName();
        createDocument();
    }
   
    /**
     * Create the change log entries.
     * @throws IOException if there is a problem creating the change log
     * entries.
     */
    private void generateEntries() throws IOException
    {
        ChangeLogFactory factory = createFactory();
        ChangeLogGenerator generator = factory.createGenerator();
        ChangeLogParser parser = factory.createParser();

        generator.init(this);
        parser.init(this);

        try
        {
            setEntries(generator.getEntries(parser));
            if (LOG.isInfoEnabled()) {
                LOG.info("ChangeLog found: " + getEntries().size() 
                    + " entries");
            }
        } catch (IOException e) {
            LOG.warn(e.getLocalizedMessage(), e);
            throw e;
        }
        finally
        {
            parser.cleanup();
            generator.cleanup();
        }
    }

    /**
     * Create a new instance of the ChangeLogFactory specified by the
     * <code>clFactory</code> member.
     *
     * @return the new ChangeLogFactory instance
     * @throws IOException if there is a problem creating the instance.
     */
    private ChangeLogFactory createFactory() throws IOException
    {
        try
        {
            Class clazz = Class.forName(clFactoryClass);
            return (ChangeLogFactory) clazz.newInstance();
        }
        catch (ClassNotFoundException cnfe)
        {
            throw new IOException("Cannot find class " + clFactoryClass
                + " " + cnfe.toString());
        }
        catch (IllegalAccessException iae)
        {
            throw new IOException("Cannot access class " + clFactoryClass
                + " " + iae.toString());
        }
        catch (InstantiationException ie)
        {
            throw new IOException("Cannot instantiate class " + clFactoryClass
                + " " + ie.toString());
        }
    }
    
    /**
     * Set up list of developers mapping id to name.
     * @task This should be a facility on the maven project itself
     * @return a list of developers ids and names
     */
    private Properties getUserList()
    {
        Properties userList = new Properties();
        
        Developer developer = null;
        for (Iterator i = getDevelopers().iterator(); i.hasNext();)
        {
            developer = (Developer) i.next();
            userList.put(developer.getId(), developer.getName());
        }
        
        return userList;
    }

    /**
     * replace all known author's id's with their maven specified names
     */
    private void replaceAuthorIdWithName()
    {
        Properties userList = getUserList();
        ChangeLogEntry entry = null;
        for (Iterator i = getEntries().iterator(); i.hasNext();)
        {
            entry = (ChangeLogEntry) i.next();
            if (userList.containsKey(entry.getAuthor()))
            {
                entry.setAuthor(userList.getProperty(entry.getAuthor()));
            }
        }
    }
    
    /**
     * Create the XML document from the currently available details
     * @throws FileNotFoundException when the output file previously provided
     *      does not exist
     * @throws UnsupportedEncodingException when the platform doesn't support
     *      ISO-8859-1 encoding
     */
    private void createDocument() throws FileNotFoundException,
        UnsupportedEncodingException
    {
        File dir = output.getParentFile();
        if (dir != null) {
            dir.mkdirs();
        }
        PrintWriter out = new PrintWriter(new OutputStreamWriter(
            new FileOutputStream(output), getOutputEncoding()));
        out.println(toXML());
        out.flush();
        out.close();
    }
    
    /**
     * @return an XML document representing this change log and it's entries 
     */
    private String toXML()
    {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<?xml version=\"1.0\" encoding=\"")
            .append(getOutputEncoding())
            .append("\" ?>\n")
            .append("<changelog>\n");
        
        for (Iterator i = getEntries().iterator(); i.hasNext();)
        {
            buffer.append(((ChangeLogEntry) i.next()).toXML());
        }

        buffer.append("</changelog>\n");
        
        return buffer.toString();
    }
    
    /**
     * Getter for property entries.
     * @return Value of property entries.
     */
    public Collection getEntries()
    {
        if (entries == null) 
        {
            entries = Arrays.asList(new Object[0]);
        }
        return entries;
    }
    
    /** 
     * Setter for property entries.
     * @param entries New value of property entries.
     */
    public void setEntries(Collection entries)
    {
        this.entries = entries;
    }
    
    /**
     * Returns the developers.
     * @return List
     */
    public List getDevelopers()
    {
        return developers;
    }

    /**
     * Sets the developers.
     * @param developers The developers to set
     */
    public void setDevelopers(List developers)
    {
        this.developers = developers;
    }

    /**
     * Returns the outputEncoding.
     * @return String
     */
    public String getOutputEncoding()
    {
        return outputEncoding;
    }

    /**
     * Sets the outputEncoding.
     * @param outputEncoding The outputEncoding to set
     */
    public void setOutputEncoding(String outputEncoding)
    {
        this.outputEncoding = outputEncoding;
    }

} // end of ChangeLog
