/*
 * @(#)NavigationHelper.java 11/5/2011
 *
 * Copyright 2002 - 2011 JIDE Software Inc. All rights reserved.
 */

package com.jidesoft.swing;

import com.jidesoft.utils.ColorUtils;

import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;

/**
 * <code>NavigationComponentHelper</code> is a helper class to implement on JTree, JList and JTable (or JIDE table
 * subclasses) so they can be used for the navigation purpose. In order to make the component suitable for the
 * navigation, we want the selection effect to be easily noticeable and covers the row (instead of just one cell or one
 * node in the case of JTable and JTree respectively). We also want to have rollover effect the mouse is over a row.
 * Further more, the selection should have different color when the component is focused so that when multiple
 * navigation components are used, we can tell which one is active. Some L&Fs already do it by default but the most L&Fs
 * don't do it. This class provides some common code to make the implementation easy.
 */
abstract public class NavigationComponentHelper {
    private int _rolloverRow = -1;
    private Point _mousePosition = null;

    /**
     * Gets the bounds of the row.
     *
     * @param row the bounds of the specific row.
     * @return the bounds of the row. Or null if there is no row at all or the specified row doesn't exist.
     */
    protected abstract Rectangle getRowBounds(int row);

    protected abstract int rowAtPoint(Point p);

    protected abstract int[] getSelectedRows();

