/*
 * 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.devkit.generator;

import com.intellij.icons.AllIcons.General;
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.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.PopupAction;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.ui.ScrollPaneFactory;
import jetbrains.mps.generator.GenerationSettingsProvider;
import jetbrains.mps.generator.IGenerationSettings.GenTraceSettings;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.project.Project;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.ModelComputeRunnable;
import jetbrains.mps.workbench.action.ActionUtils;
import jetbrains.mps.workbench.action.BaseAction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.module.SRepository;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;

/**
 * Tree of trace elements and an action bar
 */
final class GenerationTracerView {

  private final Project myProject;

  enum Kind { TraceForward, TraceBackward }

  private final JPanel myPanel;
  private final GenerationTracerTree myTree;
  private final GenerationTracerViewTool myTool;
  private final SNodeReference myInputNode;
  private final Kind myViewToken;

  public GenerationTracerView(GenerationTracerViewTool tool, SNodeReference inputNode, Kind viewToken, TraceNodeUI tracerNode) {
    myTool = tool;
    myInputNode = inputNode;
    myViewToken = viewToken;
    myPanel = new JPanel(new BorderLayout());
    myProject = ProjectHelper.fromIdeaProject(tool.getProject());
    myTree = new GenerationTracerTree(this, tracerNode, myProject, inputNode);
    myTree.setRootVisible(false);
    myPanel.add(ScrollPaneFactory.createScrollPane(myTree), BorderLayout.CENTER);
    myPanel.add(createActionsToolbar(), BorderLayout.WEST);

    myTree.rebuildLater();
  }

