/*
 * 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.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

import org.apache.commons.io.IOUtils;

import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;
import org.apache.xmlgraphics.image.loader.impl.ImageRawStream;
import org.apache.xmlgraphics.ps.ImageEncoder;

/**
 * ImageEncoder implementation for PNG images.
 */
public class ImageEncoderPNG implements ImageEncoder {
    private final ImageRawPNG image;
    private int numberOfInterleavedComponents;

    /**
     * Main constructor
     * @param image the PNG image
     */
    public ImageEncoderPNG(ImageRawPNG image) {
        this.image = image;
        ColorModel cm = ((ImageRawPNG) this.image).getColorModel();
        if (cm instanceof IndexColorModel) {
            numberOfInterleavedComponents = 1;
        } else {
            // this can be 1 (gray), 2 (gray + alpha), 3 (rgb) or 4 (rgb + alpha)
            // numberOfInterleavedComponents = (cm.hasAlpha() ? 1 : 0) + cm.getNumColorComponents();
            numberOfInterleavedComponents = cm.getNumComponents();
        }
    }

    /** {@inheritDoc} */
    public void writeTo(OutputStream out) throws IOException {
        // TODO: refactor this code with equivalent PDF code
        InputStream in = ((ImageRawStream) image).createInputStream();
        try {
            if (numberOfInterleavedComponents == 1 || numberOfInterleavedComponents == 3) {
                // means we have Gray, RGB, or Palette
                IOUtils.copy(in, out);
            } else {
                // means we have Gray + alpha or RGB + alpha
                int numBytes = numberOfInterleavedComponents - 1; // 1 for Gray, 3 for RGB
                int numColumns = image.getSize().getWidthPx();
                InflaterInputStream infStream = new InflaterInputStream(in, new Inflater());
                DataInputStream dataStream = new DataInputStream(infStream);
                int offset = 0;
                int bytesPerRow = numberOfInterleavedComponents * numColumns;
                int filter;
                // here we need to inflate the PNG pixel data, which includes alpha, separate the alpha
                // channel and then deflate the RGB channels back again
                // TODO: not using the baos below and using the original out instead (as happens in PDF)
                // would be preferable but that does not work with the rest of the postscript code; this
                // needs to be revisited
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                DeflaterOutputStream dos = new DeflaterOutputStream(/* out */baos, new Deflater());
                while ((filter = dataStream.read()) != -1) {
                    byte[] bytes = new byte[bytesPerRow];
                    dataStream.readFully(bytes, 0, bytesPerRow);
                    dos.write((byte) filter);
                    for (int j = 0; j < numColumns; j++) {
                        dos.write(bytes, offset, numBytes);
                        offset += numberOfInterleavedComponents;
                    }
                    offset = 0;
                }
                dos.close();
                IOUtils.copy(new ByteArrayInputStream(baos.toByteArray()), out);
            }
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    /** {@inheritDoc} */
    public String getImplicitFilter() {
        String filter = "<< /Predictor 15 /Columns " + image.getSize().getWidthPx();
        filter += " /Colors " + (numberOfInterleavedComponents > 2 ? 3 : 1);
        filter += " /BitsPerComponent " + image.getBitDepth() + " >> /FlateDecode";
        return filter;
    }
}
