/*
 * Copyright 2000-2009 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.ui.content;

import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.ui.*;
import com.intellij.ui.content.tabs.PinToolwindowTabAction;
import com.intellij.ui.content.tabs.TabbedContentAction;
import com.intellij.util.IJSwingUtilities;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.TabbedPaneUI;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;

/**
 * @author Eugene Belyaev
 * @author Anton Katilin
 * @author Vladimir Kondratyev
 */
public class TabbedPaneContentUI implements ContentUI, PropertyChangeListener {
  public static final String POPUP_PLACE = "TabbedPanePopup";

  private ContentManager myManager;
  private TabbedPaneWrapper myTabbedPaneWrapper;

  /**
   * Creates <code>TabbedPaneContentUI</code> with bottom tab placement.
   */
  public TabbedPaneContentUI() {
    this(JTabbedPane.BOTTOM);
  }

  /**
   * Creates <code>TabbedPaneContentUI</code> with cpecified tab placement.
   *
   * @param tabPlacement constant which defines where the tabs are located.
   *                     Acceptable values are <code>javax.swing.JTabbedPane#TOP</code>,
   *                     <code>javax.swing.JTabbedPane#LEFT</code>, <code>javax.swing.JTabbedPane#BOTTOM</code>
   *                     and <code>javax.swing.JTabbedPane#RIGHT</code>.
   */
  public TabbedPaneContentUI(int tabPlacement) {
    myTabbedPaneWrapper = new MyTabbedPaneWrapper(tabPlacement);
  }

  public JComponent getComponent() {
    return myTabbedPaneWrapper.getComponent();
  }

  public void setManager(@NotNull ContentManager manager) {
    if (myManager != null) {
      throw new IllegalStateException();
    }
    myManager = manager;
    myManager.addContentManagerListener(new MyContentManagerListener());
  }

  public void propertyChange(PropertyChangeEvent e) {
    if (Content.PROP_DISPLAY_NAME.equals(e.getPropertyName())) {
      Content content = (Content)e.getSource();
      int index = myTabbedPaneWrapper.indexOfComponent(content.getComponent());
      if (index != -1) {
        myTabbedPaneWrapper.setTitleAt(index, content.getTabName());
      }
    }
    else if (Content.PROP_DESCRIPTION.equals(e.getPropertyName())) {
      Content content = (Content)e.getSource();
      int index = myTabbedPaneWrapper.indexOfComponent(content.getComponent());
      if (index != -1) {
        myTabbedPaneWrapper.setToolTipTextAt(index, content.getDescription());
      }
    }
    else if (Content.PROP_COMPONENT.equals(e.getPropertyName())) {
      Content content = (Content)e.getSource();
      JComponent oldComponent = (JComponent)e.getOldValue();
      int index = myTabbedPaneWrapper.indexOfComponent(oldComponent);
      if (index != -1) {
        boolean hasFocus = IJSwingUtilities.hasFocus2(oldComponent);
        myTabbedPaneWrapper.setComponentAt(index, content.getComponent());
        if (hasFocus) {
          content.getComponent().requestDefaultFocus();
        }
      }
    }
    else if (Content.PROP_ICON.equals(e.getPropertyName())) {
      Content content = (Content)e.getSource();
      int index = myTabbedPaneWrapper.indexOfComponent(content.getComponent());
      if (index != -1) {
        myTabbedPaneWrapper.setIconAt(index, (Icon)e.getNewValue());
      }
    }
  }

  private Content getSelectedContent() {
    JComponent selectedComponent = myTabbedPaneWrapper.getSelectedComponent();
    return myManager.getContent(selectedComponent);
  }




  private class MyTabbedPaneWrapper extends TabbedPaneWrapper.AsJTabbedPane {
    public MyTabbedPaneWrapper(int tabPlacement) {
      super(tabPlacement);
    }

    protected TabbedPane createTabbedPane(int tabPlacement) {
      return new MyTabbedPane(tabPlacement);
    }

    protected TabbedPaneHolder createTabbedPaneHolder() {
      return new MyTabbedPaneHolder(this);
    }

    private class MyTabbedPane extends TabbedPaneImpl {
      public MyTabbedPane(int tabPlacement) {
        super(tabPlacement);
        addMouseListener(new MyPopupHandler());
        enableEvents(AWTEvent.MOUSE_EVENT_MASK);
      }

      private void closeTabAt(int x, int y) {
        TabbedPaneUI ui = getUI();
        int index = ui.tabForCoordinate(this, x, y);
        if (index < 0 || !myManager.canCloseContents()) {
          return;
        }
        final Content content = myManager.getContent(index);
        if (content != null && content.isCloseable()) {
          myManager.removeContent(content, true);
        }
      }

      /**
       * Hides selected menu.
       */
      private void hideMenu() {
        MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager();
        menuSelectionManager.clearSelectedPath();
      }

