/*
 * Copyright 2003-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 jetbrains.mps.ide.devkit.components;

import com.intellij.ide.actions.CloseTabToolbarAction;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.ui.ScrollPaneFactory;
import jetbrains.mps.icons.MPSIcons.Nodes;
import jetbrains.mps.ide.ui.smodel.ConceptTreeNode;
import jetbrains.mps.ide.ui.smodel.PropertiesTreeNode;
import jetbrains.mps.ide.ui.smodel.ReferencesTreeNode;
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.ide.ui.tree.smodel.SNodeTreeNode.NodeChildrenProvider;
import jetbrains.mps.openapi.navigation.EditorNavigator;
import jetbrains.mps.project.MPSProject;
import jetbrains.mps.smodel.ModelReadRunnable;
import jetbrains.mps.typesystem.PresentationManager;
import jetbrains.mps.typesystem.inference.TypeChecker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.model.SReference;

import javax.swing.JPanel;
import java.awt.BorderLayout;

public class NodeExplorerComponent extends JPanel implements Disposable {
  private MPSProject myProject;
  private SNode myNode;

  private final MyTree myTree = new MyTree();

  private Runnable myCloseAction = null;

  public NodeExplorerComponent(MPSProject mpsProject, SNode node) {
    super(new BorderLayout());
    myProject = mpsProject;

    this.add(ScrollPaneFactory.createScrollPane(myTree), BorderLayout.CENTER);

    DefaultActionGroup group = new DefaultActionGroup();
    group.addAll(new CloseTabToolbarAction() {
      @Override
      public void actionPerformed(AnActionEvent e) {
        if(myCloseAction != null) {
          myCloseAction.run();
        }
      }
    }, ActionManager.getInstance().getAction(IdeActions.ACTION_PIN_ACTIVE_TAB));
    final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.TOOLBAR, group, false);
    actionToolbar.setTargetComponent(myTree);
    this.add(actionToolbar.getComponent(), BorderLayout.WEST);

    myNode = node;
    myTree.rebuildNow();
  }

  public void setCloseAction(Runnable closeAction) {
    myCloseAction = closeAction;
  }

  @Override
  public void dispose() {
    myProject = null;
    myNode = null;

    myTree.dispose();
  }

  private class MyTree extends MPSTree implements NodeChildrenProvider {
    @Override
    protected ActionGroup createPopupActionGroup(MPSTreeNode node) {
      return null;
    }

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

    @Override
    protected MPSTreeNode rebuild() {
      myTree.setRootVisible(myNode == null);
      if (myNode == null) {
        return new TextTreeNode("No node");
      } else {
        TextTreeNode textTreeNode = new TextTreeNode("Node");
        SNodeTreeNode sNodeTreeNode = new SNodeTreeNodeWithType(myNode);
        textTreeNode.add(sNodeTreeNode);
        return textTreeNode;
      }
    }

    @Override
    public void populate(SNodeTreeNode treeNode) {
      SNode n = treeNode.getSNode();
      if (n == null || n.getModel() == null) {
        return;
      }

      treeNode.add(new ConceptTreeNode(n));
      treeNode.add(new PropertiesTreeNode(n));
      treeNode.add(new ReferencesTreeNode(n) {
        @Override
        protected void doInit() {
          // TODO: move to base ReferencesTreeNode class as option?
          for (final SReference ref : getSNode().getReferences()) {
            final SNode targetNode = ref.getTargetNode();
            if (targetNode != null) {
              add(new SNodeTreeNodeWithType(targetNode, ref.getLink().getName()));
            } else {
              // Try to show user as much info as possible
              add(new TextTreeNode(Nodes.Unknown, String.valueOf(ref.getTargetNodeReference())));
            }
          }
          myInitialized = true;
        }
      });
    }

    @Override
    protected void doubleClick(@NotNull MPSTreeNode nodeToClick) {
      // TODO: update navigation logic to avoid this copy/paste
      // Copied from jetbrains.mps.ide.projectPane.logicalview.ProjectPaneTree.doubleClick()
      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 class SNodeTreeNodeWithType extends SNodeTreeNode {

    public SNodeTreeNodeWithType(SNode node) {
      super(node);
    }

    public SNodeTreeNodeWithType(SNode node, String role) {
      super(node, role);
    }

    @Override
    protected void doUpdatePresentation_internal() {
      super.doUpdatePresentation_internal();

      // TODO: add to base SNodeTreeNode class and get rid of this inner class?
      String string = getText();
      final SNode typeOf = TypeChecker.getInstance().getTypeOf(getSNode());
      if (typeOf != null) {
        String typeInfo = " {" + PresentationManager.toString(typeOf) + "}";
        setText(string + typeInfo);
      }
    }
  }
}
