/*
 * 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.pdf;
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.io.IOUtils;

import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG;
import org.apache.xmlgraphics.image.loader.impl.JPEGConstants;
import org.apache.xmlgraphics.image.loader.impl.JPEGFile;
import org.apache.xmlgraphics.image.loader.util.ImageUtil;

import org.apache.fop.pdf.DCTFilter;
import org.apache.fop.pdf.PDFDeviceColorSpace;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFFilter;
import org.apache.fop.pdf.PDFFilterList;

/**
 * PDFImage implementation for the PDF renderer which handles raw JPEG images.
 * <p>
 * The JPEG is copied to the XObject's stream as-is but some elements (marker segments) are
 * filtered. For example, an embedded color profile is filtered since it is already added as
 * a PDF object and associated with the XObject. This way, the PDF file size is kept as small
 * as possible.
 */
public class ImageRawJPEGAdapter extends AbstractImageAdapter {

    private PDFFilter pdfFilter;

    /**
     * Creates a new PDFImage from an Image instance.
     * @param image the JPEG image
     * @param key XObject key
     */
    public ImageRawJPEGAdapter(ImageRawJPEG image, String key) {
        super(image, key);
    }

    /**
     * Returns the {@link ImageRawJPEG} instance for this adapter.
     * @return the image instance
     */
    public ImageRawJPEG getImage() {
        return ((ImageRawJPEG)this.image);
    }

    /** {@inheritDoc} */
    public void setup(PDFDocument doc) {
        pdfFilter = new DCTFilter();
        pdfFilter.setApplied(true);

        super.setup(doc);
    }

    /** {@inheritDoc} */
    public PDFDeviceColorSpace getColorSpace() {
        // DeviceGray, DeviceRGB, or DeviceCMYK
        return toPDFColorSpace(getImageColorSpace());
    }

    /** {@inheritDoc} */
    public int getBitsPerComponent() {
        return 8;
    }

    /** @return true for CMYK images generated by Adobe Photoshop */
    public boolean isInverted() {
        return getImage().isInverted();
    }

    /** {@inheritDoc} */
    public PDFFilter getPDFFilter() {
        return pdfFilter;
    }

    /** {@inheritDoc} */
    public void outputContents(OutputStream out) throws IOException {
        InputStream in = getImage().createInputStream();
        in = ImageUtil.decorateMarkSupported(in);
        try {
            JPEGFile jpeg = new JPEGFile(in);
            DataInput din = jpeg.getDataInput();

            //Copy the whole JPEG file except:
            // - the ICC profile
            //TODO Thumbnails could safely be skipped, too.
            //TODO Metadata (XMP, IPTC, EXIF) could safely be skipped, too.
            while (true) {
                int reclen;
                int segID = jpeg.readMarkerSegment();
                switch (segID) {
                case JPEGConstants.SOI:
                    out.write(0xFF);
                    out.write(segID);
                    break;
                case JPEGConstants.EOI:
                case JPEGConstants.SOS:
                    out.write(0xFF);
                    out.write(segID);
                    IOUtils.copy(in, out); //Just copy the rest!
                    return;
                /*
                case JPEGConstants.APP1: //Metadata
                case JPEGConstants.APPD:
                    jpeg.skipCurrentMarkerSegment();
                    break;*/
                case JPEGConstants.APP2: //ICC (see ICC1V42.pdf)
                    boolean skipICCProfile = false;
                    in.mark(16);
                    try {
                        reclen = jpeg.readSegmentLength();
                        // Check for ICC profile
                        byte[] iccString = new byte[11];
                        din.readFully(iccString);
                        if (din.skipBytes(1) != 1) { //string terminator (null byte)
                            throw new IOException("premature EOF when skipping terminator byte");
                        }
                        if ("ICC_PROFILE".equals(new String(iccString, "US-ASCII"))) {
                            skipICCProfile = (this.image.getICCProfile() != null);
                        }
                    } finally {
                        in.reset();
                    }
                    if (skipICCProfile) {
                        //ICC profile is skipped as it is already embedded as a PDF object
                        jpeg.skipCurrentMarkerSegment();
                        break;
                    }
                default:
                    out.write(0xFF);
                    out.write(segID);

                    reclen = jpeg.readSegmentLength();
                    //write short
                    out.write((reclen >>> 8) & 0xFF);
                    out.write((reclen >>> 0) & 0xFF);
                    int left = reclen - 2;
                    byte[] buf = new byte[2048];
                    while (left > 0) {
                        int part = Math.min(buf.length, left);
                        din.readFully(buf, 0, part);
                        out.write(buf, 0, part);
                        left -= part;
                    }
                }
            }
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    /** {@inheritDoc} */
    public String getFilterHint() {
        return PDFFilterList.JPEG_FILTER;
    }

}