    @SuppressWarnings({"UnusedParameters"})
    public void mouseMoved(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public void mousePressed(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
    }

    public void mouseClicked(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void focusGained(FocusEvent e) {
    }

    public void focusLost(FocusEvent e) {
    }

    /**
     * Paints the rollover row and selection rows.
     *
     * @param g the Graphics
     * @param c the component
     */
    public void paint(Graphics g, JComponent c) {
        if (_rolloverRow != -1) {
            paintRolloverRow(g, c, _rolloverRow);
        }
        int[] rows = getSelectedRows();
        if (rows != null) {
            for (int row : rows) {
                paintSelectedRow(g, c, row);
            }
        }
    }

    /**
     * Paints the selected row. This method is called after the tree is painted. It will paint over the content of the
     * tree. In order to not cover the content, the painting code must be semi-transparent. By default, we paint it
     * using the selection color which can be retrieved from UIDefault "Tree.selectionBackground"  but with an alpha
     * between 70 to 100 to create a gradient effect.
     *
     * @param g   the Graphics
     * @param c   the component
     * @param row the row index
     */
    protected void paintSelectedRow(Graphics g, JComponent c, int row) {
        Color selectedColor = getSelectionColor(c);
        Rectangle bounds = getRowBounds(row);
        if (bounds != null) {
            bounds.width -= 1;
            bounds.height -= 1;
            paintRow(g, row, bounds, selectedColor, 30, 70, 50, 128);
        }
    }

    /**
     * Gets the color to paint the selected rows.
     *
     * @param c the component
     * @return the selection color.
     * @since 3.4.6
     */
    protected Color getSelectionColor(JComponent c) {
        Color selectedColor = UIManager.getColor("NavigationComponent.selectionBackground");
        if (selectedColor == null) {
            selectedColor = UIManager.getColor("Tree.selectionBackground");
        }
        if (!c.hasFocus()) {
            selectedColor = ColorUtils.toGrayscale(selectedColor).brighter();
            if (Color.WHITE.equals(selectedColor)) {
                selectedColor = new Color(202, 202, 202);
            }
        }
        return selectedColor;
    }

    /**
     * Paints the rollover row. This method is called after the tree is painted. It will paint over the content of the
     * tree. In order to not cover the content, the painting code must be semi-transparent. By default, we paint it
     * using the selection color which can be retrieved from UIDefault "Tree.selectionBackground"  but with an alpha
     * between 10 to 40 to create a gradient effect.
     *
     * @param g   the Graphics
     * @param c   the component
     * @param row the row index
     */
    @SuppressWarnings({"UnusedParameters"})
    protected void paintRolloverRow(Graphics g, JComponent c, int row) {
        Color selectedColor = UIManager.getColor("Tree.selectionBackground");
        Rectangle bounds = getRowBounds(row);
        if (bounds != null) {
            bounds.width -= 1;
            bounds.height -= 1;
            paintRow(g, row, bounds, selectedColor, 10, 40, 20, 100);
        }
    }

    @SuppressWarnings({"UnusedParameters"})
    private void paintRow(Graphics g, int row, Rectangle bounds, Color color, int a1, int a2, int a3, int a4) {
        Object o = JideSwingUtilities.setupShapeAntialiasing(g);
        ((Graphics2D) g).setPaint(new LinearGradientPaint(bounds.x, bounds.y, bounds.x, bounds.y + bounds.height, new float[]{0.5f, 0.95f, 1f}, new Color[]{
                new Color(color.getRed(), color.getGreen(), color.getBlue(), a1),
                new Color(color.getRed(), color.getGreen(), color.getBlue(), a2),
                new Color(color.getRed(), color.getGreen(), color.getBlue(), a3)
        }, MultipleGradientPaint.CycleMethod.NO_CYCLE));
        int cornerSize = 5;
        g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, cornerSize, cornerSize);
        g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), a4));
        g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, cornerSize, cornerSize);
        JideSwingUtilities.restoreShapeAntialiasing(g, o);
    }

    public void setup(final JComponent c) {
        MouseInputAdapter inputAdapter = new MouseInputAdapter() {
            @Override
            public void mouseExited(MouseEvent e) {
                if (c != null) {
                    _mousePosition = null;
                    int old = _rolloverRow;
                    _rolloverRow = -1;
                    if (old != -1) {
                        Rectangle bounds = getRowBounds(old);
                        if (bounds != null) {
                            c.repaint(bounds);
                        }
                    }
                }
                NavigationComponentHelper.this.mouseExited(e);
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                NavigationComponentHelper.this.mouseEntered(e);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                NavigationComponentHelper.this.mousePressed(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                NavigationComponentHelper.this.mouseReleased(e);
            }

            @Override
            public void mouseClicked(MouseEvent e) {
                NavigationComponentHelper.this.mouseClicked(e);
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                if (c != null) {
                    int row = rowAtPoint(e.getPoint());
                    if (row != -1) {
                        Rectangle bounds = getRowBounds(row);
                        if (c instanceof JTree) {
                            int maxIconSize = bounds != null ? bounds.height : ((JTree) c).getRowHeight();
                            if (_mousePosition != null) {
                                c.repaint(new Rectangle(_mousePosition.x - maxIconSize, _mousePosition.y - maxIconSize, 2 * maxIconSize, 2 * maxIconSize));
                            }
                            _mousePosition = e.getPoint();
                            if (_mousePosition != null) {
                                c.repaint(new Rectangle(_mousePosition.x - maxIconSize, _mousePosition.y - maxIconSize, 2 * maxIconSize, 2 * maxIconSize));
                            }
                        }
                        if (_rolloverRow != row) {
                            int old = _rolloverRow;
                            _rolloverRow = row;
                            if (old != -1) {
                                Rectangle oldBounds = getRowBounds(old);
                                if (oldBounds != null) {
                                    c.repaint(oldBounds);
                                }
                            }
                            if (bounds != null) {
                                c.repaint(bounds);
                            }
                        }
                    }
                    else {
                        int old = _rolloverRow;
                        _rolloverRow = -1;
                        if (old != -1) {
                            Rectangle bounds = getRowBounds(old);
                            if (bounds != null) {
                                c.repaint(bounds);
                            }
                        }
                    }
                }
                NavigationComponentHelper.this.mouseMoved(e);
            }
        };
        c.addMouseMotionListener(inputAdapter);
        c.addMouseListener(inputAdapter);
        c.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
                repaintSelections(c);
                NavigationComponentHelper.this.focusGained(e);
            }

            @Override
            public void focusLost(FocusEvent e) {
                repaintSelections(c);
                NavigationComponentHelper.this.focusLost(e);
            }
        });
    }

    public void repaintSelections(JComponent c) {
        int[] rows = getSelectedRows();
        if (rows != null) {
            for (int row : rows) {
                Rectangle bounds = getRowBounds(row);
                if (bounds != null) {
                    bounds.x = 0;
                    bounds.width = c.getWidth();
                    c.repaint(bounds);
                }
            }
        }
        if (_rolloverRow != -1) {
            Rectangle bounds = getRowBounds(_rolloverRow);
            if (bounds != null) {
                c.repaint(bounds);
            }
        }
    }

    /**
     * Gets the rollover row that currently has rollover effect.
     *
     * @return the row that has the rollover effect.
     */
    public int getRolloverRow() {
        return _rolloverRow;
    }

    /**
     * Sets the rollover row.
     *
     * @param rolloverRow the row to show the rollover effect.
     */
    public void setRolloverRow(int rolloverRow) {
        _rolloverRow = rolloverRow;
    }

    public Point getMousePosition() {
        return _mousePosition;
    }
}
