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

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import org.apache.commons.io.IOUtils;

import org.apache.xmlgraphics.ps.DSCConstants;
import org.apache.xmlgraphics.ps.PSResource;
import org.apache.xmlgraphics.ps.dsc.DSCException;
import org.apache.xmlgraphics.ps.dsc.DSCListener;
import org.apache.xmlgraphics.ps.dsc.DSCParser;
import org.apache.xmlgraphics.ps.dsc.DefaultNestedDocumentHandler;
import org.apache.xmlgraphics.ps.dsc.events.AbstractResourcesDSCComment;
import org.apache.xmlgraphics.ps.dsc.events.DSCAtend;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBeginDocument;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentDocumentNeededResources;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentDocumentSuppliedResources;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentEndOfFile;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentIncludeResource;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPage;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPages;

import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.render.intermediate.IFContext;

/**
 * Tests the PostScript resource optimization (selective de-duplication of
 * images that are used multiple times).
 */
public class ResourceOptimizationTestCase extends AbstractPostScriptTest {

    /**
     * Tests resource optimization.
     * @throws Exception if an error occurs
     */
    @Test
    public void testResourceOptimization() throws Exception {
        FOUserAgent ua = fopFactory.newFOUserAgent();
        PSDocumentHandler handler = new PSDocumentHandler(new IFContext(ua));
        // This is the important part: we're enabling resource optimization
        handler.getPSUtil().setOptimizeResources(true);
        ua.setDocumentHandlerOverride(handler);

        // Prepare output file
        File outputFile = renderFile(ua, "ps-resources.fo",
                "-if-l" + handler.getPSUtil().getLanguageLevel());
        verifyPostScriptFile(outputFile);
    }

