/*
 * 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.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.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.ui.content.ContentManagerAdapter;
import com.intellij.ui.content.ContentManagerEvent;
import jetbrains.mps.generator.GenerationTrace;
import jetbrains.mps.ide.generator.TransientModelsComponent;
import jetbrains.mps.ide.icons.IdeIcons;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.ide.tools.BaseProjectTool;
import jetbrains.mps.ide.tools.CloseAction;
import jetbrains.mps.util.ComputeRunnable;
import jetbrains.mps.workbench.action.ActionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;

import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.ArrayList;
import java.util.List;

public class GenerationTracerViewTool extends BaseProjectTool {
  private NoTabsComponent myNoTabsComponent;

  private List<GenerationTracerView> myTracerViews = new ArrayList<>();
  private ContentManagerAdapter myContentListener;
  private final TransientModelsComponent myTransientModelsOwner;
  private boolean myAutoscroll;


  public GenerationTracerViewTool(Project project, TransientModelsComponent transientModels) {
    super(project, "Generation Tracer", null, IdeIcons.DEFAULT_ICON, ToolWindowAnchor.BOTTOM, false, true);
    myTransientModelsOwner = transientModels;
    myNoTabsComponent = new NoTabsComponent(this);
  }

  //////
  public boolean hasTracingData() {
    ComputeRunnable<Boolean> r = new ComputeRunnable<>(() -> {
      // FIXME not quite nice code
      return myTransientModelsOwner.getModules().iterator().hasNext();
    });
    ProjectHelper.getModelAccess(getProject()).runReadAction(r);
    return r.getResult();
  }
  public boolean hasTraceInputData(SModelReference modelReference) {
    return myTransientModelsOwner.getTrace(modelReference) != null;
  }
  public boolean hasTracebackData(SModelReference modelReference) {
    return myTransientModelsOwner.getTrace(modelReference) != null;
  }
  public boolean showTraceInputData(@NotNull SNode node) {
    int index = getTabIndex(GenerationTracerView.Kind.TraceForward, node.getReference());
    if (index > -1) {
      selectIndex(index);
      openToolLater(true);
      return true;
    }

    TraceNodeUI tracerNode = buildForwardTrace(node);
    if (tracerNode == null) {
      return false;
    }
    showTraceView(GenerationTracerView.Kind.TraceForward, tracerNode, node);
    return true;
  }

  public boolean showTracebackData(SNode node) {
    int index = getTabIndex(GenerationTracerView.Kind.TraceBackward, node.getReference());
    if (index > -1) {
      selectIndex(index);
      openToolLater(true);
      return true;
    }
    TraceNodeUI tracerNode = buildBackwardTrace(node);
    if (tracerNode == null) {
      return false;
    }
    showTraceView(GenerationTracerView.Kind.TraceBackward, tracerNode, node);
    return true;
  }

  //////////////////

  @Override
  protected void createTool(boolean early) {
    StartupManager.getInstance(getProject()).runWhenProjectIsInitialized(() -> {
      showNoTabsComponent();
      setTracingDataIsAvailable(hasTracingData());
      setAvailable(false);
    });
  }

  protected void doRegister() {
    super.doRegister();
    myContentListener = new ContentManagerAdapter() {
      public void contentRemoved(ContentManagerEvent event) {
        final boolean removedNoTabsTab = event.getContent().getComponent() == myNoTabsComponent;
        //noTabs component could be removed
        if (!removedNoTabsTab) {
          myTracerViews.remove(event.getIndex());
        }
        if (getContentManager().getContentCount() == 0) {
          showNoTabsComponent();
          if (removedNoTabsTab) {
            makeUnavailableLater();
          }
        }
      }
    };

    getContentManager().addContentManagerListener(myContentListener);
  }

  @Override
  protected void doUnregister() {
    final ContentManager contentManager = getContentManager();
    if (myContentListener != null && contentManager != null && !contentManager.isDisposed()) {
      contentManager.removeContentManagerListener(myContentListener);
    }
    myContentListener = null;
    super.doUnregister();
  }

  private void showNoTabsComponent() {
    ContentManager manager = getContentManager();
    manager.removeAllContents(true);
    addContent(myNoTabsComponent, "", null, false);
  }

  private void closeTab(int index) {
    //noinspection ConstantConditions
    getContentManager().removeContent(getContentManager().getContent(index), true);
  }

  public void closeAll() {
    getContentManager().removeAllContents(true);
  }

  void selectIndex(int index) {
    ContentManager manager = getContentManager();
    //noinspection ConstantConditions
    manager.setSelectedContent(manager.getContent(index));
  }

  int getTabIndex(GenerationTracerView.Kind kind, SNodeReference node) {
    int index = 0;
    for (GenerationTracerView tracerView : myTracerViews) {
      if (tracerView.isViewFor(kind, node)) {
        return index;
      }
      index++;
    }
    return -1;
  }
  boolean isAutoscroll() {
    return myAutoscroll;
  }
  void autoscrollsChanged(boolean b) {
    if (myAutoscroll != b) {
      myAutoscroll = b;
      for (GenerationTracerView tracerView : myTracerViews) {
        tracerView.setAutoscrollToSource(b);
      }
    }
  }
  void close(GenerationTracerView view) {
    closeTab(myTracerViews.indexOf(view));
  }

  void showTraceView(GenerationTracerView.Kind viewToken, TraceNodeUI tracerNode, SNode node) {
    GenerationTracerView tracerView = new GenerationTracerView(this, node.getReference(), viewToken, tracerNode);
    myTracerViews.add(tracerView);
    Icon i = Icons.getIcon(tracerView.isForwardTraceView() ? TraceNodeUI.Kind.INPUT : TraceNodeUI.Kind.OUTPUT, node);
    Content content = addContent(tracerView.getComponent(), node.getPresentation(), i, true);
    getContentManager().setSelectedContent(content);

    Content noTabsContent = getContentManager().getContent(myNoTabsComponent);
    if (noTabsContent != null) {
      getContentManager().removeContent(noTabsContent, true);
    }

    openToolLater(true);
  }

  public void setTracingDataIsAvailable(final boolean dataPresent) {
    ApplicationManager.getApplication().invokeLater(() -> myNoTabsComponent.setDataIsAvailable(dataPresent));
  }

  @Override
  public Project getProject() {
    return super.getProject(); // public for GenerationTracerView
  }

  TraceNodeUI buildForwardTrace(SNode node) {
    final GenerationTrace ngt = myTransientModelsOwner.getTrace(node.getModel().getReference());
    if (ngt == null) {
      return null;
    }
    TraceNodeUI newTrace = new TraceNodeUI("New gen tracer", Icons.COLLECTION, node.getReference());
    // XXX for now, we assume template source models reside in the same repository as the transient/origin node, this in
    // not generally true. Likely shall be project repository (if different than that of transient modules) or the one with deployed languages
    for (TraceNodeUI n : TraceBuilderUI.buildTrace(ngt, node, node.getModel().getRepository())) {
      newTrace.addChild(n);
    }
    return newTrace;
  }
  TraceNodeUI buildBackwardTrace(SNode node) {
    final GenerationTrace ngt = myTransientModelsOwner.getTrace(node.getModel().getReference());
    if (ngt == null) {
      return null;
    }
    TraceNodeUI newTrace = new TraceNodeUI("New gen tracer", Icons.COLLECTION, node.getReference());
    for (TraceNodeUI n : TraceBuilderUI.buildBackTrace(ngt, node, node.getModel().getRepository())) {
      newTrace.addChild(n);
    }
    return newTrace;
  }

  public static class NoTabsComponent extends JPanel {
    JPanel myLabelsPanel = new JPanel();

    public NoTabsComponent(final GenerationTracerViewTool tool) {
      setLayout(new BorderLayout());

      JPanel mainPanel = new JPanel(new GridBagLayout());
      myLabelsPanel.setLayout(new BoxLayout(myLabelsPanel, BoxLayout.Y_AXIS));
      GridBagConstraints c = new GridBagConstraints();
      c.anchor = GridBagConstraints.CENTER;
      mainPanel.add(myLabelsPanel, c);
      add(mainPanel, BorderLayout.CENTER);

      ApplicationManager.getApplication().invokeLater(() -> {
        ActionGroup group = ActionUtils.groupFromActions(new CloseAction(tool));

        ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, false);
        add(toolbar.getComponent(), BorderLayout.WEST);
      });
    }

    public void setDataIsAvailable(boolean state) {
      myLabelsPanel.removeAll();

      String[] strings;
      if (state) {
        strings = new String[]{"Tracing data is available.", "To view trace/traceback data use generated node's popup menu."};
      } else {
        strings = new String[]{"No data available.", "To use the 'generation tracer' generate model with the 'save transient models' option."};
      }

      for (String string : strings) {
        JLabel label = new JLabel(string);
        label.setAlignmentX(Component.CENTER_ALIGNMENT);
        myLabelsPanel.add(label);
      }
    }
  }
}
