/*
 * MIT License
 *
 * Copyright (c) 2020 Jannis Weis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
package com.github.weisj.darklaf.ui.spinner;

import java.awt.*;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicSpinnerUI;

import com.github.weisj.darklaf.components.ArrowButton;
import com.github.weisj.darklaf.delegate.LayoutManagerDelegate;
import com.github.weisj.darklaf.graphics.PaintUtil;
import com.github.weisj.darklaf.util.PropertyKey;

/**
 * @author Konstantin Bulenkov
 * @author Jannis Weis
 */
public class DarkSpinnerUI extends BasicSpinnerUI implements SpinnerConstants {

    protected DarkSpinnerListener spinnerListener;

    protected Color arrowBackground;
    protected Color background;
    protected Icon arrowDownIcon;
    protected Color inactiveBackground;
    protected int arc;
    protected int borderSize;
    protected Icon arrowUpIcon;
    protected Icon plusIcon;
    protected Icon minusIcon;
    protected Icon arrowDownInactiveIcon;
    protected Icon arrowUpInactiveIcon;
    protected Icon plusInactiveIcon;
    protected Icon minusInactiveIcon;
    private JComponent editor;
    private JButton prevButton;
    private Component editorComponent;
    private JButton nextButton;

    public static ComponentUI createUI(final JComponent c) {
        return new DarkSpinnerUI();
    }

    @Override
    protected void installDefaults() {
        super.installDefaults();
        arc = UIManager.getInt("Spinner.arc");
        borderSize = UIManager.getInt("Spinner.borderThickness");
        background = UIManager.getColor("Spinner.activeBackground");
        inactiveBackground = UIManager.getColor("Spinner.inactiveBackground");
        arrowBackground = UIManager.getColor("Spinner.arrowBackground");

        arrowDownIcon = UIManager.getIcon("Spinner.arrowDown.icon");
        arrowUpIcon = UIManager.getIcon("Spinner.arrowUp.icon");
        minusIcon = UIManager.getIcon("Spinner.minus.icon");
        plusIcon = UIManager.getIcon("Spinner.plus.icon");

        arrowDownInactiveIcon = UIManager.getIcon("Spinner.arrowDownInactive.icon");
        arrowUpInactiveIcon = UIManager.getIcon("Spinner.arrowUpInactive.icon");
        minusInactiveIcon = UIManager.getIcon("Spinner.minusInactive.icon");
        plusInactiveIcon = UIManager.getIcon("Spinner.plusInactive.icon");
        LookAndFeel.installProperty(spinner, PropertyKey.OPAQUE, false);
    }

    @Override
    protected void installListeners() {
        super.installListeners();
        spinnerListener = createSpinnerListener();
        spinner.addMouseListener(spinnerListener);
        spinner.addPropertyChangeListener(spinnerListener);
    }

    protected DarkSpinnerListener createSpinnerListener() {
        return new DarkSpinnerListener(spinner, this);
    }

    @Override
    protected void uninstallListeners() {
        super.uninstallListeners();
        if (editor != null && editor.getComponents().length > 0) {
            editor.getComponents()[0].removeFocusListener(spinnerListener);
        }
        spinner.removeMouseListener(spinnerListener);
        spinner.removePropertyChangeListener(spinnerListener);
        spinnerListener = null;
    }

    protected LayoutManager createLayout() {
        return new LayoutManagerDelegate(super.createLayout()) {
            private Component editor = null;

            @Override
            public void addLayoutComponent(final String name, final Component comp) {
                super.addLayoutComponent(name, comp);
                if ("Editor".equals(name)) {
                    editor = comp;
                }
            }

            protected void adjustButton(final JComponent button, final int adj) {
                Rectangle bounds = button.getBounds();
                bounds.x += adj;
                button.setBounds(bounds);
            }

            @Override
            public void layoutContainer(final Container parent) {
                super.layoutContainer(parent);
                if (SpinnerConstants.isTreeOrTableCellEditor(spinner)) {
                    int adj = borderSize / 2;
                    if (!spinner.getComponentOrientation().isLeftToRight()) adj *= -1;
                    adjustButton(prevButton, adj);
                    adjustButton(nextButton, adj);
                }
                if (editor != null && !spinner.getComponentOrientation().isLeftToRight()) {
                    Rectangle bounds = editor.getBounds();
                    bounds.x += borderSize;
                    bounds.width -= borderSize;
                    editor.setBounds(bounds);
                }
            }
        };
    }

    @Override
    protected Component createPreviousButton() {
        prevButton = createArrow(SwingConstants.SOUTH);
        prevButton.setName("Spinner.previousButton");
        prevButton.setBorder(new EmptyBorder(1, 1, 1, 1));
        prevButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        installPreviousButtonListeners(prevButton);
        return prevButton;
    }

