/*
 * SVG Salamander
 * Copyright (c) 2004, Mark McKay
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 *   - Redistributions of source code must retain the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer.
 *   - 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS 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
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, 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.
 *
 * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
 * projects can be found at http://www.kitfox.com
 *
 * Created on January 26, 2004, 9:00 AM
 */
package com.kitfox.svg;

import com.kitfox.svg.xml.StyleAttribute;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.net.URI;
import java.util.List;

/**
 * Maintains bounding box for this element
 *
 * @author Mark McKay
 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
 */
abstract public class RenderableElement extends TransformableElement
{
    AffineTransform cachedXform = null;

    Mask cachedMask;
    Filter filter;
    Shape cachedClip = null;
    public static final int VECTOR_EFFECT_NONE = 0;
    public static final int VECTOR_EFFECT_NON_SCALING_STROKE = 1;
    int vectorEffect;

    private BufferPainter.Cache bufferCache;

    /**
     * Creates a new instance of BoundedElement
     */
    public RenderableElement()
    {
    }

    public RenderableElement(String id, SVGElement parent)
    {
        super(id, parent);
    }

    BufferPainter.Cache getBufferCache()
    {
        return bufferCache;
    }

    void setBufferImage(BufferPainter.Cache bufferCache)
    {
        this.bufferCache = bufferCache;
    }

    @Override
    protected void build() throws SVGException
    {
        super.build();

        StyleAttribute sty = new StyleAttribute();

        if (getPres(sty.setName("vector-effect")))
        {
            if ("non-scaling-stroke".equals(sty.getStringValue()))
            {
                vectorEffect = VECTOR_EFFECT_NON_SCALING_STROKE;
            } else
            {
                vectorEffect = VECTOR_EFFECT_NONE;
            }
        } else
        {
            vectorEffect = VECTOR_EFFECT_NONE;
        }

        cachedMask = getMask(sty);
        filter = getFilter(sty);
    }

    public void render(Graphics2D g) throws SVGException
    {
        BufferPainter.paintElement(g, this);
    }

    private Mask getMask(StyleAttribute styleAttrib) throws SVGException
    {
        if (getStyle(styleAttrib.setName("mask"), false)
            && !"none".equals(styleAttrib.getStringValue())) {
            URI uri = styleAttrib.getURIValue(getXMLBase());
            if (uri == null) {
                return null;
            }
            return  (Mask) diagram.getUniverse().getElement(uri);
        }
        return null;
    }

    private Filter getFilter(StyleAttribute styleAttrib) throws SVGException
    {
        if (getStyle(styleAttrib.setName("filter"), false)
            && !"none".equals(styleAttrib.getStringValue())) {
            URI uri = styleAttrib.getURIValue(getXMLBase());
            if (uri == null) {
                return null;
            }
            return  (Filter) diagram.getUniverse().getElement(uri);
        }
        return null;
    }

    abstract protected void doRender(Graphics2D g) throws SVGException;

    void pick(Point2D point, boolean boundingBox, List<List<SVGElement>> retVec) throws SVGException
    {
        if (cachedMask != null)
        {
            cachedMask.pickElement(point, boundingBox, retVec, this);
        } else
        {
            doPick(point, boundingBox, retVec);
        }
    }

    protected abstract void doPick(Point2D point, boolean boundingBox, List<List<SVGElement>> retVec) throws SVGException;

    void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List<List<SVGElement>> retVec) throws SVGException
    {
        if (cachedMask != null)
        {
            cachedMask.pickElement(pickArea, ltw, boundingBox, retVec, this);
        } else
        {
            doPick(pickArea, ltw, boundingBox, retVec);
        }
    }

    protected abstract void doPick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List<List<SVGElement>> retVec) throws SVGException;

    abstract public Rectangle2D getBoundingBox() throws SVGException;
    /*
     public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
     {
     super.loaderStartElement(helper, attrs, parent);
     }
     */

    /**
     * Pushes transform stack, transforms to local coordinates and sets up
     * clipping mask.
     *
     * @param g Graphics context
     * @throws com.kitfox.svg.SVGException
     */
    protected void beginLayer(Graphics2D g) throws SVGException
    {
        if (xform != null)
        {
            cachedXform = g.getTransform();
            g.transform(xform);
        }

        StyleAttribute styleAttrib = new StyleAttribute();

        //Get clipping path
//        StyleAttribute styleAttrib = getStyle("clip-path", false);
        Shape clipPath = null;
        int clipPathUnits = ClipPath.CP_USER_SPACE_ON_USE;
        if (getStyle(styleAttrib.setName("clip-path"), false)
             && !"none".equals(styleAttrib.getStringValue()))
        {
            URI uri = styleAttrib.getURIValue(getXMLBase());
            if (uri != null)
            {
                ClipPath ele = (ClipPath) diagram.getUniverse().getElement(uri);
                clipPath = ele.getClipPathShape();
                clipPathUnits = ele.getClipPathUnits();
            }
        }

        //Return if we're out of clipping range
        if (clipPath != null)
        {
            if (clipPathUnits == ClipPath.CP_OBJECT_BOUNDING_BOX && (this instanceof ShapeElement))
            {
                Rectangle2D rect = ((ShapeElement) this).getBoundingBox();
                AffineTransform at = new AffineTransform();
                at.scale(rect.getWidth(), rect.getHeight());
                clipPath = at.createTransformedShape(clipPath);
            }

            cachedClip = g.getClip();
            if (cachedClip == null)
            {
                g.setClip(clipPath);
            } else
            {
                Area newClip = new Area(cachedClip);
                newClip.intersect(new Area(clipPath));
                g.setClip(newClip);
            }
        }
    }

    /**
     * Restores transform and clipping values to the way they were before this
     * layer was drawn.
     * @param g
     */
    protected void finishLayer(Graphics2D g)
    {
        if (cachedClip != null)
        {
            g.setClip(cachedClip);
        }

        if (cachedXform != null)
        {
            g.setTransform(cachedXform);
        }
    }
}
