/*
 * 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 April 21, 2005, 10:45 AM
 */

package com.kitfox.svg.app.beans;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.net.URI;

import javax.swing.ImageIcon;

import com.kitfox.svg.SVGCache;
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGException;
import com.kitfox.svg.SVGUniverse;

/**
 *
 * @author kitfox
 */
public class SVGIcon extends ImageIcon
{
    public static final long serialVersionUID = 1;

    public static final String PROP_AUTOSIZE = "PROP_AUTOSIZE";

    private final PropertyChangeSupport changes = new PropertyChangeSupport(this);

    SVGUniverse svgUniverse = SVGCache.getSVGUniverse();
    public static final int INTERP_NEAREST_NEIGHBOR = 0;
    public static final int INTERP_BILINEAR = 1;
    public static final int INTERP_BICUBIC = 2;

    private boolean antiAlias;
    private int interpolation = INTERP_NEAREST_NEIGHBOR;
    private boolean clipToViewbox;

    URI svgURI;

//    private boolean scaleToFit;
    AffineTransform scaleXform = new AffineTransform();

    public static final int AUTOSIZE_NONE = 0;
    public static final int AUTOSIZE_HORIZ = 1;
    public static final int AUTOSIZE_VERT = 2;
    public static final int AUTOSIZE_BESTFIT = 3;
    public static final int AUTOSIZE_STRETCH = 4;
    private int autosize = AUTOSIZE_NONE;

    Dimension preferredSize;

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

    public void addPropertyChangeListener(PropertyChangeListener p)
    {
        changes.addPropertyChangeListener(p);
    }

    public void removePropertyChangeListener(PropertyChangeListener p)
    {
        changes.removePropertyChangeListener(p);
    }

    @Override
    public Image getImage()
    {
        BufferedImage bi = new BufferedImage(getIconWidth(), getIconHeight(), BufferedImage.TYPE_INT_ARGB);
        paintIcon(null, bi.getGraphics(), 0, 0);
        return bi;
    }

    /**
     * @return height of this icon
     */
    public int getIconHeightIgnoreAutosize()
    {
        if (preferredSize != null &&
                (autosize == AUTOSIZE_VERT || autosize == AUTOSIZE_STRETCH
                || autosize == AUTOSIZE_BESTFIT))
        {
            return preferredSize.height;
        }

        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
        if (diagram == null)
        {
            return 0;
        }
        return (int)diagram.getHeight();
    }

    /**
     * @return width of this icon
     */
    public int getIconWidthIgnoreAutosize()
    {
        if (preferredSize != null &&
                (autosize == AUTOSIZE_HORIZ || autosize == AUTOSIZE_STRETCH
                || autosize == AUTOSIZE_BESTFIT))
        {
            return preferredSize.width;
        }

        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
        if (diagram == null)
        {
            return 0;
        }
        return (int)diagram.getWidth();
    }

    private boolean isAutoSizeBestFitUseFixedHeight(final int iconWidthIgnoreAutosize, final int iconHeightIgnoreAutosize,
            final SVGDiagram diagram)
    {
        return iconHeightIgnoreAutosize/diagram.getHeight() < iconWidthIgnoreAutosize/diagram.getWidth();
    }

    @Override
    public int getIconWidth()
    {
        final int iconWidthIgnoreAutosize = getIconWidthIgnoreAutosize();
        final int iconHeightIgnoreAutosize = getIconHeightIgnoreAutosize();
        final SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
        if (preferredSize != null && (autosize == AUTOSIZE_VERT ||
                                     (autosize == AUTOSIZE_BESTFIT && isAutoSizeBestFitUseFixedHeight(iconWidthIgnoreAutosize, iconHeightIgnoreAutosize, diagram))))
        {
            final double aspectRatio = diagram.getHeight()/diagram.getWidth();
            return (int)(iconHeightIgnoreAutosize / aspectRatio);
        }
        else
        {
            return iconWidthIgnoreAutosize;
        }
    }