    @Override
    protected Component createNextButton() {
        nextButton = createArrow(SwingConstants.NORTH);
        nextButton.setName("Spinner.nextButton");
        nextButton.setBorder(new EmptyBorder(1, 1, 1, 1));
        nextButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        installNextButtonListeners(nextButton);
        return nextButton;
    }

    @Override
    protected JComponent createEditor() {
        editor = super.createEditor();
        if (editor instanceof JSpinner.DefaultEditor) {
            editorComponent = ((JSpinner.DefaultEditor) editor).getTextField();
        } else {
            editorComponent = editor;
        }
        editorComponent.addFocusListener(spinnerListener);
        return editor;
    }

    @Override
    protected void replaceEditor(final JComponent oldEditor, final JComponent newEditor) {
        super.replaceEditor(oldEditor, newEditor);
        editor = newEditor;
        if (oldEditor != null && oldEditor.getComponents().length > 0) {
            oldEditor.getComponents()[0].removeFocusListener(spinnerListener);
        }
        if (newEditor != null && newEditor.getComponents().length > 0) {
            Component comp = newEditor.getComponents()[0];
            comp.addFocusListener(spinnerListener);
        }
    }

    private JButton createArrow(final int direction) {
        int buttonPad = UIManager.getInt("Spinner.buttonPad");
        Insets insets = new Insets(0, buttonPad, 0, buttonPad);
        JButton button = ArrowButton.createUpDownArrow(spinner,
                                                       getArrowIcon(direction),
                                                       getArrowInactiveIcon(direction), direction,
                                                       false, true, insets);
        Border buttonBorder = UIManager.getBorder("Spinner.arrowButtonBorder");
        if (buttonBorder instanceof UIResource) {
            // Wrap the border to avoid having the UIResource be replaced by
            // the ButtonUI. This is the opposite of using BorderUIResource.
            button.setBorder(new CompoundBorder(buttonBorder, null));
        } else {
            button.setBorder(buttonBorder);
        }
        button.setInheritsPopupMenu(true);
        return button;
    }

    protected SpinnerIcon getArrowIcon(final int direction) {
        if (direction == SwingConstants.SOUTH) {
            return new SpinnerIcon(spinner, arrowDownIcon, minusIcon);
        } else {
            return new SpinnerIcon(spinner, arrowUpIcon, plusIcon);
        }
    }

    protected SpinnerIcon getArrowInactiveIcon(final int direction) {
        if (direction == SwingConstants.SOUTH) {
            return new SpinnerIcon(spinner, arrowDownInactiveIcon, minusInactiveIcon);
        } else {
            return new SpinnerIcon(spinner, arrowUpInactiveIcon, plusInactiveIcon);
        }
    }

    @Override
    public void paint(final Graphics g, final JComponent c) {
        final Container parent = c.getParent();
        if (parent != null && parent.isOpaque() && !c.isEnabled()) {
            g.setColor(parent.getBackground());
            g.fillRect(0, 0, c.getWidth(), c.getHeight());
            return;
        }

        int size = borderSize;
        int width = c.getWidth();
        int height = c.getHeight();
        JComponent editor = spinner.getEditor();

        if (editorComponent != null) {
            editorComponent.setBackground(getBackground(c));
            g.setColor(editorComponent.getBackground());
        } else {
            g.setColor(getBackground(c));
        }
        if (!SpinnerConstants.isTreeOrTableCellEditor(c)) {
            PaintUtil.fillRoundRect((Graphics2D) g, size, size, width - 2 * size, height - 2 * size, arc);
        } else {
            g.fillRect(0, 0, width, height);
        }
        if (editor != null) {
            paintSpinBackground((Graphics2D) g, width, height, size, arc);
        }
    }

    protected void paintSpinBackground(final Graphics2D g, final int width, final int height,
                                       final int bSize, final int arc) {
        Rectangle arrowBounds = prevButton.getBounds();
        boolean leftToRight = spinner.getComponentOrientation().isLeftToRight();

        Shape oldClip = g.getClip();
        g.setColor(getArrowBackground(spinner));

        if (leftToRight) {
            g.clipRect(arrowBounds.x, 0, width - arrowBounds.x, height);
        } else {
            g.clipRect(0, 0, arrowBounds.x + arrowBounds.width, height);
        }
        if (SpinnerConstants.isTreeOrTableCellEditor(spinner)) {
            g.fillRect(0, 0, width, height);
        } else {
            PaintUtil.fillRoundRect(g, bSize, bSize, width - 2 * bSize, height - 2 * bSize, arc);
        }
        g.setClip(oldClip);
    }

    protected Color getBackground(final JComponent c) {
        return c == null || !c.isEnabled() ? inactiveBackground : background;
    }

    protected Color getArrowBackground(final JComponent c) {
        return c == null || !c.isEnabled() ? inactiveBackground : arrowBackground;
    }

    public Component getEditorComponent() {
        return editorComponent;
    }

    public JComponent getEditor() {
        return editor;
    }
}