/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.ext.awt.image.renderable;

import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderContext;

import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.ext.awt.image.PadMode;
import org.apache.batik.ext.awt.image.SVGComposite;
import org.apache.batik.ext.awt.image.rendered.CachableRed;
import org.apache.batik.ext.awt.image.rendered.PadRed;

/**
 * Concrete implementation of the PadRable interface.
 * This pads the image to a specified rectangle in user coord system.
 *
 * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
 * @version $Id: PadRable8Bit.java,v 1.7 2003/04/11 13:57:48 vhardy Exp $
 */
public class PadRable8Bit extends AbstractRable
    implements PadRable, PaintRable {

    PadMode           padMode;
    Rectangle2D       padRect;

    public PadRable8Bit(Filter src,
                            Rectangle2D padRect,
                            PadMode     padMode) {
        super.init(src, null);
        this.padRect = padRect;
        this.padMode = padMode;
    }

    /**
     * Returns the source to be affine.
     */
    public Filter getSource() {
        return (Filter)srcs.get(0);
    }

    /**
     * Sets the source to be affine.
     * @param src image to affine.
     */
    public void setSource(Filter src) {
        super.init(src, null);
    }

    public Rectangle2D getBounds2D() {
        return (Rectangle2D)padRect.clone();
    }

    /**
     * Set the current rectangle for padding.
     * @param rect the new rectangle to use for pad.
     */
    public void setPadRect(Rectangle2D rect) {
        touch();
        this.padRect = rect;
    }

    /**
     * Get the current rectangle for padding
     * @returns Rectangle currently in use for pad.
     */
    public Rectangle2D getPadRect() {
        return (Rectangle2D)padRect.clone();
    }

    /**
     * Set the current extension mode for pad
     * @param mode the new pad mode
     */
    public void setPadMode(PadMode padMode) {
        touch();
        this.padMode = padMode;
    }

    /**
     * Get the current extension mode for pad
     * @returns Mode currently in use for pad
     */
    public PadMode getPadMode() {
        return padMode;
    }

    /**
     * Should perform the equivilent action as 
     * createRendering followed by drawing the RenderedImage to 
     * Graphics2D, or return false.
     *
     * @param g2d The Graphics2D to draw to.
     * @return true if the paint call succeeded, false if
     *         for some reason the paint failed (in which 
     *         case a createRendering should be used).
     */
    public boolean paintRable(Graphics2D g2d) {
        // This optimization only apply if we are using
        // SrcOver.  Otherwise things break...
        Composite c = g2d.getComposite();
        if (!SVGComposite.OVER.equals(c))
            return false;

        if (getPadMode() != PadMode.ZERO_PAD)
            return false;

        Rectangle2D padBounds = getPadRect();

        Shape clip = g2d.getClip();
        g2d.clip(padBounds);
        GraphicsUtil.drawImage(g2d, getSource());
        g2d.setClip(clip);
        return true;
    }

    public RenderedImage createRendering(RenderContext rc) {
        RenderingHints rh = rc.getRenderingHints();
        if (rh == null) rh = new RenderingHints(null);

        Filter src = getSource();
        Shape aoi = rc.getAreaOfInterest();

        if(aoi == null){
            aoi = getBounds2D();
        }

        AffineTransform usr2dev = rc.getTransform();

        // We only depend on our source for stuff that is inside
        // our bounds and his bounds (remember our bounds may be
        // tighter than his in one or both directions).
        Rectangle2D srect = src.getBounds2D();
        Rectangle2D rect  = getBounds2D();
        Rectangle2D arect = aoi.getBounds2D();
        
        // System.out.println("Rects Src:" + srect +
        //                    "My: " + rect +
        //                    "AOI: " + arect);
        if (arect.intersects(rect) == false)
            return null;
        Rectangle2D.intersect(arect, rect, arect);

        RenderedImage ri = null;
        if (arect.intersects(srect) == true) {
            srect = (Rectangle2D)srect.clone();
            Rectangle2D.intersect(srect, arect, srect);
            
            RenderContext srcRC = new RenderContext(usr2dev, srect, rh);
            ri = src.createRendering(srcRC);

            // System.out.println("Pad filt: " + src + " R: " +
            //                    src.getBounds2D());
        }

        // No source image so create a 1,1 transparent one...
        if (ri == null)
            ri = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);

        // org.apache.batik.test.gvt.ImageDisplay.showImage("Paded: ", ri);
        // System.out.println("RI: " + ri + " R: " + srect);

        CachableRed cr = GraphicsUtil.wrap(ri);
            
        arect = usr2dev.createTransformedShape(arect).getBounds2D();
            
        // System.out.println("Pad rect : " + arect);
        // Use arect (my bounds intersect area of interest)
        cr = new PadRed(cr, arect.getBounds(), padMode, rh);
        return cr;
    }

    public Shape getDependencyRegion(int srcIndex, Rectangle2D outputRgn) {
        if (srcIndex != 0)
            throw new IndexOutOfBoundsException("Affine only has one input");

        // We only depend on our source for stuff that is inside
        // our bounds and his bounds (remember our bounds may be
        // tighter than his in one or both directions).
        Rectangle2D srect = getSource().getBounds2D();
        if (srect.intersects(outputRgn) == false)
            return new Rectangle2D.Float();
        Rectangle2D.intersect(srect, outputRgn, srect);

        Rectangle2D bounds = getBounds2D();
        if (srect.intersects(bounds) == false)
            return new Rectangle2D.Float();
        Rectangle2D.intersect(srect, bounds, srect);
        return srect;
    }

    public Shape getDirtyRegion(int srcIndex, Rectangle2D inputRgn) {
        if (srcIndex != 0)
            throw new IndexOutOfBoundsException("Affine only has one input");

        inputRgn = (Rectangle2D)inputRgn.clone();
        Rectangle2D bounds = getBounds2D();
        // Changes in the input region don't propogate outside our
        // bounds.
        if (inputRgn.intersects(bounds) == false)
            return new Rectangle2D.Float();
        Rectangle2D.intersect(inputRgn, bounds, inputRgn);
        return inputRgn;
    }

}