    @Override
    public int getIconHeight()
    {
        final int iconWidthIgnoreAutosize = getIconWidthIgnoreAutosize();
        final int iconHeightIgnoreAutosize = getIconHeightIgnoreAutosize();
        final SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
        if (preferredSize != null && (autosize == AUTOSIZE_HORIZ ||
                                      (autosize == AUTOSIZE_BESTFIT && !isAutoSizeBestFitUseFixedHeight(iconWidthIgnoreAutosize, iconHeightIgnoreAutosize, diagram))))
        {
            final double aspectRatio = diagram.getHeight()/diagram.getWidth();
            return (int)(iconWidthIgnoreAutosize * aspectRatio);
        }
        else
        {
            return iconHeightIgnoreAutosize;
        }
    }

    /**
     * Draws the icon to the specified component.
     * @param comp - Component to draw icon to.  This is ignored by SVGIcon, and can be set to null; only gg is used for drawing the icon
     * @param gg - Graphics context to render SVG content to
     * @param x - X coordinate to draw icon
     * @param y - Y coordinate to draw icon
     */
    @Override
    public void paintIcon(Component comp, Graphics gg, int x, int y)
    {
        //Copy graphics object so that
        Graphics2D g = (Graphics2D)gg.create();
        paintIcon(comp, g, x, y);
        g.dispose();
    }

    private void paintIcon(Component comp, Graphics2D g, int x, int y)
    {
        Object oldAliasHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);

        Object oldInterpolationHint = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
        switch (interpolation)
        {
            case INTERP_NEAREST_NEIGHBOR:
                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
                break;
            case INTERP_BILINEAR:
                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                break;
            case INTERP_BICUBIC:
                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
                break;
        }


        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
        if (diagram == null)
        {
            return;
        }

        g.translate(x, y);
        diagram.setIgnoringClipHeuristic(!clipToViewbox);
        if (clipToViewbox)
        {
            g.setClip(new Rectangle2D.Float(0, 0, diagram.getWidth(), diagram.getHeight()));
        }


        if (autosize == AUTOSIZE_NONE)
        {
            try
            {
                diagram.render(g);
                g.translate(-x, -y);
                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
            }
            catch (Exception e)
            {
                throw new RuntimeException(e);
            }
            return;
        }

        final int width = getIconWidthIgnoreAutosize();
        final int height = getIconHeightIgnoreAutosize();
//        int width = getWidth();
//        int height = getHeight();

        if (width == 0 || height == 0)
        {
            return;
        }

//        if (width == 0 || height == 0)
//        {
//           //Chances are we're rendering offscreen
//            Dimension dim = getSize();
//            width = dim.width;
//            height = dim.height;
//            return;
//        }

//        g.setClip(0, 0, width, height);


//        final Rectangle2D.Double rect = new Rectangle2D.Double();
//        diagram.getViewRect(rect);
//
//        scaleXform.setToScale(width / rect.width, height / rect.height);
        double diaWidth = diagram.getWidth();
        double diaHeight = diagram.getHeight();

        double scaleW = 1;
        double scaleH = 1;
        if (autosize == AUTOSIZE_BESTFIT)
        {
            scaleW = scaleH = (height / diaHeight < width / diaWidth)
                    ? height / diaHeight : width / diaWidth;
        }
        else if (autosize == AUTOSIZE_HORIZ)
        {
            scaleW = scaleH = width / diaWidth;
        }
        else if (autosize == AUTOSIZE_VERT)
        {
            scaleW = scaleH = height / diaHeight;
        }
        else if (autosize == AUTOSIZE_STRETCH)
        {
            scaleW = width / diaWidth;
            scaleH = height / diaHeight;
        }
        scaleXform.setToScale(scaleW, scaleH);

        AffineTransform oldXform = g.getTransform();
        g.transform(scaleXform);

        try
        {
            diagram.render(g);
        }
        catch (SVGException e)
        {
            throw new RuntimeException(e);
        }

        g.setTransform(oldXform);


