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

import java.awt.Point;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;

import org.apache.xmlgraphics.image.GraphicsUtil;

// CSOFF: NeedBraces
// CSOFF: WhitespaceAfter
// CSOFF: WhitespaceAround

/**
 * This allows you to specify the ColorModel, Alpha premult and/or
 * SampleModel to be used for output.  If the input image lacks
 * Alpha and alpha is included in output then it is filled with
 * alpha=1.  In all other cases bands are simply copied.
 *
 * @version $Id$
 *
 * Originally authored by Thomas DeWeese.
 */
public class FormatRed extends AbstractRed {

    public static CachableRed construct(CachableRed src, ColorModel cm) {
        ColorModel srcCM = src.getColorModel();
        if ((cm.hasAlpha() != srcCM.hasAlpha())
            || (cm.isAlphaPremultiplied() != srcCM.isAlphaPremultiplied())) {
            return new FormatRed(src, cm);
        }

        if (cm.getNumComponents() != srcCM.getNumComponents()) {
            throw new IllegalArgumentException(
                "Incompatible ColorModel given");
        }


        if ((srcCM instanceof ComponentColorModel)
            && (cm    instanceof ComponentColorModel)) {
            return src;
        }

        if ((srcCM instanceof DirectColorModel)
            && (cm    instanceof DirectColorModel)) {
            return src;
        }

        return new FormatRed(src, cm);
    }

    /**
     * Construct an instance of CachableRed around a BufferedImage.
     */
    public FormatRed(CachableRed cr, SampleModel sm) {
        super(cr, cr.getBounds(),
              makeColorModel(cr, sm), sm,
              cr.getTileGridXOffset(),
              cr.getTileGridYOffset(),
              null);
    }

    public FormatRed(CachableRed cr, ColorModel cm) {
        super(cr, cr.getBounds(),
              cm, makeSampleModel(cr, cm),
              cr.getTileGridXOffset(),
              cr.getTileGridYOffset(),
              null);
    }

    /**
     * fetch the source image for this node.
     */
    public CachableRed getSource() {
        return (CachableRed)getSources().get(0);
    }

    public Object getProperty(String name) {
        return getSource().getProperty(name);
    }

    public String [] getPropertyNames() {
        return getSource().getPropertyNames();
    }

    public WritableRaster copyData(WritableRaster wr) {
        ColorModel  cm    = getColorModel();
        CachableRed cr    = getSource();
        ColorModel  srcCM = cr.getColorModel();
        SampleModel srcSM = cr.getSampleModel();
        srcSM = srcSM.createCompatibleSampleModel(wr.getWidth(),
                                                  wr.getHeight());
        WritableRaster srcWR;
        srcWR = Raster.createWritableRaster(srcSM, new Point(wr.getMinX(),
                                                             wr.getMinY()));
        getSource().copyData(srcWR);

        BufferedImage srcBI = new BufferedImage(
            srcCM, srcWR.createWritableTranslatedChild(0, 0),
             srcCM.isAlphaPremultiplied(), null);
        BufferedImage dstBI = new BufferedImage(
            cm, wr.createWritableTranslatedChild(0, 0),
             cm.isAlphaPremultiplied(), null);

        GraphicsUtil.copyData(srcBI, dstBI);

        return wr;
    }

    public static SampleModel makeSampleModel(CachableRed cr, ColorModel cm) {
        SampleModel srcSM = cr.getSampleModel();
        return cm.createCompatibleSampleModel(srcSM.getWidth(),
                                              srcSM.getHeight());
    }

    public static ColorModel makeColorModel(CachableRed cr, SampleModel sm) {
        ColorModel srcCM = cr.getColorModel();
        ColorSpace cs    = srcCM.getColorSpace();

        int bands = sm.getNumBands();

        int bits;
        int dt = sm.getDataType();
        switch (dt) {
        case DataBuffer.TYPE_BYTE:   bits = 8;  break;
        case DataBuffer.TYPE_SHORT:  bits = 16; break;
        case DataBuffer.TYPE_USHORT: bits = 16; break;
        case DataBuffer.TYPE_INT:    bits = 32; break;
        default:
            throw new IllegalArgumentException(
                "Unsupported DataBuffer type: " + dt);
        }

        boolean hasAlpha = srcCM.hasAlpha();
        if (hasAlpha) {
            // if Src has Alpha then our out bands must
            // either be one less than the source (no out alpha)
            // or equal (still has alpha)
            if (bands == srcCM.getNumComponents() - 1) {
                hasAlpha = false;
            } else if (bands != srcCM.getNumComponents()) {
                throw new IllegalArgumentException(
                    "Incompatible number of bands in and out");
            }
        } else {
            if (bands == srcCM.getNumComponents() + 1) {
                hasAlpha = true;
            } else if (bands != srcCM.getNumComponents()) {
                throw new IllegalArgumentException(
                    "Incompatible number of bands in and out");
            }
        }

        boolean preMult  = srcCM.isAlphaPremultiplied();
        if (!hasAlpha) {
            preMult = false;
        }

        if (sm instanceof ComponentSampleModel) {
            int [] bitsPer = new int[bands];
            for (int i = 0; i < bands; i++) {
                bitsPer[i] = bits;
            }

            return new ComponentColorModel(
                cs, bitsPer, hasAlpha, preMult,
                 hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE,
                 dt);
        } else if (sm instanceof SinglePixelPackedSampleModel) {
            SinglePixelPackedSampleModel sppsm;
            sppsm = (SinglePixelPackedSampleModel)sm;
            int[] masks  = sppsm.getBitMasks();
            if (bands == 4) {
                return new DirectColorModel(
                    cs, bits, masks[0], masks[1], masks[2], masks[3],
                     preMult, dt);
            } else if (bands == 3) {
                return new DirectColorModel(
                    cs, bits, masks[0], masks[1], masks[2], 0x0,
                     preMult, dt);
            } else {
                throw new IllegalArgumentException(
                    "Incompatible number of bands out for ColorModel");
            }
        }
        throw new IllegalArgumentException(
            "Unsupported SampleModel Type");
    }
}
