/*****************************************************************************
 * 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.geom.Rectangle2D;
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;

/**
 * Implements a filter chain. A filter chain is defined by its
 * filter region (i.e., the bounding box of its input/output), its
 * filter resolution and its source. Its source cannot be null,
 * but its resolution can. <br />
 * The filter chain decomposes as follows: 
 * <ul>
 *  <li>A pad operation that makes the input image a big as the
 *      filter region.</li>
 *  <li>If there is a filterResolution specified along at least
 *      one of the axis, a <tt>AffineRable</tt>
 * </ul>
 *
 * @author <a href="mailto:vincent.hardy@eng.sun.com>Vincent Hardy</a>
 * @version $Id: FilterChainRable8Bit.java,v 1.5 2003/04/11 13:57:48 vhardy Exp $
 */
public class FilterChainRable8Bit extends AbstractRable
    implements FilterChainRable, PaintRable {
    /**
     * Resolution along the X axis
     */
    private int filterResolutionX;

    /**
     * Resolution along the Y axis
     */
    private int filterResolutionY;

    /**
     * The chain's source
     */
    private Filter chainSource;

    /**
     * Scale operation. May be null
     */
    private FilterResRable filterRes;

    /**
     * Crop operation.
     */
    private PadRable crop;

    /**
     * Filter region
     */
    private Rectangle2D filterRegion;

    /**
     * Default constructor.
     */
    public FilterChainRable8Bit(Filter source, Rectangle2D filterRegion){
        if(source == null){
            throw new IllegalArgumentException();
        }
        if(filterRegion == null){
            throw new IllegalArgumentException();
        }

        // Build crop with chain source and dummy region (will be lazily evaluated
        // later on).
        Rectangle2D padRect = (Rectangle2D)filterRegion.clone();
        crop = new PadRable8Bit(source, padRect, 
                                    PadMode.ZERO_PAD);

        // Keep a reference to the chain source and filter
        // regions.
        this.chainSource = source;
        this.filterRegion = filterRegion;

        // crop is the real shource for this filter
        // The filter chain is a simple passthrough to its
        // crop node.
        init(crop); 
  
    }

    /**
     * Returns the resolution along the X axis.
     */
    public int getFilterResolutionX(){
        return filterResolutionX;
    }

    /**
     * Sets the resolution along the X axis, i.e., the maximum
     * size for intermediate images along that axis.
     * If filterResolutionX is less than zero, no filter resolution
     * is forced on the filter chain. If filterResolutionX is zero,
     * then the filter returns null. If filterResolutionX is positive,
     * then the filter resolution is applied.
     */
    public void setFilterResolutionX(int filterResolutionX){
        touch();
        this.filterResolutionX = filterResolutionX;

        setupFilterRes();
    }

    /**
     * Returns the resolution along the Y axis.
     */
    public int getFilterResolutionY(){
        return filterResolutionY;
    }

    /**
     * Sets the resolution along the Y axis, i.e., the maximum
     * size for intermediate images along that axis.
     * If filterResolutionY is zero or less, the value of
     * filterResolutionX is used.
     */
    public void setFilterResolutionY(int filterResolutionY){
        touch();
        this.filterResolutionY = filterResolutionY;
        setupFilterRes();
    }
    
    /**
     * Implementation. Checks the current value of the 
     * filterResolutionX and filterResolutionY attribute and 
     * setup the filterRes operation accordingly.
     */
    private void setupFilterRes(){
        if(filterResolutionX >=0){
            if(filterRes == null){
                filterRes = new FilterResRable8Bit();
                filterRes.setSource(chainSource);
            }
            
            filterRes.setFilterResolutionX(filterResolutionX);
            filterRes.setFilterResolutionY(filterResolutionY);
        }
        else{
            // X is negative, this disables the resolution filter.
            filterRes = null;
        }

        // Now, update the crop source to reflect the filterRes
        // settings.
        if(filterRes != null){
            crop.setSource(filterRes);
        }
        else{
            crop.setSource(chainSource);
        }
    }
    
    /**
     * Sets the filter output area, in user space. 
     * A null value is illegal.
     */
    public void setFilterRegion(Rectangle2D filterRegion){
        if(filterRegion == null){
            throw new IllegalArgumentException();
        }
        touch();
        this.filterRegion = filterRegion;
     }

    /**
     * Returns the filter output area, in user space
     */
    public Rectangle2D getFilterRegion(){
        return filterRegion;
    }

    /**
     * Returns the source of the chain. Note that a crop and
     * affine operation may be inserted before the source, 
     * depending on the filterRegion and filterResolution 
     * parameters.
     */
    public Filter getSource() {
        return (Filter)crop;
    }
    
    /**
     * Sets the source to be src.
     * @param src image to the chain.
     */
    public void setSource(Filter chainSource) {
        if(chainSource == null){
            throw new IllegalArgumentException();
        }
        touch();
        this.chainSource = chainSource;
        
        if(filterRes == null){
            crop.setSource(chainSource);
        }
        else{
            filterRes.setSource(chainSource);
        }
    }

    /**
     * Returns this filter's bounds
     */
    public Rectangle2D getBounds2D(){
        return (Rectangle2D)filterRegion.clone();
    }

    /**
     * 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;
        
        GraphicsUtil.drawImage(g2d, getSource());

        return true;
    }

    public RenderedImage createRendering(RenderContext context){
        return crop.createRendering(context);
    }
}
