/*
 * Copyright 2003-2016 JetBrains s.r.o.
 *
 * Licensed 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 jetbrains.mps.nodeEditor.cells.contextAssistant;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopupStep;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.UserActivityProviderComponent;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.MouseEventAdapter;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.DefaultButtonModel;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;

/**
 * A combo-box button that shows menu based on a {@link ListPopupStep}. Copied from {@link com.intellij.openapi.actionSystem.ex.ComboBoxAction} and simplified.
 */
abstract class AbstractStepComboBoxButton extends JButton implements UserActivityProviderComponent {
  private boolean myForcePressed = false;
  private JBPopup myPopup;

  AbstractStepComboBoxButton(String text) {
    this();
    setText(text + " \u25be"); // BLACK DOWN-POINTING SMALL TRIANGLE (U+25BE)
  }

  private AbstractStepComboBoxButton() {
    setModel(new MyButtonModel());
    setHorizontalAlignment(LEFT);
    putClientProperty("styleCombo", Boolean.TRUE);

    Insets margins = getMargin();
    setMargin(JBUI.insets(margins.top, 2, margins.bottom, 2));

    setFont(UIUtil.getLabelFont());
    setOpaque(true);

    InputMap inputMap = getInputMap();
    inputMap.put(KeyStroke.getKeyStroke("DOWN"), "pressed");
    inputMap.put(KeyStroke.getKeyStroke("released DOWN"), "released");

    addActionListener(
        e -> {
          if (!myForcePressed) {
            IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(this::showPopup);
          }
        }
    );

    addMouseListener(new MouseAdapter() {
      @Override
      public void mousePressed(final MouseEvent e) {
        if (SwingUtilities.isLeftMouseButton(e)) {
          e.consume();
          doClick();
        }
      }

      @Override
      public void mouseReleased(MouseEvent e) {
        dispatchEventToPopup(e);
      }
    });
    addMouseMotionListener(new MouseMotionListener() {
      @Override
      public void mouseDragged(MouseEvent e) {
        mouseMoved(MouseEventAdapter.convert(e, e.getComponent(),
            MouseEvent.MOUSE_MOVED,
            e.getWhen(),
            e.getModifiers() | e.getModifiersEx(),
            e.getX(),
            e.getY()));
      }

      @Override
      public void mouseMoved(MouseEvent e) {
        dispatchEventToPopup(e);
      }
    });
  }

  // Event forwarding. We need it if user does press-and-drag gesture for opening popup and choosing item there.
  // It works in JComboBox, here we provide the same behavior
  private void dispatchEventToPopup(MouseEvent e) {
    if (myPopup != null && myPopup.isVisible()) {
      JComponent content = myPopup.getContent();
      Rectangle rectangle = content.getBounds();
      Point location = rectangle.getLocation();
      SwingUtilities.convertPointToScreen(location, content);
      Point eventPoint = e.getLocationOnScreen();
      rectangle.setLocation(location);
      if (rectangle.contains(eventPoint)) {
        MouseEvent event = SwingUtilities.convertMouseEvent(e.getComponent(), e, myPopup.getContent());
        Component component = SwingUtilities.getDeepestComponentAt(content, event.getX(), event.getY());
        if (component != null)
          component.dispatchEvent(event);
      }
    }
  }

  @NotNull
  private Disposable setForcePressed() {
    myForcePressed = true;
    repaint();

    return () -> {
      // give the button a chance to handle action listener
      ApplicationManager.getApplication().invokeLater(() -> {
        myForcePressed = false;
        myPopup = null;
      }, ModalityState.any());
      repaint();
      fireStateChanged();
    };
  }

  @Nullable
  @Override
  public String getToolTipText() {
    return myForcePressed ? null : super.getToolTipText();
  }

  private void showPopup() {
    myPopup = JBPopupFactory.getInstance().createListPopup(getStep());
    Disposer.register(myPopup, setForcePressed());
    myPopup.showUnderneathOf(this);
  }

  protected abstract ListPopupStep<?> getStep();

  @Override
  public Dimension getMinimumSize() {
    return new Dimension(super.getMinimumSize().width, getPreferredSize().height);
  }

  private class MyButtonModel extends DefaultButtonModel {
    @Override
    public boolean isPressed() {
      return myForcePressed || super.isPressed();
    }

    @Override
    public boolean isArmed() {
      return myForcePressed || super.isArmed();
    }
  }
}
