/*
 * 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.ide.bookmark;

import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.AnActionEvent;
import jetbrains.mps.ide.bookmark.BookmarkManager.BookmarkListener;
import jetbrains.mps.ide.icons.IdeIcons;
import jetbrains.mps.ide.ui.tree.MPSTree;
import jetbrains.mps.ide.ui.tree.MPSTreeNode;
import jetbrains.mps.ide.ui.tree.TextTreeNode;
import jetbrains.mps.ide.ui.tree.smodel.NodeTargetProvider;
import jetbrains.mps.ide.ui.tree.smodel.SNodeTreeNode;
import jetbrains.mps.openapi.navigation.EditorNavigator;
import jetbrains.mps.project.Project;
import jetbrains.mps.smodel.ModelReadRunnable;
import jetbrains.mps.workbench.action.ActionUtils;
import jetbrains.mps.workbench.action.BaseAction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;

import java.util.List;
import java.util.Map;

public class BookmarksTree extends MPSTree {
  private final BookmarkManager myBookmarkManager;
  private final Project myProject;

  public BookmarksTree(Project project, BookmarkManager bookmarkManager) {
    myBookmarkManager = bookmarkManager;
    myProject = project;

    myBookmarkManager.addBookmarkListener(new BookmarkListener() {
      @Override
      public void bookmarkAdded(int number, SNode node) {
        rebuildBookmarksTree();
      }

      @Override
      public void bookmarkRemoved(int number, SNode node) {
        rebuildBookmarksTree();
      }

      private void rebuildBookmarksTree() {
        BookmarksTree.this.rebuildLater();
      }
    });
  }

  @Override
  protected void doInit(MPSTreeNode node, Runnable nodeInitRunnable) {
    super.doInit(node, new ModelReadRunnable(myProject.getModelAccess(), nodeInitRunnable));
  }

  @Override
  protected ActionGroup createPopupActionGroup(final MPSTreeNode node) {
    if (node instanceof BookmarkNode) {
      BaseAction action = new BaseAction("Remove Bookmark") {
        @Override
        protected void doExecute(AnActionEvent e, Map<String, Object> _params) {
          ((BookmarkNode) node).removeBookmark();
        }
      };
      return ActionUtils.groupFromActions(action);
    } else if(!(node instanceof SNodeTreeNode)){
      BaseAction hierarchyAction = new BaseAction("Remove All Bookmarks") {
        @Override
        protected void doExecute(AnActionEvent e, Map<String, Object> _params) {
          myBookmarkManager.clearBookmarks();
        }
      };
      return ActionUtils.groupFromActions(hierarchyAction);
    }
    return null;
  }

  @Override
  protected MPSTreeNode rebuild() {
    MPSTreeNode root = new TextTreeNode("no bookmarks");
    root.setIcon(IdeIcons.DEFAULT_ICON);
    List<SNodeReference> nodePointers = myBookmarkManager.getAllNumberedBookmarks();
    boolean hasBookmarks = false;
    for (int i = 0; i < nodePointers.size(); i++) {
      final SNodeReference nodePointer = nodePointers.get(i);
      if (nodePointer != null && nodePointer.resolve(myProject.getRepository()) != null) {
        hasBookmarks = true;
        TextTreeNode textTreeNode = new MyTextTreeNodeNumbered(i);
        textTreeNode.setIcon(BookmarkManager.getIcon(i));
        textTreeNode.add(new SNodeTreeNode(nodePointer.resolve(myProject.getRepository())));
        root.add(textTreeNode);
      }
    }
    nodePointers = myBookmarkManager.getAllUnnumberedBookmarks();
    for (SNodeReference nodePointer : nodePointers) {
      if (nodePointer != null && nodePointer.resolve(myProject.getRepository()) != null) {
        hasBookmarks = true;
        TextTreeNode textTreeNode = new MyTextTreeNodeUnnumbered(nodePointer);
        textTreeNode.setIcon(BookmarkManager.getIcon(-1));
        textTreeNode.add(new SNodeTreeNode(nodePointer.resolve(myProject.getRepository())));
        root.add(textTreeNode);
      }
    }
    if (hasBookmarks) {
      root.setText("bookmarks");
    }
    return root;
  }

  public void gotoSelectedBookmark() {
    final BookmarkNode node = getSelectedBookmarkNode();
    if (node != null) {
      node.navigateToBookmark();
    }
  }

  public void removeSelectedBookmark() {
    BookmarkNode node = getSelectedBookmarkNode();
    if (node != null) {
      node.removeBookmark();
    }
  }

  private BookmarkNode getSelectedBookmarkNode() {
    MPSTreeNode selectedNode = (MPSTreeNode) getSelectionPath().getLastPathComponent();
    while (selectedNode != null) {
      if (selectedNode instanceof BookmarkNode) {
        return (BookmarkNode) selectedNode;
      }
      selectedNode = (MPSTreeNode) selectedNode.getParent();
    }
    return null;
  }

  @Override
  protected void doubleClick(@NotNull MPSTreeNode nodeToClick) {
    if (nodeToClick instanceof NodeTargetProvider) {
      final SNodeReference navigationTarget = ((NodeTargetProvider) nodeToClick).getNavigationTarget();
      if (navigationTarget != null) {
        new EditorNavigator(myProject).shallFocus(true).selectIfChild().open(navigationTarget);
        return;
      }
      // fall-through
    }
    super.doubleClick(nodeToClick);
  }

  private interface BookmarkNode {
    public void navigateToBookmark();

    public void removeBookmark();
  }

  private class MyTextTreeNodeNumbered extends TextTreeNode implements BookmarkNode {
    int myNumber;

    public MyTextTreeNodeNumbered(int i) {
      super("bookmark " + i);
      myNumber = i;
      setNodeIdentifier("bookmark" + i);
    }

    @Override
    public void removeBookmark() {
      myBookmarkManager.removeBookmark(myNumber);
    }

    @Override
    public void navigateToBookmark() {
      myBookmarkManager.navigateToBookmark(myNumber);
    }
  }

  private class MyTextTreeNodeUnnumbered extends TextTreeNode implements BookmarkNode {
    SNodeReference myNodePointer;

    @Override
    public void removeBookmark() {
      myBookmarkManager.removeUnnumberedBookmark(myNodePointer);
    }

    public MyTextTreeNodeUnnumbered(SNodeReference nodePointer) {
      super("bookmark");
      myNodePointer = nodePointer;
      setNodeIdentifier("bookmark_" + nodePointer.toString());
    }

    @Override
    public void navigateToBookmark() {
      new EditorNavigator(myProject).shallFocus(true).shallSelect(true).open(myNodePointer);
    }
  }
}