  private Component createActionsToolbar() {
    ToggleAction autoscrollAction = new ToggleAction("", "Autoscroll to Source", Icons.AUTOSCROLL_TO_SOURCE) {
      public boolean isSelected(AnActionEvent e) {
        return myTool.isAutoscroll();
      }

      public void setSelected(AnActionEvent e, boolean state) {
        myTool.autoscrollsChanged(state);
      }
    };

    AnAction closeAction = new AnAction("", "Close", Icons.CLOSE) {
      @Override
      public void actionPerformed(AnActionEvent e) {
        myTool.close(GenerationTracerView.this);
      }
    };

    class PresentationSettings extends AnAction implements PopupAction {
      PresentationSettings() {
        super("Settings", "Settings", General.Settings);
      }

      @Override
      public void actionPerformed(AnActionEvent e) {
        JPopupMenu menu = createViewSettingsMenu();
        menu.show(e.getInputEvent().getComponent(), 10, 10);
      }
    }

    ActionGroup group = ActionUtils.groupFromActions(autoscrollAction, closeAction, new PresentationSettings());
    ActionManager manager = ActionManager.getInstance();
    ActionToolbar toolbar = manager.createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, group, false);
    return toolbar.getComponent();
  }

  boolean isViewFor(Kind kind, SNodeReference node) {
    return myViewToken == kind && myInputNode.equals(node);
  }

  @Nullable
  ActionGroup getTraceActionGroup(TraceNodeUI selected) {
    if (selected.getNavigateTarget() == null) {
      return null;
    }
    if (selected.hasPrevStep() || selected.hasNextStep()) {
      return new ModelComputeRunnable<ActionGroup>(new NodeActionGroup(selected, myProject.getRepository())).runRead(myProject.getModelAccess());
    }
    return null;
  }

  boolean isForwardTraceView() {
    return myViewToken == Kind.TraceForward;
  }
  boolean isBackwardTraceView() {
    return myViewToken == Kind.TraceBackward;
  }
  GenerationTracerViewTool getTool() {
    return myTool;
  }

  public JComponent getComponent() {
    return myPanel;
  }

  void setAutoscrollToSource(boolean b) {
    myTree.setAutoOpen(b);
  }

  void viewSettingsChanged() {
    myProject.getModelAccess().runReadAction(new Runnable() {
      @Override
      public void run() {
        rebuildTree();
      }
    });
  }

  void rebuildTree() {
    SNode inputNode = myInputNode.resolve(myProject.getRepository());
    if (inputNode == null) {
      return;
    }
    TraceNodeUI traceNode = null;
    if (isForwardTraceView()) {
      traceNode = myTool.buildForwardTrace(inputNode);
    } else if (isBackwardTraceView()) {
      traceNode = myTool.buildBackwardTrace(inputNode);
    }
    if (traceNode != null) {
      myTree.setRoot(traceNode);
      myTree.rebuildLater();
    }
  }

  JPopupMenu createViewSettingsMenu() {
    final GenTraceSettings settings = GenerationSettingsProvider.getInstance().getGenerationSettings().getTraceSettings();
    ActionListener l = new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        if (false == e.getSource() instanceof JCheckBoxMenuItem) {
          return;
        }
        boolean value = ((JCheckBoxMenuItem) e.getSource()).isSelected();
        switch (Integer.parseInt(e.getActionCommand())) {
          case 1 : settings.setGroupByStep(value); break;
          case 2 : settings.setCompactTemplates(value); break;
          case 3 : settings.setShowEmptySteps(value); break;
          case 4 : settings.setGroupByChange(value);
        }
        viewSettingsChanged();
      }
    };
    final JPopupMenu menu = new JPopupMenu();
    JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem("Group changes by step");
    menuItem.setToolTipText("Changes grouped by generation steps");
    menuItem.setSelected(settings.isGroupByStep());
    menuItem.setActionCommand(String.valueOf(1));
    menuItem.addActionListener(l);
    menu.add(menuItem);
    menuItem = new JCheckBoxMenuItem("Show change-specific templates only");
    menuItem.setToolTipText("Hides template elements except those closest to effective template node");
    menuItem.setSelected(settings.isCompactTemplates());
    menuItem.setActionCommand(String.valueOf(2));
    menuItem.addActionListener(l);
    menu.add(menuItem);
    menuItem = new JCheckBoxMenuItem("Show steps without changes");
    menuItem.setToolTipText("Reveal steps with no changes to selected node");
    menuItem.setSelected(settings.isShowEmptySteps());
    menuItem.setActionCommand(String.valueOf(3));
    menuItem.addActionListener(l);
    menu.add(menuItem);
    menuItem = new JCheckBoxMenuItem("Show individual changes");
    menuItem.setToolTipText("Each change entry (input-output pair) shown individually");
    menuItem.setSelected(settings.isGroupByChange());
    menuItem.setActionCommand(String.valueOf(4));
    menuItem.addActionListener(l);
    menu.add(menuItem);
    return menu;
  }

  private class NodeActionGroup implements Computable<ActionGroup> {
    private final TraceNodeUI myTraceNode;
    private final SRepository myRepo;

    public NodeActionGroup(@NotNull TraceNodeUI nodeUI, @NotNull SRepository repository) {
      myTraceNode = nodeUI;
      myRepo = repository;
    }

    public ActionGroup compute() {
      assert myTraceNode.getNavigateTarget() != null;
      DefaultActionGroup group = new DefaultActionGroup();
      if (myTraceNode.hasPrevStep()) {
        if (isBackwardTraceView()) {
          group.add(new ShowTraceAction("Show Trace", myTraceNode.getNavigateTarget(), myRepo));
        }

        group.add(new ShowTracebackAction("Show Prev Step Traceback", myTraceNode.getNavigateTarget(), myRepo));
      } else if (myTraceNode.hasNextStep()) {
        if (isForwardTraceView()) {
          group.add(new ShowTracebackAction("Show Traceback", myTraceNode.getNavigateTarget(), myRepo));
        }
        group.add(new ShowTraceAction("Show Next Step Trace", myTraceNode.getNavigateTarget(), myRepo));
      }
      //
      return group;
    }

  }

  private class ShowTraceAction extends BaseAction {
    private final SNodeReference myNode;
    private final SRepository myRepo;

    ShowTraceAction(@NotNull String title, @NotNull SNodeReference node, @NotNull SRepository repository) {
      super(title);
      myNode = node;
      myRepo = repository;
    }

    @Override
    protected void doExecute(AnActionEvent e, Map<String, Object> _params) {
      getTool().showTraceInputData(myNode.resolve(myRepo));
    }

    @Override
    protected void doUpdate(AnActionEvent e, Map<String, Object> _params) {
      boolean enabled = myNode.resolve(myRepo) != null && getTool().hasTraceInputData(myNode.getModelReference());
      setEnabledState(e.getPresentation(), enabled);
    }
  }

  private class ShowTracebackAction extends BaseAction {
    private final SNodeReference myNode;
    private final SRepository myRepo;

    ShowTracebackAction(@NotNull String title, @NotNull SNodeReference node, @NotNull SRepository repository) {
      super(title);
      myNode = node;
      myRepo = repository;
    }

    @Override
    protected void doExecute(AnActionEvent e, Map<String, Object> _params) {
      getTool().showTracebackData(myNode.resolve(myRepo));
    }

    @Override
    protected void doUpdate(AnActionEvent e, Map<String, Object> _params) {
      boolean enabled = myNode.resolve(myRepo) != null && getTool().hasTracebackData(myNode.getModelReference());
      setEnabledState(e.getPresentation(), enabled);
    }
  }
}
