/*
 * 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: TIFFImageWriter.java 750418 2009-03-05 11:03:54Z vhennebert $ */

package org.apache.xmlgraphics.image.writer.internal;

import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.xmlgraphics.image.codec.tiff.TIFFEncodeParam;
import org.apache.xmlgraphics.image.codec.tiff.TIFFField;
import org.apache.xmlgraphics.image.codec.tiff.TIFFImageDecoder;
import org.apache.xmlgraphics.image.codec.tiff.TIFFImageEncoder;
import org.apache.xmlgraphics.image.writer.AbstractImageWriter;
import org.apache.xmlgraphics.image.writer.ImageWriter;
import org.apache.xmlgraphics.image.writer.ImageWriterParams;
import org.apache.xmlgraphics.image.writer.MultiImageWriter;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;

/**
 * ImageWriter implementation that uses the internal TIFF codec to
 * write TIFF files.
 *
 * @version $Id: TIFFImageWriter.java 750418 2009-03-05 11:03:54Z vhennebert $
 */
public class TIFFImageWriter extends AbstractImageWriter {

    /**
     * @see ImageWriter#writeImage(java.awt.image.RenderedImage, java.io.OutputStream)
     */
    public void writeImage(RenderedImage image, OutputStream out)
            throws IOException {
        writeImage(image, out, null);
    }

    /**
     * @see ImageWriter#writeImage(java.awt.image.RenderedImage, java.io.OutputStream, ImageWriterParams)
     */
    public void writeImage(RenderedImage image, OutputStream out,
            ImageWriterParams params) throws IOException {
        TIFFEncodeParam encodeParams = createTIFFEncodeParams(params);
        updateParams(encodeParams, params, image);
        TIFFImageEncoder encoder = new TIFFImageEncoder(out, encodeParams);
        encoder.encode(image);
    }

    /**
     * This method updates the encode parameters based on the image to be encoded. One thing done
     * here is to initialize the JPEG encoding parameters if JPEG compression is selected.
     * @param encodeParams the TIFF encoding parameters
     * @param image the image to be encoded
     */
    private void updateParams(TIFFEncodeParam encodeParams, ImageWriterParams params,
            RenderedImage image) {
        if (encodeParams.getCompression() == TIFFEncodeParam.COMPRESSION_JPEG_TTN2) {
            ColorModel cm = image.getColorModel();
            int imageType = cm.getColorSpace().getType();
            int colorID;
            //The following translation table is taken from the Javadoc for JPEGEncodeParam.
            switch (imageType) {
            case ColorSpace.TYPE_GRAY:
                colorID = JPEGEncodeParam.COLOR_ID_GRAY;
                break;
            case ColorSpace.TYPE_RGB:
                if (cm.hasAlpha()) {
                    colorID = JPEGEncodeParam.COLOR_ID_YCbCrA;
                } else {
                    colorID = JPEGEncodeParam.COLOR_ID_YCbCr;
                }
                break;
            case ColorSpace.TYPE_YCbCr:
                if (cm.hasAlpha()) {
                    colorID = JPEGEncodeParam.COLOR_ID_YCbCrA;
                } else {
                    colorID = JPEGEncodeParam.COLOR_ID_YCbCr;
                }
                break;
            case ColorSpace.TYPE_CMYK:
                colorID = JPEGEncodeParam.COLOR_ID_CMYK;
                break;
            default:
                //TODO Don't know how to determine whether image is PYCC or not
                //(see JPEGEncodeParam)
                colorID = JPEGEncodeParam.COLOR_ID_UNKNOWN;
            }
            JPEGEncodeParam jpegParam = JPEGCodec.getDefaultJPEGEncodeParam(
                    image.getData(), colorID);
            if (params.getJPEGQuality() != null || params.getJPEGForceBaseline() != null) {
                float qual = (params.getJPEGQuality() != null
                        ? params.getJPEGQuality().floatValue() : 0.75f);
                boolean force = (params.getJPEGForceBaseline() != null
                        ? params.getJPEGForceBaseline().booleanValue() : false);
                jpegParam.setQuality(qual, force);
            }
            encodeParams.setJPEGEncodeParam(jpegParam);
        }
    }

    private TIFFEncodeParam createTIFFEncodeParams(ImageWriterParams params) {
        TIFFEncodeParam encodeParams = new TIFFEncodeParam();
        if (params == null) {
            encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_NONE);
        } else {
            if (params.getCompressionMethod() == null) {
                //PackBits as default
                encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_PACKBITS);
            } else if ("PackBits".equalsIgnoreCase(params.getCompressionMethod())) {
                encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_PACKBITS);
            } else if ("NONE".equalsIgnoreCase(params.getCompressionMethod())) {
                encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_NONE);
            } else if ("JPEG".equalsIgnoreCase(params.getCompressionMethod())) {
                encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_JPEG_TTN2);
            } else if ("Deflate".equalsIgnoreCase(params.getCompressionMethod())) {
                encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_DEFLATE);
            } else {
                throw new UnsupportedOperationException("Compression method not supported: "
                        + params.getCompressionMethod());
            }

            if (params.getResolution() != null) {
                // Set target resolution
                float pixSzMM = 25.4f / params.getResolution().floatValue();
                // num Pixs in 100 Meters
                int numPix = (int)(((1000 * 100) / pixSzMM) + 0.5);
                int denom = 100 * 100;  // Centimeters per 100 Meters;
                long [] rational = {numPix, denom};
                TIFFField [] fields = {
                    new TIFFField(TIFFImageDecoder.TIFF_RESOLUTION_UNIT,
                                  TIFFField.TIFF_SHORT, 1,
                                  new char[] {(char)3}),
                    new TIFFField(TIFFImageDecoder.TIFF_X_RESOLUTION,
                                  TIFFField.TIFF_RATIONAL, 1,
                                  new long[][] {rational}),
                    new TIFFField(TIFFImageDecoder.TIFF_Y_RESOLUTION,
                                  TIFFField.TIFF_RATIONAL, 1,
                                  new long[][] {rational})
                        };
                encodeParams.setExtraFields(fields);
            }
        }
        return encodeParams;
    }

    /**
     * @see ImageWriter#getMIMEType()
     */
    public String getMIMEType() {
        return "image/tiff";
    }

    /**
     * @see org.apache.xmlgraphics.image.writer.ImageWriter#createMultiImageWriter(
     *          java.io.OutputStream)
     */
    public MultiImageWriter createMultiImageWriter(OutputStream out) throws IOException {
        return new TIFFMultiImageWriter(out);
    }

    /** @see org.apache.xmlgraphics.image.writer.ImageWriter#supportsMultiImageWriter() */
    public boolean supportsMultiImageWriter() {
        return true;
    }

    private class TIFFMultiImageWriter implements MultiImageWriter {

        private OutputStream out;
        private TIFFEncodeParam encodeParams;
        private TIFFImageEncoder encoder;
        private Object context;

        public TIFFMultiImageWriter(OutputStream out) throws IOException {
            this.out = out;
        }

        public void writeImage(RenderedImage image, ImageWriterParams params) throws IOException {
            if (encoder == null) {
                encodeParams = createTIFFEncodeParams(params);
                updateParams(encodeParams, params, image);
                encoder = new TIFFImageEncoder(out, encodeParams);
            }
            context = encoder.encodeMultiple(context, image);
        }

        public void close() throws IOException {
            if (encoder != null) {
                encoder.finishMultiple(context);
            }
            encoder = null;
            encodeParams = null;
            out.flush();
        }

    }


}
