package com.jidesoft.plaf.basic;

import com.jidesoft.plaf.UIDefaultsLookup;
import com.jidesoft.swing.JideSwingUtilities;

import javax.swing.*;
import javax.swing.border.AbstractBorder;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.metal.MetalComboBoxUI;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class BasicJideComboBoxUI extends MetalComboBoxUI {

    @SuppressWarnings({"UnusedDeclaration"})
    public static BasicJideComboBoxUI createUI(JComponent c) {
        return new BasicJideComboBoxUI();
    }

    @Override
    protected void installDefaults() {
        super.installDefaults();
        JideSwingUtilities.installBorder(comboBox, createComboBoxBorder());
    }

    protected BasicJideComboBoxBorder createComboBoxBorder() {
        return new BasicJideComboBoxBorder();
    }


    @Override
    protected void uninstallDefaults() {
        super.uninstallDefaults();
        LookAndFeel.uninstallBorder(comboBox);
    }

    @Override
    protected void installListeners() {
        super.installListeners();
        if (_rolloverListener == null) {
            _rolloverListener = createRolloverListener();
        }
        comboBox.addMouseListener(_rolloverListener);
    }

    @Override
    protected void uninstallListeners() {
        super.uninstallListeners();
        comboBox.removeMouseListener(_rolloverListener);
        _rolloverListener = null;
    }

    protected RolloverListener createRolloverListener() {
        return new RolloverListener();
    }

    @Override
    protected JButton createArrowButton() {
        JButton button = new BasicJideComboBoxButton(
                comboBox, new BasicJideComboBoxIcon(),
                comboBox.isEditable(),
                currentValuePane, listBox);
        button.setMargin(new Insets(1, 3, 0, 4));
        button.setFocusPainted(comboBox.isEditable());
        button.addMouseListener(_rolloverListener);
        return button;
    }

    @Override
    public void unconfigureArrowButton() {
        super.unconfigureArrowButton();
        arrowButton.removeMouseListener(_rolloverListener);
    }

    @Override
    public void unconfigureEditor() {
        super.unconfigureEditor();
        editor.removeMouseListener(_rolloverListener);
        editor.removeFocusListener(_rolloverListener);
    }

    @Override
    public void configureEditor() {
        if (editor instanceof JComponent) {
            ((JComponent) editor).setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
        }
        editor.addMouseListener(_rolloverListener);
        editor.addFocusListener(_rolloverListener);
    }

    // This is here because of a bug in the compiler.
    // When a protected-inner-class-savvy compiler comes out we
    // should move this into MetalComboBoxLayoutManager.
    @Override
    public void layoutComboBox(Container parent, MetalComboBoxLayoutManager manager) {
        if (arrowButton != null) {
            if (arrowButton instanceof BasicJideComboBoxButton) {
                Icon icon = ((BasicJideComboBoxButton) arrowButton).getComboIcon();
                Insets buttonInsets = arrowButton.getInsets();
                Insets insets = comboBox.getInsets();
                int buttonWidth = icon.getIconWidth() + buttonInsets.left +
                        buttonInsets.right;
                arrowButton.setBounds(
                        comboBox.getComponentOrientation().isLeftToRight()
                                ? (comboBox.getWidth() - insets.right - buttonWidth)
                                : insets.left,
                        insets.top, buttonWidth,
                        comboBox.getHeight() - insets.top - insets.bottom);
            }
            else {
                Insets insets = comboBox.getInsets();
                int width = comboBox.getWidth();
                int height = comboBox.getHeight();
                arrowButton.setBounds(
                        insets.left, insets.top,
                        width - (insets.left + insets.right),
                        height - (insets.top + insets.bottom));
            }
        }

        if (editor != null) {
            Rectangle cvb = rectangleForCurrentValue();
            editor.setBounds(cvb);
        }
    }

    public boolean isRollOver() {
        return _rollOver || (editor != null ? editor.hasFocus() : hasFocus);
    }

    public void setRollOver(boolean rollOver) {
        _rollOver = rollOver;
    }

    protected class BasicJideComboBoxBorder extends AbstractBorder implements UIResource {
        private static final long serialVersionUID = 4633179647696691207L;

        @Override
        public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
            Color old = g.getColor();
            JComboBox box = (JComboBox) c;
            if (box.isPopupVisible() || isRollOver()) {
                g.setColor(UIDefaultsLookup.getColor("JideButton.borderColor"));
            }
            else {
                g.setColor(UIDefaultsLookup.getColor("TextField.background"));
            }
            // draw default border
            g.drawRect(x, y, width - 1, height - 1);

            g.setColor(old);
        }

        @Override
        public Insets getBorderInsets(Component c) {
            return new Insets(1, 1, 1, 1);
        }

        @Override
        public Insets getBorderInsets(Component c, Insets insets) {
            insets.left = insets.right = insets.top = insets.bottom = 1;
            return insets;
        }
    }

    private boolean _rollOver = false;
    protected RolloverListener _rolloverListener;

    protected class RolloverListener extends MouseAdapter implements FocusListener {
        @Override
        public void mouseEntered(MouseEvent e) {
            setRollOver(true);
            comboBox.repaint();
        }

        @Override
        public void mouseExited(MouseEvent e) {
            if (comboBox.isPopupVisible()) {
                // this might be the ugliest hack I've ever written
                // so here is an explanation:
                // when a popup is visible, we need to show the white background until the
                // popup is closed if, however, the mouse re-enters, we do not want to reset the background color
                // In order to get around these problems we'll change the background to white, but a different instance
                // than HOVER_COLOR and perform an identity check when the popup closes.
                // If the background color still is not HOVER_COLOR (which it'd be if the mouse had re-entered meanwhile)
                // then we can override it with DEFAULT_COLOR.
                final PopupMenuListener l = new PopupMenuListener() {
                    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                        // it already is visible
                    }

                    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                        setRollOver(false);
                        ((JPopupMenu) popup).removePopupMenuListener(this);
                    }

                    public void popupMenuCanceled(PopupMenuEvent e) {
                    }
                };
                ((JPopupMenu) popup).addPopupMenuListener(l);
                setRollOver(true);
            }
            else {
                setRollOver(false);
            }
            comboBox.repaint();
        }

        public void focusGained(FocusEvent e) {
            comboBox.repaint();
        }

        public void focusLost(FocusEvent e) {
            comboBox.repaint();
        }
    }

    protected class BasicJideComboBoxIcon implements Icon {
        public void paintIcon(Component c, Graphics g, int x, int y) {
            JideSwingUtilities.paintArrow(g, c.getForeground(), x, y, 5, SwingConstants.HORIZONTAL);
        }

        public int getIconWidth() {
            return 5;
        }

        public int getIconHeight() {
            return 3;
        }
    }
}
