/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

/* $Id$ */

package org.apache.fop.render.intermediate.util;

import java.awt.Dimension;

import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;

import org.apache.xmlgraphics.xmp.Metadata;

import org.apache.fop.render.intermediate.IFDocumentHandler;
import org.apache.fop.render.intermediate.IFDocumentNavigationHandler;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFParser;

/**
 * This class allows to concatenate multiple intermediate format files to a single output file
 * in the final format. It is based on the SAX API and is therefore very fast and does not
 * require to load the individual documents into memory as DOM documents, for example.
 * <p>
 * Note: This class will filter/ignore any document navigation events. Support for this may be
 * added later.
 * <p>
 * Note: document-level extensions will only be transferred from the first document passed in.
 * If you need to merge extensions from all the concatenated documents, you may have to merge
 * these manually on the XML level, for example using XSLT.
 */
public class IFConcatenator {

    private IFDocumentHandler targetHandler;

    private int nextPageIndex;
    private boolean inFirstDocument = true;

    /**
     * Creates a new IF concatenator.
     * @param targetHandler the target document handler
     * @param metadata the metadata object for the generated file (may be null)
     * @throws IFException if an IF-related error occurs
     */
    public IFConcatenator(IFDocumentHandler targetHandler, Metadata metadata) throws IFException {
        this.targetHandler = targetHandler;
        startDocument(metadata);
    }

    private void startDocument(Metadata metadata) throws IFException {
        this.targetHandler.startDocument();
        this.targetHandler.startDocumentHeader();
        if (metadata != null) {
            this.targetHandler.handleExtensionObject(metadata);
        }
        this.targetHandler.endDocumentHeader();
    }

    private void endDocument() throws IFException {
        this.targetHandler.startPageTrailer();
        this.targetHandler.endPageTrailer();
        this.targetHandler.endDocument();
    }

    /**
     * Returns the target document handler.
     * @return the target document handler
     */
    protected IFDocumentHandler getTargetHandler() {
        return this.targetHandler;
    }

    /**
     * Properly finishes the current output file by creating an empty document trailer and calling
     * {@link IFDocumentHandler#endDocument()}.
     * @throws IFException if an IF-related error occurs
     */
    public void finish() throws IFException {
        endDocument();
    }

    /**
     * Appends another intermediate format document to the current output file. All document-level
     * content (i.e. the document header and trailer) is ignored. This method shall not be called
     * after {@link #finish()} has been called.
     * @param src the JAXP Source identifying the input document
     * @throws TransformerException if an XML-related exception occurs during
     * @throws IFException if an IF-related error occurs
     */
    public void appendDocument(Source src) throws TransformerException, IFException {
        IFParser parser = new IFParser();
        parser.parse(src, new IFPageSequenceFilter(getTargetHandler()),
                getTargetHandler().getContext().getUserAgent());
    }

    private class IFPageSequenceFilter extends IFDocumentHandlerProxy {

        private boolean inPageSequence;

        public IFPageSequenceFilter(IFDocumentHandler delegate) {
            super(delegate);
        }

        /** {@inheritDoc} */
        public void startDocument() throws IFException {
            //ignore
        }

        /** {@inheritDoc} */
        public void startDocumentHeader() throws IFException {
            //ignore
        }

        /** {@inheritDoc} */
        public void endDocumentHeader() throws IFException {
            //ignore
        }

        /** {@inheritDoc} */
        public void startPageSequence(String id) throws IFException {
            assert !this.inPageSequence;
            this.inPageSequence = true;

            super.startPageSequence(id);
        }

        /** {@inheritDoc} */
        public void startPage(int index, String name, String pageMasterName, Dimension size)
                throws IFException {
            //Adjust page indices
            super.startPage(nextPageIndex, name, pageMasterName, size);
            nextPageIndex++;
        }

        /** {@inheritDoc} */
        public void endPageSequence() throws IFException {
            super.endPageSequence();

            assert this.inPageSequence;
            this.inPageSequence = false;
        }

        /** {@inheritDoc} */
        public void startDocumentTrailer() throws IFException {
            //ignore
        }

        /** {@inheritDoc} */
        public void endDocumentTrailer() throws IFException {
            //ignore
        }

        /** {@inheritDoc} */
        public void endDocument() throws IFException {
            //ignore
            inFirstDocument = false;
        }

        /** {@inheritDoc} */
        public void handleExtensionObject(Object extension) throws IFException {
            if (inPageSequence || inFirstDocument) {
                //Only pass through when inside page-sequence
                //or for the first document (for document-level extensions).
                super.handleExtensionObject(extension);
            }
            //Note:Extensions from non-first documents are ignored!
        }

        /** {@inheritDoc} */
        public IFDocumentNavigationHandler getDocumentNavigationHandler() {
            return null; //Document Navigation is filtered!!!
        }

    }
}
