// $Id: TransformerImpl.java 98 2005-02-28 21:37:10Z blindsey $

package com.jclark.xsl.trax;

import com.jclark.xsl.tr.Sheet;
import com.jclark.xsl.tr.Engine;
import com.jclark.xsl.tr.OutputMethod;

import com.jclark.xsl.om.XSLException;
import com.jclark.xsl.sax2.XSLProcessor;
import com.jclark.xsl.sax2.XSLProcessorImpl;

import com.jclark.xsl.sax2.OutputMethodHandlerImpl;

import com.jclark.xsl.sax.Destination;
import com.jclark.xsl.sax.OutputStreamDestination;

import org.xml.sax.XMLReader;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;

import java.util.Enumeration;
import java.util.Properties;
import java.util.Hashtable;

import javax.xml.transform.*;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;

import java.io.OutputStream;

/**
 * An implementation of the TrAX Transformer class.
 * Can transform a source tree into a result tree
 */
class TransformerImpl extends Transformer
{

    private URIResolver _uriResolver;
    
    private ErrorListener _errorListener;
    private XSLProcessorImpl _processor;
    private Sheet _sheet;
    private TransformerFactoryImpl _factory;

    /**
     * Create a TransformerImpl.
     */
    public TransformerImpl(Sheet sheet, Engine engine, 
                           TransformerFactoryImpl factory)
    {
        _sheet = sheet;
        _processor = new XSLProcessorImpl(sheet, engine);
        _factory = factory;
    }
    
    /**
     * Process the source tree to the output result.
     */
    public void transform(Source source, Result result)
        throws TransformerException
    {
        
        XSLProcessorImpl processor = (XSLProcessorImpl) _processor.clone();
        
        if (result instanceof StreamResult) {
            StreamResult sr = (StreamResult) result;
            
            OutputMethodHandlerImpl outputMethodHandler = 
                new OutputMethodHandlerImpl();
            
            processor.setOutputMethodHandler(outputMethodHandler);
            Destination dest;
            OutputStream ostream = sr.getOutputStream();
            if (ostream != null) {
                dest = new OutputStreamDestination(ostream);
            } else {
                // FIXME: we need to handle a characterWriter
                throw new TransformerException("cannot use Writer result");
            }

            outputMethodHandler.setDestination(dest);

        } else if (result instanceof SAXResult) {
            SAXResult sr = (SAXResult) result;
            processor.setContentHandler(sr.getHandler());
            // FIXME: set lexical handler?
        } else {
            throw new TransformerException("unrecognized Result class: " +
                                           result.getClass().getName());
        }

        try {
            // now find an adapter for the input source
            XMLReader reader = _factory.getReader(source);
            processor.setSourceReader(reader);
            String sysId = source.getSystemId();
            InputSource src = SAXSource.sourceToInputSource(source);
            if (src == null) {
                if (sysId == null) {
                    src = new InputSource("dummy");
                } else {
                    src = new InputSource(sysId);
                }
            }

            // FIXME: set error handler
            processor.parse(src);
            
        } catch (Exception ex) {
            throw new TransformerException(ex);
        }
    }
    
    /**
     * Clear all parameters set with setParameter.
     */
    public void clearParameters()
    {
        _processor.clearParameters();
    }
    
    /**
     * Add a parameter for the transformation.
     */
    public void setParameter(String name, Object value)
    {
        _processor.setParameter(name, value);
    }
    
    /**
     * Get a parameter that was explicitly set with setParameter
     * or setParameters.
     */
    public Object getParameter(String name)
    {
        return _processor.getParameter(name);
    }
    
    /**
     * Get an object that will be used to resolve URIs used in
     * document(), etc.
     */
    public URIResolver getURIResolver()
    {
        return _uriResolver;
    }
    
    /**
     * Set an object that will be used to resolve URIs used in
     * document().
     */
    public void setURIResolver(URIResolver resolver)
    {
        _uriResolver = resolver;
    }
    
    /**
     * Set the output properties for the transformation.  These
     * properties will override properties set in the Templates
     * with xsl:output.
     */
    public void setOutputProperties(Properties oformat)
        throws IllegalArgumentException
    {
        if (oformat == null) {
            _sheet.clearOutputMethodProperties();
        } else {
            for (Enumeration keys = oformat.keys(); keys.hasMoreElements(); ) {
                String key = (String)keys.nextElement();
                String val = oformat.getProperty(key);
                setOutputProperty(key, val);
            }
        }
    }
    
    /**
     * 
     */
    public Properties getOutputProperties()
    {
        return _sheet.getOutputMethodProperties();
    }
    
    /**
     * Set an output property that will be in effect for the
     * transformation.
     */
    public void setOutputProperty(String name, String value)
        throws IllegalArgumentException
    {
        if (name == null) {
            throw new IllegalArgumentException("name is null");
        }
        try {
            _sheet.setOutputMethodProperty("", name, value);
        } catch (XSLException ex) {
            throw new IllegalArgumentException(name + " = " + value);
        }
    }
    
    //      private boolean getBoolean(String value)
    //      {
    //          if ("yes".equals(value))
    //              return true;
    //          else if ("no".equals(value))
    //              return false;
    //          else
    //              throw new IllegalArgumentException("value must be 'yes' or 'no', but is '" + value + "'");
    //      }
    
    
    /**
     * Return null. See comment for getOutputProperties().
     */
    public String getOutputProperty(String name)
        throws IllegalArgumentException
    {
        if (name == null) {
            throw new IllegalArgumentException("name is null");
        } 
        return _sheet.getOutputMethodProperty("", name);
    }
    
    
    /**
     * Set the error event listener in effect for the transformation.
     */
    public void setErrorListener (ErrorListener listener)
        throws IllegalArgumentException
    {
        _errorListener = listener;
    }
    
    
    /**
     * Get the error event handler in effect for the transformation.
     * @return The current error handler, which should never be null.
     */
    public ErrorListener getErrorListener()
    {
        return _errorListener;
    }
    
}

