/*****************************************************************************
 * 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.extension.svg;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.lang.ref.SoftReference;

import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.CSSUtilities;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.SVGBrokenLinkProvider;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.bridge.ViewBox;
import org.apache.batik.ext.awt.image.renderable.ClipRable8Bit;
import org.apache.batik.ext.awt.image.renderable.Filter;
import org.apache.batik.ext.awt.image.spi.ImageTagRegistry;
import org.apache.batik.gvt.AbstractGraphicsNode;
import org.apache.batik.gvt.CompositeGraphicsNode;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.RasterImageNode;
import org.apache.batik.util.ParsedURL;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGSVGElement;

/**
 * RasterRable This is used to wrap a Rendered Image back into the
 * RenderableImage world.
 *
 * @author <a href="mailto:Thomas.DeWeese@Kodak.com>Thomas DeWeese</a>
 * @version $Id: MultiResGraphicsNode.java,v 1.7 2003/06/05 10:03:16 deweese Exp $
 */
public class MultiResGraphicsNode
    extends AbstractGraphicsNode implements SVGConstants {

    SoftReference [] srcs;
    Element       [] srcElems;
    Dimension     [] minSz;
    Dimension     [] maxSz;
    Rectangle2D      bounds;

    BridgeContext  ctx;

    Element multiImgElem;

    public MultiResGraphicsNode(Element multiImgElem,
                                Rectangle2D  bounds,
                                Element   [] srcElems,
                                Dimension [] minSz,
                                Dimension [] maxSz,
                                BridgeContext ctx) {

        this.multiImgElem = multiImgElem;
        this.srcElems     = new Element  [srcElems.length];
        this.minSz        = new Dimension[srcElems.length];
        this.maxSz        = new Dimension[srcElems.length];
        this.ctx          = ctx;

        for (int i=0; i<srcElems.length; i++) {
            this.srcElems[i] = srcElems[i];
            this.minSz[i]    = minSz[i];
            this.maxSz[i]    = maxSz[i];
        }

        this.srcs = new SoftReference[srcElems.length];
        this.bounds = bounds;
    }

    /**
     * Paints this node without applying Filter, Mask, Composite, and clip.
     *
     * @param g2d the Graphics2D to use
     */
    public void primitivePaint(Graphics2D g2d) {
        // get the current affine transform
        AffineTransform at = g2d.getTransform();

        double scx = Math.sqrt(at.getShearY()*at.getShearY()+
                               at.getScaleX()*at.getScaleX());
        double scy = Math.sqrt(at.getShearX()*at.getShearX()+
                               at.getScaleY()*at.getScaleY());

        GraphicsNode gn = null;
        int idx =-1;
        double w = bounds.getWidth()*scx;
        double minDist = calcDist(w, minSz[0], maxSz[0]);
        int    minIdx = 0;
        // System.err.println("Width: " + w);
        for (int i=0; i<minSz.length; i++) {
            double dist = calcDist(w, minSz[i], maxSz[i]);
            // System.err.println("Dist: " + dist);
            if (dist < minDist) {
                minDist = dist;
                minIdx = i;
            } 
                
            if (((minSz[i] == null) || (w >= minSz[i].width)) &&
                ((maxSz[i] == null) || (w <= maxSz[i].width))) {
                // We have a range match
                // System.err.println("Match: " + i + " " + 
                //                    minSz[i] + " -> " + maxSz[i]);
                if ((idx == -1) || (minIdx == i)) {
                    idx = i;
                }
            }
        }

        if (idx == -1)
            idx = minIdx;
        gn = getGraphicsNode(idx);

        if (gn == null) return;

        // This makes sure that the image 'pushes out' to it's pixel
        // bounderies.
        Rectangle2D gnBounds = gn.getBounds();
        double gnDevW = gnBounds.getWidth()*scx;
        double gnDevH = gnBounds.getHeight()*scy;
        double gnDevX = gnBounds.getX()*scx;
        double gnDevY = gnBounds.getY()*scy;
        double gnDevX0, gnDevX1, gnDevY0, gnDevY1;
        if (gnDevW < 0) {
            gnDevX0 = gnDevX+gnDevW;
            gnDevX1 = gnDevX;
        } else {
            gnDevX0 = gnDevX;
            gnDevX1 = gnDevX+gnDevW;
        }
        if (gnDevH < 0) {
            gnDevY0 = gnDevY+gnDevH;
            gnDevY1 = gnDevY;
        } else {
            gnDevY0 = gnDevY;
            gnDevY1 = gnDevY+gnDevH;
        }
        // This calculate the width/height in pixels given 'worst
        // case' assessment.
        gnDevW = (int)(Math.ceil(gnDevX1)-Math.floor(gnDevX0));
        gnDevH = (int)(Math.ceil(gnDevY1)-Math.floor(gnDevY0));
        scx = (gnDevW/gnBounds.getWidth())/scx;
        scy = (gnDevH/gnBounds.getHeight())/scy;

        // This scales things up slightly so our edges fall on device
        // pixel boundries.
        AffineTransform nat = g2d.getTransform();
        nat = new AffineTransform(nat.getScaleX()*scx, nat.getShearY()*scx,
                                 nat.getShearX()*scy, nat.getScaleY()*scy,
                                 nat.getTranslateX(), nat.getTranslateY());
        g2d.setTransform(nat);

        // double sx = bounds.getWidth()/sizes[idx].getWidth();
        // double sy = bounds.getHeight()/sizes[idx].getHeight();
        // System.err.println("Scale: [" + sx + ", " + sy + "]");

        gn.paint(g2d);
    }

    // This function can be tweaked to any extent.  This is a very
    // simple measure of 'goodness'.  It has two main flaws as is,
    // mostly in regards to distance calc with 'unbounded' ranges.
    // First it doesn't punish if the distance is the wrong way on the
    // unbounded range (so over a max by 10 is the same as under a max
    // by 10) this is compensated by the absolute preference for
    // matches 'in range' above.  The other issue is that unbounded
    // ranages tend to 'win' when the value is near the boundry point
    // since they use distance from the boundry point rather than the
    // middle of the range.  As it is this seems to meet all the
    // requirements of the SVG specification however.
    public double calcDist(double loc, Dimension min, Dimension max) {
        if (min == null) {
            if (max == null) 
                return 10E10; // very large number.
            else
                return Math.abs(loc-max.width);
        } else {
            if (max == null) 
                return Math.abs(loc-min.width);
            else {
                double mid = (max.width+min.width)/2.0;
                return Math.abs(loc-mid);
            }
        }
    }

    /**
     * Returns the bounds of the area covered by this node's primitive paint.
     */
    public Rectangle2D getPrimitiveBounds() {
        return bounds;
    }

    public Rectangle2D getGeometryBounds(){
        return bounds;
    }

    public Rectangle2D getSensitiveBounds(){
        return bounds;
    }

    /**
     * Returns the outline of this node.
     */
    public Shape getOutline() {
        return bounds;
    }

    public GraphicsNode getGraphicsNode(int idx) {
        // System.err.println("Getting: " + idx);
        if (srcs[idx] != null) {
            Object o = srcs[idx].get();
            if (o != null) 
                return (GraphicsNode)o;
        }
        
        try {
            GVTBuilder builder = ctx.getGVTBuilder();
            GraphicsNode gn;
            gn = builder.build(ctx, srcElems[idx]);
            srcs[idx] = new SoftReference(gn);
            return gn;
        } catch (Exception ex) { ex.printStackTrace();  }

        return null;
    }
}    

