/*

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */
package org.apache.batik.apps.svgbrowser;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;

import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.gvt.Overlay;

import org.w3c.dom.Element;

/**
 * Manages element overlay on the canvas.
 *
 * @version $Id: ElementOverlayManager.java 1804130 2017-08-04 14:41:11Z ssteiner $
 */
public class ElementOverlayManager {

    /**
     * The color of the outline of the element overlay.
     */
    protected Color elementOverlayStrokeColor = Color.black;

    /**
     * The color of the element overlay.
     */
    protected Color elementOverlayColor = Color.white;

    /**
     * The xor mode.
     */
    protected boolean xorMode = true;

    /**
     * The canvas.
     */
    protected JSVGCanvas canvas;

    /**
     * The element overlay.
     */
    protected Overlay elementOverlay = new ElementOverlay();

    /**
     * Elements to paint.
     */
    protected ArrayList elements;

    /**
     * The controller for the element overlay.
     */
    protected ElementOverlayController controller;

    /**
     * Whether the ElementOverlay is enabled.
     */
    protected boolean isOverlayEnabled = true;

    /**
     * Constructor.
     *
     * @param canvas
     *            The parent canvas
     */
    public ElementOverlayManager(JSVGCanvas canvas) {
        this.canvas = canvas;
        elements = new ArrayList();
        canvas.getOverlays().add(elementOverlay);
    }

    /**
     * Adds an element to the element selection.
     *
     * @param elem
     *            The element to add
     */
    public void addElement(Element elem) {
        elements.add(elem);
    }

    /**
     * Removes the element from the element selection and adds its bound to the
     * 'dirty' region.
     *
     * @param elem
     *            The element to remove
     */
    public void removeElement(Element elem) {
        if (elements.remove(elem)) {
//            // Gets the area that should be repainted
//            Rectangle currentElementBounds = getElementBounds(elem);
//            if (dirtyArea == null) {
//                dirtyArea = currentElementBounds;
//            } else if (currentElementBounds != null) {
//                dirtyArea.add(currentElementBounds);
//            }
        }
    }

    /**
     * Removes all elements from the element selection list.
     */
    public void removeElements() {
        elements.clear();
        repaint();
    }

    /**
     * Get the current selection bounds.
     *
     * @return the current selection bounds
     */
    protected Rectangle getAllElementsBounds() {
        Rectangle resultBound = null;
        int n = elements.size();
        for (Object element : elements) {
            Element currentElement = (Element) element;
            Rectangle currentBound = getElementBounds(currentElement);
            if (resultBound == null) {
                resultBound = currentBound;
            } else {
                resultBound.add(currentBound);
            }
        }
        return resultBound;
    }

    /**
     * The bounds of a given element.
     *
     * @param elem
     *            The given element
     * @return Rectangle bounds
     */
    protected Rectangle getElementBounds(Element elem) {
        return getElementBounds(canvas.getUpdateManager().getBridgeContext()
                .getGraphicsNode(elem));
    }

    /**
     * The bounds of a given graphics node.
     *
     * @param node
     *            The given graphics node
     * @return the bounds
     */
    protected Rectangle getElementBounds(GraphicsNode node) {
        if (node == null) {
            return null;
        }
        AffineTransform at = canvas.getRenderingTransform();
        Shape s = at.createTransformedShape(node.getOutline());
        return outset(s.getBounds(), 1);
    }

    /**
     * Increases the given rectangle area for a given amount of units in a
     * rectangle increasement manner.
     *
     * @param r
     *            The given rectangle
     * @param amount
     *            The given amount of units
     * @return <code>r</code>
     */
    protected Rectangle outset(Rectangle r, int amount) {
        r.x -= amount;
        r.y -= amount;
        r.width += 2 * amount;
        r.height += 2 * amount;
        return r;
    }

    /**
     * Repaints the canvas.
     */
    public void repaint() {
        canvas.repaint();
    }

    /**
     * The element overlay.
     */
    public class ElementOverlay implements Overlay {

        /**
         * Paints this overlay.
         */
        public void paint(Graphics g) {
            if (controller.isOverlayEnabled() && isOverlayEnabled()) {
                int n = elements.size();
                for (Object element : elements) {
                    Element currentElement = (Element) element;
                    GraphicsNode nodeToPaint = canvas.getUpdateManager()
                            .getBridgeContext().getGraphicsNode(currentElement);
                    if (nodeToPaint != null) {
                        AffineTransform elementsAt =
                                nodeToPaint.getGlobalTransform();
                        Shape selectionHighlight = nodeToPaint.getOutline();
                        AffineTransform at = canvas.getRenderingTransform();
                        at.concatenate(elementsAt);
                        Shape s = at.createTransformedShape(selectionHighlight);
                        if (s == null) {
                            break;
                        }
                        Graphics2D g2d = (Graphics2D) g;
                        if (xorMode) {
                            g2d.setColor(Color.black);
                            g2d.setXORMode(Color.yellow);
                            g2d.fill(s);
                            g2d.draw(s);
                        } else {
                            g2d.setColor(elementOverlayColor);
                            g2d.setStroke(new BasicStroke(1.8f));
                            g2d.setColor(elementOverlayStrokeColor);
                            g2d.draw(s);
                        }
                    }
                }
            }
        }
    }

    /**
     * Gets the elementOverlayColor.
     *
     * @return the elementOverlayColor
     */
    public Color getElementOverlayColor() {
        return elementOverlayColor;
    }

    /**
     * Sets the color to use for the element overlay.
     *
     * @param selectionOverlayColor The new element overlay color.
     */
    public void setElementOverlayColor(Color selectionOverlayColor) {
        this.elementOverlayColor = selectionOverlayColor;
    }

    /**
     * Gets the elementOverlayStrokeColor.
     *
     * @return the elementOverlayStrokeColor
     */
    public Color getElementOverlayStrokeColor() {
        return elementOverlayStrokeColor;
    }

    /**
     * Sets the color to use for stroking the element overlay.
     *
     * @param selectionOverlayStrokeColor
     *   The new element overlay stroking color.
     */
    public void setElementOverlayStrokeColor
            (Color selectionOverlayStrokeColor) {
        this.elementOverlayStrokeColor = selectionOverlayStrokeColor;
    }

    /**
     * Gets the xorMode.
     *
     * @return the xorMode
     */
    public boolean isXorMode() {
        return xorMode;
    }

    /**
     * Sets the xor mode.
     *
     * @param xorMode
     *            the xorMode to set
     */
    public void setXorMode(boolean xorMode) {
        this.xorMode = xorMode;
    }

    /**
     * Gets the elementOverlay.
     *
     * @return the elementOverlay
     */
    public Overlay getElementOverlay() {
        return elementOverlay;
    }

    /**
     * Removes the elementOverlay.
     */
    public void removeOverlay() {
        canvas.getOverlays().remove(elementOverlay);
    }

    /**
     * Sets the element overlay controller.
     *
     * @param controller
     *            The element overlay controller
     */
    public void setController(ElementOverlayController controller) {
        this.controller = controller;
    }

    /**
     * If the element overlay is enabled.
     *
     * @return isOverlayEnabled
     */
    public boolean isOverlayEnabled() {
        return isOverlayEnabled;
    }

    /**
     * Enables / disables the Element overlay.
     */
    public void setOverlayEnabled(boolean isOverlayEnabled) {
        this.isOverlayEnabled = isOverlayEnabled;
    }
}
