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

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.xmlgraphics.ps.DSCConstants;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.dsc.DSCException;
import org.apache.xmlgraphics.ps.dsc.DSCFilter;
import org.apache.xmlgraphics.ps.dsc.DSCParser;
import org.apache.xmlgraphics.ps.dsc.DSCParserConstants;
import org.apache.xmlgraphics.ps.dsc.DefaultNestedDocumentHandler;
import org.apache.xmlgraphics.ps.dsc.events.DSCComment;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPage;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPages;
import org.apache.xmlgraphics.ps.dsc.events.DSCEvent;
import org.apache.xmlgraphics.ps.dsc.events.DSCHeaderComment;

/**
 * This class can extract a certain range of pages from a DSC-compliant PostScript file.
 */
public final class PageExtractor implements DSCParserConstants {

    private PageExtractor() {
    }

    /**
     * Parses a DSC-compliant file and pipes the content through to the OutputStream omitting
     * all pages not within the range.
     * @param in the InputStream to parse from
     * @param out the OutputStream to write the modified file to
     * @param from the starting page (1-based)
     * @param to the last page (inclusive, 1-based)
     * @throws IOException In case of an I/O error
     * @throws DSCException In case of a violation of the DSC spec
     */
    public static void extractPages(InputStream in, OutputStream out, int from, int to)
                throws IOException, DSCException {
        if (from <= 0) {
            throw new IllegalArgumentException("'from' page number must be 1 or higher");
        }
        if (to < from) {
            throw new IllegalArgumentException(
                    "'to' page number must be equal or larger than the 'from' page number");
        }

        DSCParser parser = new DSCParser(in);
        PSGenerator gen = new PSGenerator(out);
        parser.addListener(new DefaultNestedDocumentHandler(gen));
        int pageCount = 0;

        //Skip DSC header
        DSCHeaderComment header = DSCTools.checkAndSkipDSC30Header(parser);
        header.generate(gen);
        //Set number of pages
        DSCCommentPages pages = new DSCCommentPages(to - from + 1);
        pages.generate(gen);

        parser.setFilter(new DSCFilter() {
            public boolean accept(DSCEvent event) {
                if (event.isDSCComment()) {
                    //Filter %%Pages which we add manually above
                    return !event.asDSCComment().getName().equals(DSCConstants.PAGES);
                } else {
                    return true;
                }
            }
        });

        //Skip the prolog and to the first page
        DSCComment pageOrTrailer = parser.nextDSCComment(DSCConstants.PAGE, gen);
        if (pageOrTrailer == null) {
            throw new DSCException("Page expected, but none found");
        }
        parser.setFilter(null); //Remove filter

        //Process individual pages (and skip as necessary)
        while (true) {
            DSCCommentPage page = (DSCCommentPage)pageOrTrailer;
            boolean validPage = (page.getPagePosition() >= from && page.getPagePosition() <= to);
            if (validPage) {
                page.setPagePosition(page.getPagePosition() - from + 1);
                page.generate(gen);
                pageCount++;
            }
            pageOrTrailer = DSCTools.nextPageOrTrailer(parser, (validPage ? gen : null));
            if (pageOrTrailer == null) {
                throw new DSCException("File is not DSC-compliant: Unexpected end of file");
            } else if (!DSCConstants.PAGE.equals(pageOrTrailer.getName())) {
                pageOrTrailer.generate(gen);
                break;
            }
        }

        //Write the rest
        while (parser.hasNext()) {
            DSCEvent event = parser.nextEvent();
            event.generate(gen);
        }
    }

}