        g.translate(-x, -y);

        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
        if (oldInterpolationHint != null)
        {
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, oldInterpolationHint);
        }
    }

    /**
     * @return the universe this icon draws it's SVGDiagrams from
     */
    public SVGUniverse getSvgUniverse()
    {
        return svgUniverse;
    }

    public void setSvgUniverse(SVGUniverse svgUniverse)
    {
        SVGUniverse old = this.svgUniverse;
        this.svgUniverse = svgUniverse;
        changes.firePropertyChange("svgUniverse", old, svgUniverse);
    }

    /**
     * @return the uni of the document being displayed by this icon
     */
    public URI getSvgURI()
    {
        return svgURI;
    }

    /**
     * Loads an SVG document from a URI.
     * @param svgURI - URI to load document from
     */
    public void setSvgURI(URI svgURI)
    {
        URI old = this.svgURI;
        this.svgURI = svgURI;

        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
        if (diagram != null)
        {
            Dimension size = getPreferredSize();
            if (size == null)
            {
                size = new Dimension((int)diagram.getRoot().getDeviceWidth(), (int)diagram.getRoot().getDeviceHeight());
            }
            diagram.setDeviceViewport(new Rectangle(0, 0, size.width, size.height));
        }

        changes.firePropertyChange("svgURI", old, svgURI);
    }

    /**
     * Loads an SVG document from the classpath.  This function is equivilant to
     * setSvgURI(new URI(getClass().getResource(resourcePath).toString());
     * @param resourcePath - resource to load
     */
    public void setSvgResourcePath(String resourcePath)
    {
        URI old = this.svgURI;

        try
        {
            svgURI = new URI(getClass().getResource(resourcePath).toString());
            changes.firePropertyChange("svgURI", old, svgURI);

            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
            if (diagram != null)
            {
                diagram.setDeviceViewport(new Rectangle(0, 0, preferredSize.width, preferredSize.height));
            }

        }
        catch (Exception e)
        {
            svgURI = old;
        }
    }

    public Dimension getPreferredSize()
    {
        if (preferredSize == null)
        {
            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
            if (diagram != null)
            {
                //preferredSize = new Dimension((int)diagram.getWidth(), (int)diagram.getHeight());
                setPreferredSize(new Dimension((int)diagram.getWidth(), (int)diagram.getHeight()));
            }
        }

        return new Dimension(preferredSize);
    }

    public void setPreferredSize(Dimension preferredSize)
    {
        Dimension old = this.preferredSize;
        this.preferredSize = preferredSize;

        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
        if (diagram != null)
        {
            diagram.setDeviceViewport(new Rectangle(0, 0, preferredSize.width, preferredSize.height));
        }

        changes.firePropertyChange("preferredSize", old, preferredSize);
    }

    /**
     * @return true if antiAliasing is turned on.
     */
    public boolean getAntiAlias()
    {
        return antiAlias;
    }

    /**
     * @param antiAlias true to use antiAliasing.
     */
    public void setAntiAlias(boolean antiAlias)
    {
        boolean old = this.antiAlias;
        this.antiAlias = antiAlias;
        changes.firePropertyChange("antiAlias", old, antiAlias);
    }

    /**
     * @return interpolation used in rescaling images
     */
    public int getInterpolation()
    {
        return interpolation;
    }

    /**
     * @param interpolation Interpolation value used in rescaling images.
     * Should be one of
     *    INTERP_NEAREST_NEIGHBOR - Fastest, one pixel resampling, poor quality
     *    INTERP_BILINEAR - four pixel resampling
     *    INTERP_BICUBIC - Slowest, nine pixel resampling, best quality
     */
    public void setInterpolation(int interpolation)
    {
        int old = this.interpolation;
        this.interpolation = interpolation;
        changes.firePropertyChange("interpolation", old, interpolation);
    }

    /**
     * clipToViewbox will set a clip box equivilant to the SVG's viewbox before
     * rendering.
     */
    public boolean isClipToViewbox()
    {
        return clipToViewbox;
    }

    public void setClipToViewbox(boolean clipToViewbox)
    {
        this.clipToViewbox = clipToViewbox;
    }

    /**
     * @return the autosize
     */
    public int getAutosize()
    {
        return autosize;
    }

    /**
     * @param autosize the autosize to set
     */
    public void setAutosize(int autosize)
    {
        int oldAutosize = this.autosize;
        this.autosize = autosize;
        changes.firePropertyChange(PROP_AUTOSIZE, oldAutosize, autosize);
    }

}
