/*
 * Copyright 2000-2015 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 com.intellij.openapi.wm.impl;

import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.UISettingsListener;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.actionSystem.impl.*;
import com.intellij.openapi.application.impl.LaterInvocator;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowType;
import com.intellij.openapi.wm.impl.content.ToolWindowContentUi;
import com.intellij.ui.DoubleClickListener;
import com.intellij.ui.InplaceButton;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.UIBundle;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.ui.tabs.TabsUtil;
import com.intellij.util.BitUtil;
import com.intellij.util.Producer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.plaf.PanelUI;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.util.List;

/**
 * @author pegov
 */
public abstract class ToolWindowHeader extends JPanel implements Disposable, UISettingsListener {
  @NonNls private static final String HIDE_ACTIVE_WINDOW_ACTION_ID = "HideActiveWindow";
  @NonNls private static final String HIDE_ACTIVE_SIDE_WINDOW_ACTION_ID = "HideSideWindows";

  private ToolWindow myToolWindow;
  private WindowInfoImpl myInfo;
  private final ToolWindowHeader.ActionButton myHideButton;
  private BufferedImage myImage;
  private BufferedImage myActiveImage;
  private ToolWindowType myImageType;
  private final JPanel myButtonPanel;
  private final ToolWindowHeader.ActionButton myGearButton;

  private final PresentationFactory myPresentationFactory = new PresentationFactory();
  private final ToolbarUpdater myUpdater;
  private final DefaultActionGroup myActionGroup = new DefaultActionGroup();
  private List<AnAction> myVisibleActions = ContainerUtil.newArrayListWithCapacity(2);

