/*
 * 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.intermediate;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.xml.transform.ErrorListener;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;

import org.custommonkey.xmlunit.XMLAssert;
import org.junit.After;
import org.junit.Before;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullOutputStream;

import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.events.model.EventSeverity;
import org.apache.fop.util.ConsoleEventListenerForTests;

/**
 * Abstract base class for intermediate format tests.
 */
public abstract class AbstractIntermediateTest {

    /** the test environment */
    protected static TestAssistant testAssistant = new TestAssistant();

    /** the FOP factory */
    protected FopFactory fopFactory;

    /** the directory containing the tests */
    protected File testDir = new File("test/layoutengine/standard-testcases");
    /** the output directory for any files generated by the tests */
    protected File outputDir;

    /** the test file */
    protected File testFile;
    /** the test document as DOM */
    protected Document testDoc;
    /** the intermediate format document as DOM */
    protected Document intermediate;

    /**
     * Constructor for the test suite that is used for each test file.
     * @param testFile the test file to run
     * @throws IOException if an I/O error occurs while loading the test case
     */
    public AbstractIntermediateTest(File testFile)
            throws IOException {
        this.testFile = testFile;
    }

    @Before
    public void setUp() throws Exception {
        setupOutputDirectory();
        this.testDoc = testAssistant.loadTestCase(testFile);
        this.fopFactory = testAssistant.getFopFactory(testDoc);
        intermediate = buildIntermediateDocument(testAssistant.getTestcase2FOStylesheet());
        if (outputDir != null) {
            testAssistant.saveDOM(intermediate, new File(outputDir,
                    testFile.getName() + ".1" + getIntermediateFileExtension()));
        }
    }

    @After
    public void tearDown() throws Exception {
        //Release memory
        this.intermediate = null;
        this.fopFactory = null;
        this.testDoc = null;
    }

    /**
     * Returns the file extension for the intermediate file format.
     * @return the file extension
     */
    protected abstract String getIntermediateFileExtension();

    /**
     * Returns the MIME type for which to test or to mimic for the intermediate format.
     * @return the MIME type
     */
    protected String getTargetMIME() {
        return MimeConstants.MIME_PDF;
    }

    /**
     * Validates the intermediate format file.
     * @param doc the intermediate file
     * @throws IOException if an IO error occurs while loading the schema
     * @throws SAXException if a SAX-related exception (including a validation error) occurs
     */
    protected void validate(Document doc) throws SAXException, IOException {
        //nop by default
    }

    /**
     * Builds an intermediate format document from a source file.
     * @param templates the (optional) stylesheet
     * @return the intermediate format document as a DOM
     * @throws Exception if an error occurs while processing the document
     */
    protected abstract Document buildIntermediateDocument(Templates templates) throws Exception;

    /**
     * Creates a new FOP user agent.
     * @return the user agent
     */
    protected FOUserAgent createUserAgent() {
        FOUserAgent userAgent = fopFactory.newFOUserAgent();
        userAgent.getEventBroadcaster().addEventListener(
                new ConsoleEventListenerForTests(testFile.getName(), EventSeverity.FATAL));
        return userAgent;
    }

    /**
     * Sets up the output directory.
     */
    protected void setupOutputDirectory() {
        String s = System.getProperty("fop.intermediate.outdir");
        if (s != null && s.length() > 0) {
            outputDir = new File(s);
            outputDir.mkdirs();
        }
    }

    /**
     * Tests the area tree parser by running the parsed area tree again through the area tree
     * renderer. The source and result documents are compared to each other.
     * @throws Exception if the test fails
     */
    public void testParserToIntermediateFormat() throws Exception {
        validate(intermediate);
        Source src = new DOMSource(intermediate);
        Document doc = parseAndRenderToIntermediateFormat(src);
        if (outputDir != null) {
            File tgtFile = new File(outputDir, testFile.getName() + ".2"
                    + getIntermediateFileExtension());
            testAssistant.saveDOM(doc, tgtFile);
        }

        XMLAssert.assertXMLEqual(testFile.toString(), intermediate, doc);
    }

    /**
     * Parses the intermediate file and renders it back to the intermediate format.
     * @param src the source for the intermediate file
     * @return a DOM Document with the re-created intermediate file
     * @throws Exception if an error occurs while processing the document
     */
    protected abstract Document parseAndRenderToIntermediateFormat(Source src) throws Exception;

    /**
     * Tests the area tree parser by sending the parsed area tree to the PDF Renderer. Some
     * errors might be caught by the PDFRenderer.
     * @throws Exception if the test fails
     */
    public void testParserToPDF() throws Exception {
        OutputStream out;
        if (outputDir != null) {
            File tgtFile = new File(outputDir, testFile.getName() + ".pdf");
            out = new FileOutputStream(tgtFile);
            out = new BufferedOutputStream(out);
        } else {
            out = NullOutputStream.NULL_OUTPUT_STREAM;
        }
        try {
            Source src = new DOMSource(intermediate);
            parseAndRender(src, out);
        } finally {
            IOUtils.closeQuietly(out);
        }
    }

    /**
     * Parses and renders an intermediate format document to a final format.
     * @param src the source document
     * @param out the target output stream
     * @throws Exception if an error occurs while rendering the document
     */
    protected abstract void parseAndRender(Source src, OutputStream out)
            throws Exception;

    /**
     * Run the test.
     *
     * @throws Exception if an error occurs during the test
     */
    public abstract void runTest() throws Exception;

    /**
     * Sets an error listener which doesn't swallow errors like Xalan's default one.
     * @param transformer the transformer to set the error listener on
     */
    protected void setErrorListener(Transformer transformer) {
        transformer.setErrorListener(new ErrorListener() {

            public void error(TransformerException exception) throws TransformerException {
                throw exception;
            }

            public void fatalError(TransformerException exception) throws TransformerException {
                throw exception;
            }

            public void warning(TransformerException exception) throws TransformerException {
                //ignore
            }

        });
    }

}
