/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/
package fr.gouv.culture.sdx.document;

import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
import fr.gouv.culture.sdx.utils.Utilities;
import org.apache.excalibur.xml.sax.SAXParser;
import org.apache.cocoon.serialization.XMLSerializer;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.cocoon.xml.dom.DOMStreamer;
import org.apache.avalon.framework.configuration.DefaultConfiguration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.xml.sax.SAXException;

import java.io.*;

/**
 * An XML document, parsable and indexable.
 */
public class XMLDocument extends AbstractIndexableDocument implements ParsableDocument {

    /** If true the source comes from a DOM. */
    private boolean fromDOM = false;

    /** Document's content comes from a DOM object. */
//  FIXME : private object never used locally [MP]
    private final static int FROM_DOM = 3;

    /** The document's content as a DOM. */
    private org.w3c.dom.Document cDOM = null;

    private String MIMETYPE = "text/xml";

    /**
     * Creates a document with a given id.
     *
     * @param   id  The document's id.
     * If logging is desired the super.getLog() should be set after creation.
     * @see #enableLogging
     */
    public XMLDocument(String id) throws SDXException {
        setId(id);
    }

    /**
     * Creates a document.
     *
     * The document's id must be given later.
     *
     * If logging is desired the super.getLog() should be set after creation.
     * @see #enableLogging
     */
    public XMLDocument() {
    }

    /** Opens a stream for reading the document's content. */
    public InputStream openStream() throws SDXException {
        if (fromDOM) {
            ByteArrayInputStream bIn = null;
            ByteArrayOutputStream bOut = null;
            try {
                /*maybe useful in the future
                try {
                    bOut = new ByteArrayOutputStream();
                    Transformer t = TransformerFactory.newInstance().newTransformer();
                    DOMSource ds = new DOMSource(cDOM);
                    StreamResult sr = new StreamResult(bOut);
                    t.transform(ds, sr);
                    return new ByteArrayInputStream(bOut);
                } catch (TransformerException e) {
                    e.printStackTrace(System.err);
                } catch (TransformerFactoryConfigurationError transformerFactoryConfigurationError) {
                    transformerFactoryConfigurationError.printStackTrace();
                }*/
                //i dont think this is the problem, but i hope so-rbp
                bOut = new ByteArrayOutputStream();
                XMLSerializer xs = new XMLSerializer();
                xs.enableLogging(super.getLog());
                xs.configure(new DefaultConfiguration("hack", ""));
                xs.setOutputStream(bOut);
                DOMStreamer streamer = new DOMStreamer(xs);
                streamer.stream(cDOM);
                bIn = new ByteArrayInputStream(bOut.toByteArray());
                return bIn;

            } catch (SAXException e) {
                //unable to parse
                String[] args = new String[1];
                args[0] = getId();
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_OPEN_STREAM, args, e);
            } catch (IOException e) {
                //unable to parse
                String[] args = new String[1];
                args[0] = getId();
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_OPEN_STREAM, args, e);
            } catch (ConfigurationException e) {
                String[] args = new String[1];
                args[0] = getId();
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_OPEN_STREAM, args, e);
            } finally {
                try {
                    if (bOut != null) bOut.close();
                    if (bIn != null) bIn.close();
                } catch (IOException e) {
                    throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_CLOSE_STREAM, null, e);
                }
            }
        } else
            return super.openStream();
    }

    /** Set the content as a DOM. */
    public void setContent(org.w3c.dom.Document d) {
        cDOM = d;
        fromDOM = true;
//        setPreferredFilename(null);
//        resetContent(FROM_DOM);   What to do here?
    }

    /** Returns the length of the document. */
    public int getLength() {
        if (fromDOM)
            return -1;
        else
            return super.getLength();
    }

    /** Parses the document. */
    public void parse(SAXParser parser) throws SDXException {
        parse(parser, this.xmlConsumer);
    }

    /** Starts the indexing process.
     *
     *@param parser     The parser to use
     *@param consumer   The consumer for the events generated by the indexing process
     */
    public void startIndexing(SAXParser parser, XMLConsumer consumer) throws SDXException {
        //verifying the consumer
        Utilities.checkXmlConsumer(super.getLog(), consumer);
        //resetting indexation data structures
        super.resetFields();
        parse(parser, consumer);
    }

    /** Parses a document using a specific consumer.
     *
     *@param parser     The parser to use
     *@param consumer   The consumer of the events generated by the parse
     */
    public void parse(SAXParser parser, XMLConsumer consumer) throws SDXException {
        if (parser == null) {
            //no parser here
            String[] args = new String[1];
            //is this a bad idea, we need some way to give the user more information about which document parsing has failed resides
            if (this.getURL() != null) args[0] = this.getURL().toExternalForm();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_PARSER_NULL, args, null);
        }
        //verifying the consumer
        Utilities.checkXmlConsumer(super.getLog(), consumer);
        try {
            parser.parse(getInputSource(), consumer);
//                this.parser.parse(new InputSource(super.openStream()), consumer);
        } catch (SAXException e) {
            //unable to parse
            String[] args = new String[2];
            //is this a bad idea, we need some way to give the user more information about which document parsing has failed resides
            if (this.getURL() != null) args[0] = this.getURL().toExternalForm();
            args[1] = e.getMessage();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_PARSE_DOC, args, e);
        } catch (IOException e) {
            //unable to parse
            String[] args = new String[2];
            //is this a bad idea, we need some way to give the user more information about which document parsing has failed resides
            if (this.getURL() != null) args[0] = this.getURL().toExternalForm();
            args[1] = e.getMessage();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_PARSE_DOC, args, e);
        }
    }

    /**Gets the docType for the document*/
    public String getDocType() {
        return Document.DOCTYPE_XML;
    }

    /**Set's the transformed document for the parent document.
     * The transformed document will have the same id and preferred
     * filename as the original.
     *
     * @param content   The byte array of data
     */
    public void setTransformedDocument(byte[] content) throws SDXException {
        if (content == null) {
            String[] args = new String[1];
            args[0] = this.getId();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_SET_TRANSFORMED_DOC, args, null);
        }
        IndexableDocument doc = new XMLDocument();
        this.transformedDoc = doc;
        this.transformedDoc.enableLogging(super.getLog());
        this.transformedDoc.setContent(content);
        this.setUpTransformedDocument();
    }

    /**Set's the transformed document for the parent document.
     * The transformed document will have the same id and preferred
     * filename as the original.
     *
     * @param file      The transformed document file
     */
    public void setTransformedDocument(File file) throws SDXException {
        if (file == null) {
            String[] args = new String[1];
            args[0] = this.getId();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_SET_TRANSFORMED_DOC, args, null);
        }
        IndexableDocument doc = new XMLDocument();
        this.transformedDoc = doc;
        this.transformedDoc.enableLogging(super.getLog());
        this.transformedDoc.setContent(file);
        this.setUpTransformedDocument();
    }

    /**Returns the mimeType field (A String) for this document*/
    public String getMimeType() {
        return this.MIMETYPE;
    }

    /**Some additional system fields adding to the Lucene document*/
    public void addAdditionalSystemFields(org.apache.lucene.document.Document doc) {
      //Nothing here
    }

}
