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

import com.intellij.ide.dnd.DnDSupport;
import com.intellij.ide.dnd.DnDTarget;
import com.intellij.openapi.ui.JBPopupMenu;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.ui.UIBundle;
import com.intellij.ui.awt.RelativeRectangle;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.ui.content.ContentManagerEvent;
import com.intellij.ui.content.TabbedContent;
import com.intellij.util.ui.BaseButtonBehavior;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

class TabContentLayout extends ContentLayout {

  static final int MORE_ICON_BORDER = 6;
  LayoutData myLastLayout;

  JPopupMenu myPopup;
  final PopupMenuListener myPopupListener;

  ArrayList<ContentTabLabel> myTabs = new ArrayList<>();
  final Map<Content, ContentTabLabel> myContent2Tabs = new HashMap<>();

  private Map<String, BufferedImage> myCached = new com.intellij.util.containers.HashMap<>();

  private final MoreIcon myMoreIcon = new MoreIcon() {
    protected Rectangle getIconRec() {
      return myLastLayout.moreRect;
    }

    protected boolean isActive() {
      return myUi.myWindow.isActive();
    }

    protected int getIconY(final Rectangle iconRec) {
      return iconRec.height / TAB_ARC - getIconHeight() / TAB_ARC;
    }
  };

  TabContentLayout(ToolWindowContentUi ui) {
    super(ui);

    myPopupListener = new MyPopupListener();

    new BaseButtonBehavior(myUi) {
      protected void execute(final MouseEvent e) {
        if (!myUi.isCurrent(TabContentLayout.this)) return;

        if (myLastLayout != null) {
          final Rectangle moreRect = myLastLayout.moreRect;
          if (moreRect != null && moreRect.contains(e.getPoint())) {
            showPopup();
          }
        }
      }
    };
  }

  @Override
  public void init() {
    reset();

    myIdLabel = new BaseLabel(myUi, false) {
      @Override
      protected boolean allowEngravement() {
        return myUi.myWindow.isActive();
      }
    };
    for (int i = 0; i < myUi.myManager.getContentCount(); i++) {
      contentAdded(new ContentManagerEvent(this, myUi.myManager.getContent(i), i));
    }
  }

  @Override
  public void reset() {
    myTabs.clear();
    myContent2Tabs.clear();
    myIdLabel = null;
  }

  private void showPopup() {
    myPopup = new JBPopupMenu();
    myPopup.addPopupMenuListener(myPopupListener);

    ArrayList<ContentTabLabel> tabs = myTabs;

    for (final ContentTabLabel each : tabs) {
      final JCheckBoxMenuItem item = new JCheckBoxMenuItem(each.getText());
      if (myUi.myManager.isSelected(each.myContent)) {
        item.setSelected(true);
      }
      item.addActionListener(new ActionListener() {
        public void actionPerformed(final ActionEvent e) {
          myUi.myManager.setSelectedContent(each.myContent, true);
        }
      });
      myPopup.add(item);
    }
    myPopup.show(myUi, myLastLayout.moreRect.x, myLastLayout.moreRect.y);
  }


  private class MyPopupListener implements PopupMenuListener {
    public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
    }