  ToolWindowHeader(final ToolWindowImpl toolWindow, @NotNull WindowInfoImpl info, @NotNull final Producer<ActionGroup> gearProducer) {
    setLayout(new BorderLayout());

    myToolWindow = toolWindow;
    myInfo = info;

    JPanel westPanel = new JPanel() {
      @Override
      public void doLayout() {
        if (getComponentCount() > 0) {
          Rectangle r = getBounds();
          Insets insets = getInsets();

          Component c = getComponent(0);
          Dimension size = c.getPreferredSize();
          if (size.width < r.width - insets.left - insets.right) {
            c.setBounds(insets.left, insets.top, size.width, r.height - insets.top - insets.bottom);
          } else {
            c.setBounds(insets.left, insets.top, r.width - insets.left - insets.right, r.height - insets.top - insets.bottom);
          }
        }
      }
    };

    westPanel.setOpaque(false);
    add(westPanel, BorderLayout.CENTER);

    westPanel.add(toolWindow.getContentUI().getTabComponent());
    toolWindow.getContentUI().initMouseListeners(westPanel, toolWindow.getContentUI());

    JPanel eastPanel = new JPanel();
    eastPanel.setOpaque(false);
    eastPanel.setLayout(new BoxLayout(eastPanel, BoxLayout.X_AXIS));
    eastPanel.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 3));
    add(eastPanel, BorderLayout.EAST);

    myGearButton = new ActionButton(new AnAction() {
      @Override
      public void actionPerformed(@NotNull AnActionEvent e) {
        final InputEvent inputEvent = e.getInputEvent();
        final ActionPopupMenu popupMenu =
          ((ActionManagerImpl)ActionManager.getInstance())
            .createActionPopupMenu(ToolWindowContentUi.POPUP_PLACE, gearProducer.produce(), new MenuItemPresentationFactory(true));

        int x = 0;
        int y = 0;
        if (inputEvent instanceof MouseEvent) {
          x = ((MouseEvent)inputEvent).getX();
          y = ((MouseEvent)inputEvent).getY();
        }

        popupMenu.getComponent().show(inputEvent.getComponent(), x, y);
      }
    }, AllIcons.General.Gear) {
      @Override
      protected Icon getActiveHoveredIcon() {
        return AllIcons.General.GearHover;
      }
    };

    myHideButton = new ActionButton(new HideAction() {
      @Override
      public void actionPerformed(@NotNull AnActionEvent e) {
        hideToolWindow();
      }
    }, new HideSideAction() {
      @Override
      public void actionPerformed(@NotNull AnActionEvent e) {
        sideHidden();
      }
    }, AllIcons.General.HideLeft, null, null) {
      @Override
      protected Icon getActiveIcon() {
        return getHideToolWindowIcon(myToolWindow);
      }

      @Override
      protected Icon getAlternativeIcon() {
        return getHideIcon(myToolWindow);
      }

      @Override
      protected Icon getActiveHoveredIcon() {
        return getHideToolWindowHoveredIcon(myToolWindow);
      }

      @Override
      protected Icon getAlternativeHoveredIcon() {
        return getHideHoveredIcon(myToolWindow);
      }
    };

    addDefaultActions(eastPanel);
    myButtonPanel = eastPanel;

    westPanel.addMouseListener(new PopupHandler() {
      @Override
      public void invokePopup(final Component comp, final int x, final int y) {
        toolWindow.getContentUI().showContextMenu(comp, x, y, toolWindow.getPopupGroup(), toolWindow.getContentManager().getSelectedContent());
      }
    });
    westPanel.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(MouseEvent e) {
        toolWindow.fireActivated();
      }
    });

    addMouseListener(new MouseAdapter() {
      @Override
      public void mouseReleased(final MouseEvent e) {
        if (!e.isPopupTrigger()) {
          if (UIUtil.isCloseClick(e, MouseEvent.MOUSE_RELEASED)) {
            if (e.isAltDown()) {
              toolWindow.fireHidden();
            }
            else {
              toolWindow.fireHiddenSide();
            }
          }
          else {
            toolWindow.fireActivated();
          }
        }
      }
    });

    setOpaque(true);
    setBorder(BorderFactory.createEmptyBorder(TabsUtil.TABS_BORDER, 1, TabsUtil.TABS_BORDER, 1));

    myUpdater = new ToolbarUpdater(this) {
      @Override
      protected void updateActionsImpl(boolean transparentOnly, boolean forced) {
        ToolWindowHeader.this.updateActionsImpl(transparentOnly, forced);
      }

      @Override
      protected void updateActionTooltips() {
        for (ActionButton actionButton : UIUtil.uiTraverser(myButtonPanel).preOrderDfsTraversal().filter(ActionButton.class)) {
          actionButton.updateTooltip();
        }
      }
    };
    new DoubleClickListener(){
      @Override
      protected boolean onDoubleClick(MouseEvent event) {
        ToolWindowManagerImpl mgr = toolWindow.getToolWindowManager();
        mgr.setMaximized(myToolWindow, !mgr.isMaximized(myToolWindow));
        return true;
      }
    }.installOn(westPanel);
    westPanel.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseReleased(final MouseEvent e) {
        Runnable runnable =
          () -> dispatchEvent(SwingUtilities.convertMouseEvent(e.getComponent(), e, ToolWindowHeader.this));
        //noinspection SSBasedInspection
        SwingUtilities.invokeLater(runnable);
      }
    });
  }

  @Override
  public void uiSettingsChanged(UISettings uiSettings) {
    clearCaches();
  }

  private void addDefaultActions(JPanel eastPanel) {
    eastPanel.add(myGearButton);
    eastPanel.add(Box.createHorizontalStrut(6));
    eastPanel.add(myHideButton);
    eastPanel.add(Box.createHorizontalStrut(1));
  }

  @Override
  public void dispose() {
    removeAll();
    myToolWindow = null;
    myInfo = null;
  }

  void setAdditionalTitleActions(AnAction[] actions) {
    myActionGroup.removeAll();
    myActionGroup.addAll(actions);
    myUpdater.updateActions(false, true);
  }

  private void updateActionsImpl(boolean transparentOnly, boolean forced) {
    List<AnAction> newVisibleActions = ContainerUtil.newArrayListWithCapacity(myVisibleActions.size());
    DataContext dataContext = DataManager.getInstance().getDataContext(this);

    Utils.expandActionGroup(LaterInvocator.isInModalContext(), myActionGroup, newVisibleActions, myPresentationFactory, dataContext,
                            ActionPlaces.TOOLWINDOW_TITLE, myUpdater.getActionManager(), transparentOnly);

    if (forced || !newVisibleActions.equals(myVisibleActions)) {
      myVisibleActions = newVisibleActions;

      myButtonPanel.removeAll();
      boolean actionAdded = false;
      for (final AnAction action : newVisibleActions) {
        if (action == null) continue;
        final Presentation presentation = myPresentationFactory.getPresentation(action);
        myButtonPanel.add(new ActionButton(action, presentation.getIcon()) {
          @Override
          protected Icon getActiveHoveredIcon() {
            Icon icon = presentation.getHoveredIcon();
            return icon != null ? icon : super.getActiveHoveredIcon();
          }
        });
        myButtonPanel.add(Box.createHorizontalStrut(9));
        actionAdded = true;
      }
      if (actionAdded) {
        myButtonPanel.add(new JLabel(AllIcons.General.Divider));
        myButtonPanel.add(Box.createHorizontalStrut(6));
      }
      addDefaultActions(myButtonPanel);

      revalidate();
      repaint();
    }
  }

  private static Icon getHideToolWindowIcon(ToolWindow toolWindow) {
    ToolWindowAnchor anchor = toolWindow.getAnchor();
    if (anchor == ToolWindowAnchor.BOTTOM) {
      return AllIcons.General.HideDownPart;
    }
    if (anchor == ToolWindowAnchor.RIGHT) {
      return AllIcons.General.HideRightPart;
    }

    return AllIcons.General.HideLeftPart;
  }

  private static Icon getHideIcon(ToolWindow toolWindow) {
    ToolWindowAnchor anchor = toolWindow.getAnchor();
    if (anchor == ToolWindowAnchor.BOTTOM) {
      return AllIcons.General.HideDown;
    }
    if (anchor == ToolWindowAnchor.RIGHT) {
      return AllIcons.General.HideRight;
    }

    return AllIcons.General.HideLeft;
  }

  private static Icon getHideToolWindowHoveredIcon(ToolWindow toolWindow) {
    ToolWindowAnchor anchor = toolWindow.getAnchor();
    if (anchor == ToolWindowAnchor.BOTTOM) {
      return AllIcons.General.HideDownPartHover;
    }
    if (anchor == ToolWindowAnchor.RIGHT) {
      return AllIcons.General.HideRightPartHover;
    }

    return AllIcons.General.HideLeftPartHover;
  }

  private static Icon getHideHoveredIcon(ToolWindow toolWindow) {
    ToolWindowAnchor anchor = toolWindow.getAnchor();
    if (anchor == ToolWindowAnchor.BOTTOM) {
      return AllIcons.General.HideDownHover;
    }
    if (anchor == ToolWindowAnchor.RIGHT) {
      return AllIcons.General.HideRightHover;
    }

    return AllIcons.General.HideLeftHover;
  }

  @Override
  protected void paintComponent(Graphics g) {
    Rectangle r = getBounds();
    Graphics2D g2d = (Graphics2D)g;
    Shape clip = g2d.getClip();

    ToolWindowType type = myToolWindow.getType();

    Image image;
    if (isActive()) {
      if (myActiveImage == null || /*myActiveImage.getHeight() != r.height ||*/ type != myImageType) {
        myActiveImage = drawToBuffer(g2d, true, r.height, myToolWindow.getType() == ToolWindowType.FLOATING);
      }

      image = myActiveImage;
    } else {
      if (myImage == null || /*myImage.getHeight() != r.height ||*/ type != myImageType) {
        myImage = drawToBuffer(g2d, false, r.height, myToolWindow.getType() == ToolWindowType.FLOATING);
      }

      image = myImage;
    }

    myImageType = myToolWindow.getType();

    Rectangle clipBounds = clip.getBounds();
    for (int x = clipBounds.x; x < clipBounds.x + clipBounds.width; x+=150) {
      UIUtil.drawImage(g, image, x, 0, null);
    }
  }

  private static BufferedImage drawToBuffer(Graphics2D g2d, boolean active, int height, boolean floating) {
    final int width = 150;

    BufferedImage image = UIUtil.createImage(g2d, width, height, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = image.createGraphics();
    UIUtil.drawHeader(g, 0, width, height, active, true, !floating, true);
    g.dispose();

    return image;
  }

  @Override
  public void setUI(PanelUI ui) {
    clearCaches();

    super.setUI(ui);
  }

  public void clearCaches() {
    myImage = null;
    myActiveImage = null;
  }

  @Override
  protected void paintChildren(Graphics g) {
    Graphics2D graphics = (Graphics2D) g.create();

    UISettings.setupAntialiasing(graphics);
    super.paintChildren(graphics);

    Rectangle r = getBounds();
    if (!isActive() && !UIUtil.isUnderDarcula()) {
      graphics.setColor(new Color(255, 255, 255, 30));
      graphics.fill(r);
    }

    graphics.dispose();
  }

  protected abstract boolean isActive();

  protected abstract void hideToolWindow();

  protected abstract void sideHidden();

  protected abstract void toolWindowTypeChanged(@NotNull ToolWindowType type);

  @Override
  public Dimension getPreferredSize() {
    Dimension size = super.getPreferredSize();
    return new Dimension(size.width, TabsUtil.getTabsHeight());
  }

  @Override
  public Dimension getMinimumSize() {
    Dimension size = super.getMinimumSize();
    return new Dimension(size.width, TabsUtil.getTabsHeight());
  }

  private class ActionButton extends Wrapper implements ActionListener, AltStateManager.AltListener {
    private final InplaceButton myButton;
    private final AnAction myAction;
    private final AnAction myAlternativeAction;
    private final Icon myActiveIcon;
    private final Icon myInactiveIcon;
    private final Icon myAlternativeIcon;

    private AnAction myCurrentAction;

    public ActionButton(AnAction action, AnAction alternativeAction, @NotNull Icon activeIcon, Icon inactiveIcon,
                        Icon alternativeIcon) {
      myAction = action;
      myAlternativeAction = alternativeAction;

      myActiveIcon = activeIcon;
      myInactiveIcon = inactiveIcon;
      myAlternativeIcon = alternativeIcon;

      myCurrentAction = myAction;

      myButton = new InplaceButton(getToolTipTextByAction(action),
                                   EmptyIcon.ICON_16, this) {
        @Override
        public boolean isActive() {
          return ActionButton.this.isActive();
        }
      };

      myButton.setHoveringEnabled(!SystemInfo.isMac);
      setContent(myButton);
      setOpaque(false);

      setIcon(getActiveIcon(), getInactiveIcon() == null ? getActiveIcon() : getInactiveIcon(), getActiveHoveredIcon());

      PropertyChangeListener listener = evt -> {
        if (myAlternativeAction == null) return;
        if ("ancestor".equals(evt.getPropertyName())) {
          if (evt.getNewValue() == null) {
            AltStateManager.getInstance().removeListener(this);
            switchAlternativeAction(false);
          }
          else {
            AltStateManager.getInstance().addListener(this);
          }
        }
      };

      addPropertyChangeListener(listener);
    }

    void updateTooltip() {
      myButton.setToolTipText(getToolTipTextByAction(myCurrentAction));
    }

    protected Icon getActiveIcon() {
      return myActiveIcon;
    }

    protected Icon getActiveHoveredIcon() {
      return myActiveIcon;
    }

    protected Icon getInactiveIcon() {
      return myInactiveIcon;
    }

    protected Icon getAlternativeIcon() {
      return myAlternativeIcon;
    }

    protected Icon getAlternativeHoveredIcon() {
      return myAlternativeIcon;
    }

    private void switchAlternativeAction(boolean b) {
      if (b && myCurrentAction == myAlternativeAction) return;
      if (!b && myCurrentAction != myAlternativeAction) return;

      setIcon(b ? getAlternativeIcon() : getActiveIcon(),
              b ? getAlternativeIcon() : getInactiveIcon() == null ? getActiveIcon() : getInactiveIcon(),
              b ? getAlternativeHoveredIcon() : getActiveHoveredIcon());
      myCurrentAction = b ? myAlternativeAction : myAction;

      setToolTipText(getToolTipTextByAction(myCurrentAction));

      repaint();
    }

    public ActionButton(AnAction action, @NotNull Icon activeIcon, Icon inactiveIcon) {
      this(action, null, activeIcon, inactiveIcon, null);
    }

    public ActionButton(AnAction action, Icon activeIcon) {
      this(action, activeIcon, activeIcon);
    }

    @Override
    public void actionPerformed(final ActionEvent e) {
      AnAction action =
        myAlternativeAction != null && BitUtil.isSet(e.getModifiers(), InputEvent.ALT_MASK) ? myAlternativeAction : myAction;
      final DataContext dataContext = DataManager.getInstance().getDataContext(this);
      final ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
      InputEvent inputEvent = e.getSource() instanceof InputEvent ? (InputEvent) e.getSource() : null;
      final AnActionEvent event = AnActionEvent.createFromAnAction(action, inputEvent, ActionPlaces.TOOLWINDOW_TITLE, dataContext);
      actionManager.fireBeforeActionPerformed(action, dataContext, event);
      final Component component = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext);
      if (component != null && !component.isShowing()) {
        return;
      }

      action.actionPerformed(event);
    }

    public boolean isActive() {
      return ToolWindowHeader.this.isActive();
    }

    public void setIcon(final Icon active, Icon inactive, Icon hovered) {
      myButton.setIcons(active, inactive, hovered);
    }

    @Override
    public void setToolTipText(final String text) {
      myButton.setToolTipText(text);
    }

    @Override
    public void altPressed() {
      PointerInfo info = MouseInfo.getPointerInfo();
      if (info != null) {
        Point p = info.getLocation();
        SwingUtilities.convertPointFromScreen(p, this);
        switchAlternativeAction(myButton.getBounds().contains(p));
      }
    }

    @Override
    public void altReleased() {
      switchAlternativeAction(false);
    }
  }

  private static String getToolTipTextByAction(AnAction action) {
    String text = KeymapUtil.createTooltipText(action.getTemplatePresentation().getText(), action);

    if (action instanceof HideAction) {
      text += String.format(" (Click with %s to Hide Side)", KeymapUtil.getShortcutText(KeyboardShortcut.fromString("pressed ALT")));
    }

    return text;
  }

  private abstract class HideSideAction extends AnAction implements DumbAware {
    @NonNls static final String HIDE_ACTIVE_SIDE_WINDOW_ACTION_ID = ToolWindowHeader.HIDE_ACTIVE_SIDE_WINDOW_ACTION_ID;

    public HideSideAction() {
      copyFrom(ActionManager.getInstance().getAction(HIDE_ACTIVE_SIDE_WINDOW_ACTION_ID));
      getTemplatePresentation().setText(UIBundle.message("tool.window.hideSide.action.name"));
    }

    @Override
    public abstract void actionPerformed(@NotNull final AnActionEvent e);

    @Override
    public final void update(@NotNull final AnActionEvent event) {
      final Presentation presentation = event.getPresentation();
      presentation.setEnabled(myInfo.isVisible());
    }
  }

  private abstract class HideAction extends AnAction implements DumbAware {
    @NonNls static final String HIDE_ACTIVE_WINDOW_ACTION_ID = ToolWindowHeader.HIDE_ACTIVE_WINDOW_ACTION_ID;

    public HideAction() {
      copyFrom(ActionManager.getInstance().getAction(HIDE_ACTIVE_WINDOW_ACTION_ID));
      getTemplatePresentation().setText(UIBundle.message("tool.window.hide.action.name"));
    }

    @Override
    public final void update(@NotNull final AnActionEvent event) {
      final Presentation presentation = event.getPresentation();
      presentation.setEnabled(myInfo.isVisible());
    }
  }
}