      protected void processMouseEvent(MouseEvent e) {
        if (e.isPopupTrigger()) { // Popup doesn't activate clicked tab.
          showPopup(e.getX(), e.getY());
          return;
        }

        if (!e.isShiftDown() && (MouseEvent.BUTTON1_MASK & e.getModifiers()) > 0) { // RightClick without Shift modifiers just select tab
          if (MouseEvent.MOUSE_RELEASED == e.getID()) {
            TabbedPaneUI ui = getUI();
            int index = ui.tabForCoordinate(this, e.getX(), e.getY());
            if (index != -1) {
              setSelectedIndex(index);
            }
            hideMenu();
          }
        }
        else if (e.isShiftDown() && (MouseEvent.BUTTON1_MASK & e.getModifiers()) > 0) { // Shift+LeftClick closes the tab
          if (MouseEvent.MOUSE_RELEASED == e.getID()) {
            closeTabAt(e.getX(), e.getY());
            hideMenu();
          }
        }
        else if ((MouseEvent.BUTTON2_MASK & e.getModifiers()) > 0) { // MouseWheelClick closes the tab
          if (MouseEvent.MOUSE_RELEASED == e.getID()) {
            closeTabAt(e.getX(), e.getY());
            hideMenu();
          }
        }
        else if ((MouseEvent.BUTTON3_MASK & e.getModifiers()) > 0 && SystemInfo.isWindows) { // Right mouse button doesn't activate tab
        }
        else {
          super.processMouseEvent(e);
        }
      }

      protected ChangeListener createChangeListener() {
        return new MyModelListener();
      }

      private class MyModelListener extends ModelListener {
        public void stateChanged(ChangeEvent e) {
          Content content = getSelectedContent();
          if (content != null) {
            myManager.setSelectedContent(content);
          }
          super.stateChanged(e);
        }
      }

      /**
       * @return content at the specified location.  <code>x</code> and <code>y</code> are in
       *         tabbed pane coordinate system. The method returns <code>null</code> if there is no contnt at the
       *         specified location.
       */
      private Content getContentAt(int x, int y) {
        TabbedPaneUI ui = getUI();
        int index = ui.tabForCoordinate(this, x, y);
        if (index < 0) {
          return null;
        }
        return myManager.getContent(index);
      }

      protected class MyPopupHandler extends PopupHandler {
        public void invokePopup(Component comp, int x, int y) {
          if (myManager.getContentCount() == 0) return;
          showPopup(x, y);
        }
      }

      /**
       * Shows showPopup menu at the specified location. The <code>x</code> and <code>y</code> coordinates
       * are in JTabbedPane coordinate system.
       */
      private void showPopup(int x, int y) {
        Content content = getContentAt(x, y);
        if (content == null) {
          return;
        }
        DefaultActionGroup group = new DefaultActionGroup();
        group.add(new TabbedContentAction.CloseAction(content));
        if (myTabbedPaneWrapper.getTabCount() > 1) {
          group.add(new TabbedContentAction.CloseAllAction(myManager));
          group.add(new TabbedContentAction.CloseAllButThisAction(content));
        }
        group.addSeparator();
        group.add(PinToolwindowTabAction.getPinAction());
        group.addSeparator();
        group.add(new TabbedContentAction.MyNextTabAction(myManager));
        group.add(new TabbedContentAction.MyPreviousTabAction(myManager));
        final List<AnAction> additionalActions = myManager.getAdditionalPopupActions(content);
        if (additionalActions != null) {
          group.addSeparator();
          for (AnAction anAction : additionalActions) {
            group.add(anAction);
          }
        }
        ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(POPUP_PLACE, group);
        menu.getComponent().show(myTabbedPaneWrapper.getComponent(), x, y);
      }
    }

    private class MyTabbedPaneHolder extends TabbedPaneHolder implements DataProvider {

      private MyTabbedPaneHolder(TabbedPaneWrapper wrapper) {
        super(wrapper);
      }

      public Object getData(String dataId) {
        if (PlatformDataKeys.CONTENT_MANAGER.is(dataId)) {
          return myManager;
        }
        if (PlatformDataKeys.NONEMPTY_CONTENT_MANAGER.is(dataId) && myManager.getContentCount() > 1) {
          return myManager;
        }
        return null;
      }
    }
  }

  private class MyContentManagerListener extends ContentManagerAdapter {
    public void contentAdded(ContentManagerEvent event) {
      Content content = event.getContent();
      myTabbedPaneWrapper.insertTab(content.getTabName(),
                                    content.getIcon(),
                                    content.getComponent(),
                                    content.getDescription(),
                                    event.getIndex());
      content.addPropertyChangeListener(TabbedPaneContentUI.this);
    }

    public void contentRemoved(ContentManagerEvent event) {
      event.getContent().removePropertyChangeListener(TabbedPaneContentUI.this);
      myTabbedPaneWrapper.removeTabAt(event.getIndex());
    }

    public void selectionChanged(ContentManagerEvent event) {
      int index = event.getIndex();
      if (index != -1 && event.getOperation() != ContentManagerEvent.ContentOperation.remove) {
        myTabbedPaneWrapper.setSelectedIndex(index);
      }
    }
  }

  public boolean isSingleSelection() {
    return true;
  }

  public boolean isToSelectAddedContent() {
    return false;
  }

  public boolean canBeEmptySelection() {
    return false;
  }

  public void beforeDispose() {
  }

  public boolean canChangeSelectionTo(@NotNull Content content, boolean implicit) {
    return true;
  }

  @NotNull
  @Override
  public String getCloseActionName() {
    return UIBundle.message("tabbed.pane.close.tab.action.name");
  }

  @NotNull
  @Override
  public String getCloseAllButThisActionName() {
    return UIBundle.message("tabbed.pane.close.all.tabs.but.this.action.name");
  }

  @NotNull
  @Override
  public String getPreviousContentActionName() {
    return "Select Previous Tab";
  }

  @NotNull
  @Override
  public String getNextContentActionName() {
    return "Select Next Tab";
  }

  public void dispose() {
  }
}