    private void verifyPostScriptFile(File psFile) throws IOException, DSCException {
        InputStream in = new java.io.FileInputStream(psFile);
        in = new java.io.BufferedInputStream(in);
        try {
            DSCParser parser = new DSCParser(in);
            parser.setCheckEOF(false);

            //The first form is for arrow_down_small.png (to be reused)
            PSResource form1 = new PSResource(PSResource.TYPE_FORM, "FOPForm:1");
            PSResource helvetica = new PSResource(PSResource.TYPE_FONT, "Helvetica");
            PSResource helveticaBold = new PSResource(PSResource.TYPE_FONT, "Helvetica-Bold");

            PSResource res;
            DSCCommentPages pages = (DSCCommentPages)gotoDSCComment(parser, DSCConstants.PAGES);
            assertEquals(2, pages.getPageCount());

            DSCCommentDocumentSuppliedResources supplied
                = (DSCCommentDocumentSuppliedResources)gotoDSCComment(parser,
                        DSCConstants.DOCUMENT_SUPPLIED_RESOURCES);
            Set resources = supplied.getResources();
            assertEquals(5, resources.size());
            assertTrue(resources.contains(form1));
            assertTrue("Expected barcode.eps as supplied resource",
                    resources.contains(new PSResource(PSResource.TYPE_FILE,
                            "test/resources/images/barcode.eps")));

            DSCCommentDocumentNeededResources needed
                = (DSCCommentDocumentNeededResources)gotoDSCComment(parser,
                        DSCConstants.DOCUMENT_NEEDED_RESOURCES);
            resources = needed.getResources();
            assertEquals(2, resources.size());
            assertTrue("Expected Helvetica as needed resource",
                    resources.contains(new PSResource(PSResource.TYPE_FONT, "Helvetica")));
            assertTrue("Expected Helvetica-Bold as needed resource",
                    resources.contains(new PSResource(PSResource.TYPE_FONT, "Helvetica-Bold")));

            //Some document structure checking
            assertNotNull(gotoDSCComment(parser, DSCConstants.BEGIN_DEFAULTS));
            assertNotNull(gotoDSCComment(parser, DSCConstants.END_DEFAULTS));
            assertNotNull(gotoDSCComment(parser, DSCConstants.BEGIN_PROLOG));
            assertNotNull(gotoDSCComment(parser, DSCConstants.END_PROLOG));
            assertNotNull(gotoDSCComment(parser, DSCConstants.BEGIN_SETUP));

            //Check includes for the two referenced base 14 fonts
            DSCCommentIncludeResource include;
            Collection strings = new java.util.HashSet(
                    Arrays.asList(new String[] {"Helvetica", "Helvetica-Bold"}));
            for (int i = 0; i < 2; i++) {
                include = (DSCCommentIncludeResource)gotoDSCComment(
                        parser, DSCConstants.INCLUDE_RESOURCE);
                res = include.getResource();
                assertEquals(PSResource.TYPE_FONT, res.getType());
                strings.remove(res.getName());
            }
            assertEquals(0, strings.size());

            checkResourceComment(parser, DSCConstants.BEGIN_RESOURCE,
                    new PSResource(PSResource.TYPE_ENCODING, "WinAnsiEncoding"));

            //Here, we encounter form 1 again
            checkResourceComment(parser, DSCConstants.BEGIN_RESOURCE, form1);

            assertNotNull(gotoDSCComment(parser, DSCConstants.END_SETUP));
            //Now the actual pages begin

            //---=== Page 1 ===---
            DSCCommentPage page = (DSCCommentPage)gotoDSCComment(parser, DSCConstants.PAGE);
            assertEquals(1, page.getPagePosition());

            assertEquals(DSCAtend.class,
                    gotoDSCComment(parser, DSCConstants.PAGE_RESOURCES).getClass());
            assertNotNull(gotoDSCComment(parser, DSCConstants.BEGIN_PAGE_SETUP));
            assertNotNull(gotoDSCComment(parser, DSCConstants.END_PAGE_SETUP));

            PSResource form2 = new PSResource(PSResource.TYPE_FORM, "FOPForm:2");
            checkResourceComment(parser, DSCConstants.BEGIN_RESOURCE, form2);
            assertNotNull(gotoDSCComment(parser, DSCConstants.PAGE_TRAILER));

            AbstractResourcesDSCComment pageResources;
            pageResources = (AbstractResourcesDSCComment)gotoDSCComment(
                    parser, DSCConstants.PAGE_RESOURCES);
            resources = pageResources.getResources();
            assertEquals(5, resources.size());
            assertTrue(resources.contains(form1));
            assertTrue(resources.contains(form2));
            assertTrue(resources.contains(helvetica));
            assertTrue(resources.contains(helveticaBold));

            //---=== Page 2 ===---
            page = (DSCCommentPage)gotoDSCComment(parser, DSCConstants.PAGE);
            assertEquals(2, page.getPagePosition());

            assertEquals(DSCAtend.class,
                    gotoDSCComment(parser, DSCConstants.PAGE_RESOURCES).getClass());
            assertNotNull(gotoDSCComment(parser, DSCConstants.BEGIN_PAGE_SETUP));
            assertNotNull(gotoDSCComment(parser, DSCConstants.END_PAGE_SETUP));

            DSCCommentBeginDocument beginDocument;
            beginDocument = (DSCCommentBeginDocument)gotoDSCComment(
                    parser, DSCConstants.BEGIN_DOCUMENT);
            assertEquals("test/resources/images/barcode.eps",
                    beginDocument.getResource().getName());
            DSCListener listener = new DefaultNestedDocumentHandler(null);
            listener.processEvent(beginDocument, parser);

            //And again (the barcode is generated twice)
            beginDocument = (DSCCommentBeginDocument)gotoDSCComment(
                    parser, DSCConstants.BEGIN_DOCUMENT);
            assertEquals("test/resources/images/barcode.eps",
                    beginDocument.getResource().getName());
            listener.processEvent(beginDocument, parser);

            assertNotNull(gotoDSCComment(parser, DSCConstants.PAGE_TRAILER));
            pageResources = (AbstractResourcesDSCComment)gotoDSCComment(
                    parser, DSCConstants.PAGE_RESOURCES);
            resources = pageResources.getResources();
            assertEquals(6, resources.size());
            assertTrue(resources.contains(form1));
            assertFalse(resources.contains(form2));
            assertTrue(resources.contains(helvetica));
            assertTrue(resources.contains(helveticaBold));
            assertTrue(resources.contains(beginDocument.getResource()));

            assertNotNull(gotoDSCComment(parser, DSCConstants.TRAILER));
            //No headers in between, as they should have been put at the beginning of the file
            assertEquals(DSCCommentEndOfFile.class, parser.nextEvent().asDSCComment().getClass());

        } finally {
            IOUtils.closeQuietly(in);
        }
    }

}