    public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
      if (myPopup != null) {
        myPopup.removePopupMenuListener(this);
      }
      myPopup = null;
    }

    public void popupMenuCanceled(final PopupMenuEvent e) {
    }
  }

  @Override
  public void layout() {
    Rectangle bounds = myUi.getBounds();
    ContentManager manager = myUi.myManager;
    LayoutData data = new LayoutData(myUi);

    data.eachX = 2;
    data.eachY = 0;

    if (isIdVisible()) {
      myIdLabel.setBounds(data.eachX, data.eachY, myIdLabel.getPreferredSize().width, bounds.height);
      data.eachX += myIdLabel.getPreferredSize().width;
    }
    int tabsStart = data.eachX;

    if (manager.getContentCount() == 0) return;

    Content selected = manager.getSelectedContent();
    if (selected == null) {
      selected = manager.getContents()[0];
    }

    if (myLastLayout != null &&
        myLastLayout.layoutSize.equals(bounds.getSize()) &&
        myLastLayout.contentCount == manager.getContentCount()) {
      for (ContentTabLabel each : myTabs) {
        if (!each.isValid()) break;
        if (each.myContent == selected && each.getBounds().width != 0) {
          data = myLastLayout;
          data.fullLayout = false;
        }
      }
    }


    if (data.fullLayout) {
      for (ContentTabLabel eachTab : myTabs) {
        final Dimension eachSize = eachTab.getPreferredSize();
        data.requiredWidth += eachSize.width;
        data.requiredWidth++;
        data.toLayout.add(eachTab);
      }


      data.moreRectWidth = myMoreIcon.getIconWidth() + MORE_ICON_BORDER * TAB_ARC;
      data.toFitWidth = bounds.getSize().width - data.eachX;

      final ContentTabLabel selectedTab = myContent2Tabs.get(selected);
      while (true) {
        if (data.requiredWidth <= data.toFitWidth) break;
        if (data.toLayout.size() <= 1) break;

        if (data.toLayout.get(0) != selectedTab) {
          dropTab(data, data.toLayout.remove(0));
        }
        else if (data.toLayout.get(data.toLayout.size() - 1) != selectedTab) {
          dropTab(data, data.toLayout.remove(data.toLayout.size() - 1));
        }
        else {
          break;
        }
      }

      boolean reachedBounds = false;
      data.moreRect = null;
      for (ContentTabLabel each : data.toLayout) {
        data.eachY = 0;
        final Dimension eachSize = each.getPreferredSize();
        if (data.eachX + eachSize.width < data.toFitWidth + tabsStart) {
          each.setBounds(data.eachX, data.eachY, eachSize.width, bounds.height - data.eachY);
          data.eachX += eachSize.width;
        }
        else {
          if (!reachedBounds) {
            final int width = bounds.width - data.eachX - data.moreRectWidth;
            each.setBounds(data.eachX, data.eachY, width, bounds.height - data.eachY);
            data.eachX += width;
          }
          else {
            each.setBounds(0, 0, 0, 0);
          }
          reachedBounds = true;
        }
      }

      for (ContentTabLabel each : data.toDrop) {
        each.setBounds(0, 0, 0, 0);
      }
    }

    if (data.toDrop.size() > 0) {
      data.moreRect = new Rectangle(data.eachX + MORE_ICON_BORDER, 0, myMoreIcon.getIconWidth(), bounds.height);
      final int selectedIndex = manager.getIndexOfContent(manager.getSelectedContent());
      if (selectedIndex == 0) {
        myMoreIcon.setPaintedIcons(false, true);
      }
      else if (selectedIndex == manager.getContentCount() - 1) {
        myMoreIcon.setPaintedIcons(true, false);
      }
      else {
        myMoreIcon.setPaintedIcons(true, true);
      }
    }
    else {
      data.moreRect = null;
    }

    myLastLayout = data;
  }

  @Override
  public int getMinimumWidth() {
    int result = 0;
    if (myIdLabel != null) {
      result += myIdLabel.getPreferredSize().width;
      Insets insets = myIdLabel.getInsets();
      if (insets != null) {
        result += insets.left + insets.right;
      }
    }
    if (myLastLayout != null) {
      result += myLastLayout.moreRectWidth + myLastLayout.requiredWidth;
      result -= myLastLayout.toLayout.size() > 1 ? myLastLayout.moreRectWidth + 1 : -14;
    }
    return result;
  }

  static void dropTab(final LayoutData data, final ContentTabLabel toDropLabel) {
    data.requiredWidth -= (toDropLabel.getPreferredSize().width + 1);
    data.toDrop.add(toDropLabel);
    if (data.toDrop.size() == 1) {
      data.toFitWidth -= data.moreRectWidth;
    }
  }

  boolean isToDrawTabs() {
    return myTabs.size() > 1;
  }

  static class LayoutData {
    int toFitWidth;
    int requiredWidth;
    Dimension layoutSize;
    boolean fullLayout = true;

    int moreRectWidth;

    ArrayList<ContentTabLabel> toLayout = new ArrayList<>();
    ArrayList<ContentTabLabel> toDrop = new ArrayList<>();

    Rectangle moreRect;

    public int eachX;
    public int eachY;
    public int contentCount;

    LayoutData(ToolWindowContentUi ui) {
      layoutSize = ui.getSize();
      contentCount = ui.myManager.getContentCount();
    }
  }

  @Override
  public void paintComponent(Graphics g) {
    if (!isToDrawTabs()) return;

    boolean prevSelected = false;
    for (int i = 0; i < myTabs.size(); i++) {
      boolean last = (i == myTabs.size() - 1) || ((i + 1 < myTabs.size() && myTabs.get(i + 1).getBounds().width == 0));
      ContentTabLabel each = myTabs.get(i);
      Rectangle r = each.getBounds();

      StringBuilder key = new StringBuilder().append(i);
      if (each.isSelected()) key.append('s');
      if (prevSelected) key.append('p');
      if (last) key.append('l');
      if (myUi.myWindow.isActive()) key.append('a');

      BufferedImage image = myCached.get(key.toString());
      if (image == null || image.getWidth() != r.width || image.getHeight() != r.height) {
        image = drawToBuffer(r, each.isSelected(), last, prevSelected, myUi.myWindow.isActive());
        myCached.put(key.toString(), image);
      }
      
      if (image != null) {
        UIUtil.drawImage(g, image, isIdVisible() ? r.x : r.x - 2, r.y, null);
      }
      
      prevSelected = each.isSelected();
    }
  }
  
  @Nullable
  private static BufferedImage drawToBuffer(Rectangle r, boolean selected, boolean last, boolean prevSelected, boolean active) {
    if (r.width <= 0 || r.height <= 0) return null;
    BufferedImage image = UIUtil.createImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = image.createGraphics();
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    if (selected) {
      if (!UIUtil.isUnderDarcula()) {
      g2d.setColor(active ? new Color(0, 0, 0, 70) : new Color(0, 0, 0, 90));
      g2d.fillRect(0, 0, r.width, r.height);

      g2d.setColor(new Color(0, 0, 0, 140));
      g2d.drawLine(0, 0, r.width - 1, 0);
      g2d.drawLine(0, 1, 0, r.height - 1);

      g2d.setColor(new Color(0, 0, 0, 20));
      g2d.drawLine(1, 1, r.width - 1, 1);
      g2d.drawLine(1, 2, 1, r.height - 2);
      g2d.drawLine(1, r.height - 1, r.width - 1, r.height - 1);

      g2d.setColor(new Color(0, 0, 0, 60));
      g2d.drawLine(r.width - 1, 1, r.width - 1, r.height - 2);
      }

      if (active) {
        g2d.setColor(new Color(100, 150, 230, 50));
        g2d.fill(new Rectangle(0, 0, r.width, r.height));
      }
    }
    else {
      g2d.setPaint(UIUtil.getGradientPaint(0, 0, new Color(0, 0, 0, 10), 0, r.height, new Color(0, 0, 0, 30)));
      g2d.fillRect(0, 0, r.width, r.height);

      final Color c = new Color(255, 255, 255, UIUtil.isUnderDarcula() ? 10 : 80);
      if (last) {
        if (prevSelected) {
          g2d.setColor(c);
          g2d.drawRect(0, 0, r.width - 2, r.height - 1);
        } else {
          g2d.setColor(c);
          g2d.drawRect(1, 0, r.width - 3, r.height - 1);

          g2d.setColor(new Color(0, 0, 0, 60));
          g2d.drawLine(0, 0, 0, r.height);
        }

        g2d.setColor(new Color(0, 0, 0, 60));
        g2d.drawLine(r.width - 1, 0, r.width - 1, r.height);
      } else {
        if (prevSelected) {
          g2d.setColor(c);
          g2d.drawRect(0, 0, r.width - 1, r.height - 1);
        }
        else {
          g2d.setColor(c);
          g2d.drawRect(1, 0, r.width - 2, r.height - 1);

          g2d.setColor(new Color(0, 0, 0, 60));
          g2d.drawLine(0, 0, 0, r.height);
        }
      }
    }

    g2d.dispose();
    return image;
  }

  @Override
  public void paintChildren(Graphics g) {
    if (!isToDrawTabs()) return;

    if (myLastLayout != null && myLastLayout.moreRect != null) {
      myMoreIcon.paintIcon(myUi, g);
    }
  }

  @Override
  public void update() {
    for (ContentTabLabel each : myTabs) {
      each.update();
    }

    updateIdLabel(myIdLabel);
  }

  @Override
  public void rebuild() {
    myUi.removeAll();

    myUi.add(myIdLabel);
    myUi.initMouseListeners(myIdLabel, myUi);

    for (ContentTabLabel each : myTabs) {
      myUi.add(each);
      myUi.initMouseListeners(each, myUi);
    }
    
    myCached.clear();
  }

  @Override
  public void contentAdded(ContentManagerEvent event) {
    final Content content = event.getContent();
    final ContentTabLabel tab;
    if (content instanceof TabbedContent) {
      tab = new TabbedContentTabLabel((TabbedContent)content, this);
    } else {
      tab = new ContentTabLabel(content, this);
    }
    myTabs.add(event.getIndex(), tab);
    myContent2Tabs.put(content, tab);
    if (content instanceof DnDTarget) {
      DnDTarget target = (DnDTarget)content;
      DnDSupport.createBuilder(tab).setDropHandler(target).setTargetChecker(target).install();
    }
    
    myCached.clear();
  }

  @Override
  public void contentRemoved(ContentManagerEvent event) {
    final ContentTabLabel tab = myContent2Tabs.get(event.getContent());
    if (tab != null) {
      myTabs.remove(tab);
      myContent2Tabs.remove(event.getContent());
    }
    
    myCached.clear();
  }

  @Override
  public boolean shouldDrawDecorations() {
    return isToDrawTabs();
  }

  @Override
  public void showContentPopup(ListPopup listPopup) {
    Content selected = myUi.myManager.getSelectedContent();
    if (selected != null) {
      ContentTabLabel tab = myContent2Tabs.get(selected);
      listPopup.showUnderneathOf(tab);
    } else {
      listPopup.showUnderneathOf(myIdLabel);
    }
  }

  @Override
  public RelativeRectangle getRectangleFor(Content content) {
    ContentTabLabel label = myContent2Tabs.get(content);
    return new RelativeRectangle(label.getParent(), label.getBounds());
  }

  public Component getComponentFor(Content content) {
    return myContent2Tabs.get(content);
  }

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

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

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