/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Batik" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/

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.7 2003/09/25 00:45:11 deweese 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("Null Source for Filter Chain");
        }
        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);
    }
}
