package org.apache.cocoon.serialization;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;

import org.apache.cocoon.serialization.AbstractSerializer;
import org.apache.cocoon.caching.CacheableProcessingComponent;

import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;

import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceResolver;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.source.impl.validity.NOPValidity;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.MimeConstants;
import org.xml.sax.SAXException;


public final class CacheableFOPSerializer extends AbstractSerializer implements
    Configurable, Serviceable, CacheableProcessingComponent {

    /**
     * The current <code>mime-type</code>.
     */
    protected String mimetype;

    /**
     * Should we set the content length ?
     */
    protected boolean setContentLength = true;

    /**
     * Manager to get URLFactory from.
     */
    private ServiceManager manager;

    /**
     * Comment for <code>configURI</code>
     */
    private String configURI = null;

    /**
     * @return
     * @see org.apache.cocoon.sitemap.SitemapOutputComponent#getMimeType()
     */
    public String getMimeType() {
        return mimetype;
    }

    /**
     * @param out OutputStream
     * @throws IOException java.io.IOException
     * @see org.apache.cocoon.sitemap.SitemapOutputComponent#setOutputStream(java.io.OutputStream)
     */
    public void setOutputStream(final OutputStream out) throws IOException {
        if (getLogger().isDebugEnabled()) {
            getLogger().debug("setOutputStream() - invoking method...");
        }

        Fop fop = this.createFOProcessor();
        fop.setOutputStream(out);
        try {
            this.setContentHandler(fop.getDefaultHandler());
        } catch (FOPException e) {
            getLogger().error("Can not set OutputStream!", e);
        }

        if (getLogger().isDebugEnabled()) {
            getLogger().debug("setOutputStream() - exiting method...");
        }
    }

    /**
     * @return
     * @see org.apache.cocoon.sitemap.SitemapOutputComponent#shouldSetContentLength()
     */
    public boolean shouldSetContentLength() {
        return this.setContentLength;
    }

    /**
     * @param manager
     * @throws ServiceException
     * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
     */
    public void service(final ServiceManager servicemanager) throws ServiceException {
        this.manager = servicemanager;
    }

    /**
     * @param conf
     * @throws ConfigurationException
     * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
     */
    public void configure(final Configuration conf) throws ConfigurationException {
        this.mimetype = conf.getAttribute("mime-type", MimeConstants.MIME_PDF);
        this.setContentLength = conf.getChild("set-content-length").getValueAsBoolean(true);
        String configUrl = conf.getChild("user-config").getValue(null);
        if (configUrl != null) {
            Source configSource = null;
            SourceResolver resolver = null;
            try {
                resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE);
                configSource = resolver.resolveURI(configUrl);
                this.configURI = configSource.getURI();
            } catch (Exception e) {
                getLogger().warn("Cannot load configuration from " + configUrl);
                throw new ConfigurationException("Cannot load configuration from " + configUrl, e);
            } finally {
                if (resolver != null) {
                    resolver.release(configSource);
                    manager.release(resolver);
                }
            }
        }
        if (getLogger().isDebugEnabled()) {
            getLogger().debug("configure() - mimetype: " + this.mimetype);
            getLogger().debug("configure() - user config file: " + configUrl);
        }
    }

    /**
     * Generate the unique key.
     * This key must be unique inside the space of this component.
     * This method must be invoked before the generateValidity() method.
     *
     * @return The generated key or <code>0</code> if the component
     *              is currently not cacheable.
     */
    public Serializable getKey() {
        return "1";
    }

    /**
     * Generate the validity object.
     * Before this method can be invoked the generateKey() method
     * must be invoked.
     *
     * @return The generated validity object or <code>null</code> if the
     *         component is currently not cacheable.
     */
    public SourceValidity getValidity() {
        return NOPValidity.SHARED_INSTANCE;
    }

    /**
     * Generate the FO processor.
     * If the Mime-Type is wrong,
     * FOP uses "application/pdf" as default Mime-Type
     *
     * @return The FO Processor
     */
    private Fop createFOProcessor() {
        Fop fop = null;
        if (getLogger().isDebugEnabled()) {
            getLogger().debug("createFOProcessor() - invoking method...");
        }
        if (MimeConstants.MIME_PDF.equals(this.mimetype)) {
            fop = getFOProcessor(MimeConstants.MIME_PDF, this.configURI);
        } else if (MimeConstants.MIME_RTF.equals(this.mimetype)) {
            fop = getFOProcessor(MimeConstants.MIME_RTF, this.configURI);
        } else if (MimeConstants.MIME_PCL.equals(this.mimetype)) {
            fop = getFOProcessor(MimeConstants.MIME_PCL, this.configURI);
        } else if (MimeConstants.MIME_POSTSCRIPT.equals(this.mimetype)) {
            fop = getFOProcessor(MimeConstants.MIME_POSTSCRIPT, this.configURI);
        } else if (MimeConstants.MIME_PLAIN_TEXT.equals(this.mimetype)) {
            fop = getFOProcessor(MimeConstants.MIME_PLAIN_TEXT, this.configURI);
        } else {
            fop = getFOProcessor(MimeConstants.MIME_PDF, this.configURI);
        }
        return fop;
    }

    /**
     * Return a FO processor.
     * If uri is null a default FOUserAgent is used.
     *
     * @param mimeType Mime-Type
     * @param uri URI of user config file
     * @return The FO Processor
     */
    private Fop getFOProcessor(final String mimeType, final String uri) {
        Fop fop = null;
        if (getLogger().isDebugEnabled()) {
            getLogger().debug("getFOProcessor() - invoking method...");
            getLogger().debug("getFOProcessor() - Mime-Type: " + mimeType);
        }
        if (uri != null) {
            try {
                fop = new Fop(mimeType, createFOUserAgent(uri));
            } catch (FOPException e) {
                if (getLogger().isWarnEnabled()) {
                    getLogger().warn("getFOProcessor() - Creating FO Processor failed!", e);
                    getLogger().warn("getFOProcessor() - Try to create default FO Processor...");
                }
                fop = new Fop(mimeType);
            }
        } else {
            fop = new Fop(mimeType);
        }
        return fop;
    }

    /**
     * @param uri URI of user config file
     * @return FOUserAgent
     * @throws FOPException org.apache.fop.apps.FOPException
     */
    private FOUserAgent createFOUserAgent(final String uri) throws FOPException {
        FOUserAgent userAgent = new FOUserAgent();
        if (getLogger().isDebugEnabled()) {
            getLogger().debug("createFOUserAgent() - creating FOUserAgent from: " + uri);
        }
        DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
        try {
            Configuration cfg = cfgBuilder.build(uri);
            userAgent.setUserConfig(cfg);
        } catch (ConfigurationException e) {
            throw new FOPException("createFOUserAgent() - Can not create FOUserAgent!", e);
        } catch (SAXException e) {
            throw new FOPException("createFOUserAgent() - Can not create FOUserAgent!", e);
        } catch (IOException e) {
            throw new FOPException("createFOUserAgent() - Can not create FOUserAgent!", e);
        }
        return userAgent;
    }

}

