/*
 * Copyright 2003-2011 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.ide.ui.tree;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;

/**
 * Ripped from IDEA
 */
public class TreeScrollingUtil {
  public static void selectPath(JTree tree, TreePath path) {
    tree.makeVisible(path);
    showRowCentred(tree, tree.getRowForPath(path));
  }

  public static void moveDown(JTree tree) {
    int size = tree.getRowCount();
    int row = getSelectedRow(tree);
    if (row < size - 1) {
      row++;
      showAndSelect(tree, row, row + 2, row, true);
    }
  }

  public static void moveUp(JTree tree) {
    int row = getSelectedRow(tree);
    if (row > 0) {
      row--;
      showAndSelect(tree, row - 2, row, row, true);
    }
  }

  public static void movePageUp(JTree tree) {
    int visible = getVisibleRowCount(tree);
    if (visible <= 0) {
      moveHome(tree);
      return;
    }
    int decrement = visible - 1;
    int row = Math.max(getSelectedRow(tree) - decrement, 0);
    int top = getFirstVisibleRow(tree) - decrement;
    int bottom = top + visible - 1;
    showAndSelect(tree, top, bottom, row, true);
  }

  public static void movePageDown(JTree tree) {
    int visible = getVisibleRowCount(tree);
    if (visible <= 0) {
      moveEnd(tree);
      return;
    }
    int size = tree.getRowCount();
    int increment = visible - 1;
    int index = Math.min(getSelectedRow(tree) + increment, size - 1);
    int top = getFirstVisibleRow(tree) + increment;
    int bottom = top + visible - 1;
    showAndSelect(tree, top, bottom, index, true);
  }

  public static void moveHome(JTree tree) {
    showRowCentred(tree, 0);
  }

  public static void moveEnd(JTree tree) {
    showRowCentred(tree, tree.getRowCount() - 1);
  }

  private static void showRowCentred(JTree tree, int row) {
    showRowCentred(tree, row, true);
  }

  public static void showRowCentred(JTree tree, int row, boolean centerHorizontally) {
    int visible = getVisibleRowCount(tree);
    int top = visible > 0 ? row - (visible - 1) / 2 : row;
    int bottom = visible > 0 ? top + visible - 1 : row;
    showAndSelect(tree, top, bottom, row, centerHorizontally);
  }

  private static void showAndSelect(JTree tree, int top, int bottom, int row,
                                    boolean centerHorizontally) {
    int size = tree.getRowCount();
    if (size == 0) {
      tree.clearSelection();
      return;
    }
    if (top < 0) {
      top = 0;
    }
    if (bottom >= size) {
      bottom = size - 1;
    }
    Rectangle topBounds = tree.getRowBounds(top);
    Rectangle bottomBounds = tree.getRowBounds(bottom);
    Rectangle bounds;
    if (topBounds == null) {
      bounds = bottomBounds;
    } else if (bottomBounds == null) {
      bounds = topBounds;
    } else {
      bounds = topBounds.union(bottomBounds);
    }
    if (bounds != null) {
      TreePath path = tree.getPathForRow(row);
      if (path != null && path.getParentPath() != null) {
        Rectangle parentBounds = tree.getPathBounds(path.getParentPath());
        if (parentBounds != null) {
          bounds.x = parentBounds.x;
        }
      }
      if (!centerHorizontally) {
        bounds.x = 0;
        bounds.width = tree.getWidth();
      } else {
        bounds.width = Math.min(bounds.width, tree.getVisibleRect().width);
      }
      tree.scrollRectToVisible(bounds);
    }
    tree.setSelectionRow(row);
  }

  private static int getSelectedRow(JTree tree) {
    return tree.getRowForPath(tree.getSelectionPath());
  }

  private static int getFirstVisibleRow(JTree tree) {
    Rectangle visible = tree.getVisibleRect();
    int row = -1;
    for (int i = 0; i < tree.getRowCount(); i++) {
      Rectangle bounds = tree.getRowBounds(i);
      if (visible.y <= bounds.y && visible.y + visible.height >= bounds.y + bounds.height) {
        row = i;
        break;
      }
    }
    return row;
  }

  private static int getVisibleRowCount(JTree tree) {
    Rectangle visible = tree.getVisibleRect();
    int count = 0;
    for (int i = 0; i < tree.getRowCount(); i++) {
      Rectangle bounds = tree.getRowBounds(i);
      if (visible.y <= bounds.y && visible.y + visible.height >= bounds.y + bounds.height) {
        count++;
      }
    }
    return count;
  }

  public static void installActions(final JTree tree) {
    tree.getActionMap().put("scrollUpChangeSelection", new AbstractAction() {
      @Override
      public void actionPerformed(ActionEvent e) {
        movePageUp(tree);
      }
    });
    tree.getActionMap().put("scrollDownChangeSelection", new AbstractAction() {
      @Override
      public void actionPerformed(ActionEvent e) {
        movePageDown(tree);
      }
    });
    tree.getActionMap().put("selectPrevious", new AbstractAction() {
      @Override
      public void actionPerformed(ActionEvent e) {
        moveUp(tree);
      }
    });
    tree.getActionMap().put("selectNext", new AbstractAction() {
      @Override
      public void actionPerformed(ActionEvent e) {
        moveDown(tree);
      }
    });
    Action action = tree.getActionMap().get("selectLast");
    if (action != null) {
      tree.getActionMap().put("selectLastChangeLead", action);
    }
  }

  public static void collapseAll(JTree tree, int keepSelectionLevel) {
    TreePath leadSelectionPath = tree.getLeadSelectionPath();
    // Collapse all
    int row = tree.getRowCount() - 1;
    while (row >= 0) {
      tree.collapseRow(row);
      row--;
    }
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
    tree.expandPath(new TreePath(root));
    if (leadSelectionPath != null) {
      Object[] path = leadSelectionPath.getPath();
      Object[] pathToSelect = new Object[path.length > keepSelectionLevel ? keepSelectionLevel : path.length];
      System.arraycopy(path, 0, pathToSelect, 0, pathToSelect.length);
      TreeScrollingUtil.selectPath(tree, new TreePath(pathToSelect));
    }
  }
}
