/*****************************************************************************
 * 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 file.                                                         *
 *****************************************************************************/
package org.apache.cocoon.transformation;

import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.Component;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.transformation.AbstractTransformer;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.environment.Source;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.net.URL;
import java.net.MalformedURLException;

/**
 *
 * The <code>WriteFileTransformer</code> is a class that can be plugged into a pipeline
 * to write the SAX events which passes thru this transformer in a XML form
 * to a file.
 * <br>
 * The file will be specified in a parameter tag in the sitemap pipeline to the
 * transformer as follows:
 * <p>
 * <pre>
 *   &lt;map:transform type="writeFile" src="cache/"&gt;
 *       &lt;map:parameter name="file" value="myfile.xml"/&gt;
 *		 &lt;map:parameter name="append" value="no"/&gt;
 *   &lt;/map:transform&gt;
 * </pre>
 * </p>
 *
 * The <i>src</i> parameter tells the writer where to place the files relative to the
 * web applicaiton root.  The <i>file</i> parameter specified the file name.
 * Use the <i>append</i> parameter to append data to the file name specified.
 * The default is "no" which overwrites the file each time.
 *
 * This transformations main purpose is caching and debugging.
 *
 * @author <a href="mailto:asansone@fergcons.com">Aaron M. Sansone</a>
 */
public class WriteFileTransformer extends AbstractTransformer {
    /** Weather we are forwarding XML data or not. */
    private boolean canReset=true;

    private String lf = System.getProperty("line.separator", "\n");
    private String separator = System.getProperty("file.separator","/");

    /** true if filename is valid and writeable */
    private boolean isValid = true;
    /** filename for file */
    private String filename = null;
    private URL url = null;
    /** log file */
    private FileWriter file = null;
    /** should we append content to the log file */
    private boolean append = false;

    private Source inputSource;

    /** BEGIN SitemapComponent methods **/

    public void setup(SourceResolver resolver, Map objectModel,
                      String source, Parameters parameters)
            throws ProcessingException, SAXException, IOException {

        inputSource = resolver.resolve(source);

        String appends = parameters.getParameter("append", null);
        filename = parameters.getParameter("file", null);
        if ("yes".equals(appends)) {
            append = true;
        } else {
            append = false;
        }

        // Check for null, use System.out if logfile is not specified.
        if(filename != null) {
            filename = this.inputSource.getSystemId() + "/" + filename;
            try {
                url = new URL(filename);
            } catch (MalformedURLException e) {
                getLogger().debug("Could not resolve URL: '"+ filename + "'");
            }
        }

        getLogger().debug("Using source directory: "+this.inputSource.getSystemId());
    }

    /** END SitemapComponent methods **/

    /**
     * Receive an object for locating the origin of SAX document events.
     */
    public void setDocumentLocator(Locator locator) {
        write("");
        if (super.contentHandler!=null) {
            super.contentHandler.setDocumentLocator(locator);
        }
    }

    /**
     * Receive notification of the beginning of a document.
     */
    public void startDocument()
    throws SAXException {
        try {
            if( url != null ) {
                file = new FileWriter(url.getFile(), append );
            } else {
                file = new FileWriter(java.io.FileDescriptor.out);
            }
        } catch (IOException e) {
            getLogger().debug("WriteFileTransformer startDocument", e);
            isValid = false;
            throw new SAXException( e );
        }

        getLogger().debug("WriteFileTransformer startDocument");

        write("<?xml version=\"1.0\"?>");
        if (super.contentHandler!=null) {
            super.contentHandler.startDocument();
        }

        this.canReset=false;
    }

    /**
     * Receive notification of the end of a document.
     */
    public void endDocument()
    throws SAXException {
        getLogger().debug("WriteFileTransformer endDocument");
        write ("");
        if (super.contentHandler!=null) {
            super.contentHandler.endDocument();
        }
        this.canReset=true;

        try {
            file.close();
        } catch (Exception e) {getLogger().debug("WriteFileTransformer.close file exception", e);}

    }

    /**
     * Begin the scope of a prefix-URI Namespace mapping.
     */
    public void startPrefixMapping(String prefix, String uri)
    throws SAXException {
        write ( "prefix="+prefix+",uri="+uri);
        if (super.contentHandler!=null) {
            super.contentHandler.startPrefixMapping(prefix,uri);
        }
    }

    /**
     * End the scope of a prefix-URI mapping.
     */
    public void endPrefixMapping(String prefix)
    throws SAXException {
        write ("prefix="+prefix);
        if (super.contentHandler!=null) {
            super.contentHandler.endPrefixMapping(prefix);
        }
    }

    /**
     * Receive notification of the beginning of an element.
     */
    public void startElement(String uri, String loc, String raw, Attributes a)
    throws SAXException {
        StringBuffer sb = new StringBuffer();
        sb.append("<");
        sb.append(raw);
        for (int i = 0; i < a.getLength(); i++) {
            sb.append(" ");
            sb.append(a.getQName(i));
            sb.append("=\"");
            sb.append(a.getValue(i));
            sb.append("\"");
        }
        sb.append(">");
        write(sb.toString());
        if (super.contentHandler!=null) {
            super.contentHandler.startElement(uri,loc,raw,a);
        }
    }


    /**
     * Receive notification of the end of an element.
     */
    public void endElement(String uri, String loc, String qname)
    throws SAXException {
        write ("</"+qname+">");
        if (super.contentHandler!=null) {
            super.contentHandler.endElement(uri,loc,qname);
        }
    }

    /**
     * Receive notification of character data.
     */
    public void characters(char ch[], int start, int len)
    throws SAXException {
        write ( new String(ch,start,len));
        if (super.contentHandler!=null) {
            super.contentHandler.characters(ch,start,len);
        }
    }

    /**
     * Receive notification of ignorable whitespace in element content.
     */
    public void ignorableWhitespace(char ch[], int start, int len)
    throws SAXException {
        write ( new String(ch,start,len));
        if (super.contentHandler!=null) {
            super.contentHandler.ignorableWhitespace(ch,start,len);
        }
    }

    /**
     * Receive notification of a processing instruction.
     */
    public void processingInstruction(String target, String data)
    throws SAXException {
        write ("target="+target+",data="+data);
        if (super.contentHandler!=null) {
            super.contentHandler.processingInstruction(target,data);
        }
    }

    /**
     * Receive notification of a skipped entity.
     */
    public void skippedEntity(String name)
    throws SAXException {
        write ( "name="+name);
        if (super.contentHandler!=null) {
            super.contentHandler.skippedEntity(name);
        }
    }

    /**
     * Report the start of DTD declarations, if any.
     */
    public void startDTD(String name, String publicId, String systemId)
    throws SAXException {
        write ("name="+name+",publicId="+publicId+",systemId="+systemId);
        if (super.lexicalHandler!=null) {
            super.lexicalHandler.startDTD(name,publicId,systemId);
        }
    }

    /**
     * Report the end of DTD declarations.
     */
    public void endDTD()
    throws SAXException {
        write ( "");
        if (super.lexicalHandler!=null) {
            super.lexicalHandler.endDTD();
        }
    }

    /**
     * Report the beginning of an entity.
     */
    public void startEntity(String name)
    throws SAXException {
        write ("name="+name);
        if (super.lexicalHandler!=null) {
            super.lexicalHandler.startEntity(name);
        }
    }

    /**
     * Report the end of an entity.
     */
    public void endEntity(String name)
    throws SAXException {
        write ("name="+name);
        if (super.lexicalHandler!=null) {
            super.lexicalHandler.endEntity(name);
        }
    }

    /**
     * Report the start of a CDATA section.
     */
    public void startCDATA()
    throws SAXException {
        write ("");
        if (super.lexicalHandler!=null) {
            super.lexicalHandler.startCDATA();
        }
    }

    /**
     * Report the end of a CDATA section.
     */
    public void endCDATA()
    throws SAXException {
        write ("");
        if (super.lexicalHandler!=null) {
            super.lexicalHandler.endCDATA();
        }
    }

    /**
     * Report an XML comment anywhere in the document.
     */
    public void comment(char ch[], int start, int len)
    throws SAXException {
        write ( new String(ch,start,len) );
        if (super.lexicalHandler!=null) {
            super.lexicalHandler.comment(ch,start,len);
        }
    }

    /**
     * Report to logfile.
     */
    private void write(String data) {
        if (isValid) {
            StringBuffer logEntry = new StringBuffer();
            logEntry.append ( data );
            logEntry.append ( lf );
            synchronized (file) {
                try {
                    file.write( logEntry.toString(), 0, logEntry.length());
                    file.flush();
                }
                catch(IOException ioe) { getLogger().debug("WriteFileTransformer.log", ioe); }
            }
        }
    }

    /**
     *  Attempt to close the log file when the class is GC'd
     */
    public void destroy() {
    }
}

