/*
 * Copyright 2000-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 com.intellij.ide.errorTreeView;

import com.intellij.icons.AllIcons;
import com.intellij.ide.*;
import com.intellij.ide.actions.*;
import com.intellij.ide.errorTreeView.impl.ErrorTreeViewConfiguration;
import com.intellij.ide.errorTreeView.impl.ErrorViewTextExporter;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.ui.AutoScrollToSourceHandler;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.SideBorder;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.MessageView;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.Alarm;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.MutableErrorTreeView;
import com.intellij.util.ui.StatusText;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Collections;
import java.util.List;

public class NewErrorTreeViewPanel extends JPanel implements DataProvider, OccurenceNavigator, MutableErrorTreeView, CopyProvider {
  protected static final Logger LOG = Logger.getInstance("#com.intellij.ide.errorTreeView.NewErrorTreeViewPanel");
  private volatile String myProgressText = "";
  private volatile float myFraction;
  private final boolean myCreateExitAction;
  private final ErrorViewStructure myErrorViewStructure;
  private final ErrorViewTreeBuilder myBuilder;
  private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
  private volatile boolean myIsDisposed;
  private final ErrorTreeViewConfiguration myConfiguration;

  public interface ProcessController {
    void stopProcess();

    boolean isProcessStopped();
  }

  private ActionToolbar myLeftToolbar;
  private ActionToolbar myRightToolbar;
  private final TreeExpander myTreeExpander = new MyTreeExpander();
  private final ExporterToTextFile myExporterToTextFile;
  protected Project myProject;
  private final String myHelpId;
  protected Tree myTree;
  private final JPanel myMessagePanel;
  private ProcessController myProcessController;

  private JLabel myProgressLabel;
  private JPanel myProgressPanel;

  private final AutoScrollToSourceHandler myAutoScrollToSourceHandler;
  private final MyOccurrenceNavigatorSupport myOccurrenceNavigatorSupport;

  public NewErrorTreeViewPanel(Project project, String helpId) {
    this(project, helpId, true);
  }

  public NewErrorTreeViewPanel(Project project, String helpId, boolean createExitAction) {
    this(project, helpId, createExitAction, true);
  }

  public NewErrorTreeViewPanel(Project project, String helpId, boolean createExitAction, boolean createToolbar) {
    this(project, helpId, createExitAction, createToolbar, null);
  }

  public NewErrorTreeViewPanel(Project project, String helpId, boolean createExitAction, boolean createToolbar, @Nullable Runnable rerunAction) {
    myProject = project;
    myHelpId = helpId;
    myCreateExitAction = createExitAction;
    myConfiguration = ErrorTreeViewConfiguration.getInstance(project);
    setLayout(new BorderLayout());

    myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() {
      @Override
      protected boolean isAutoScrollMode() {
        return myConfiguration.isAutoscrollToSource();
      }

      @Override
      protected void setAutoScrollMode(boolean state) {
        myConfiguration.setAutoscrollToSource(state);
      }
    };

    myMessagePanel = new JPanel(new BorderLayout());

    myErrorViewStructure = createErrorViewStructure(project, canHideWarnings());
    DefaultMutableTreeNode root = new DefaultMutableTreeNode();
    root.setUserObject(myErrorViewStructure.createDescriptor(myErrorViewStructure.getRootElement(), null));
    final DefaultTreeModel treeModel = new DefaultTreeModel(root);
    myTree = createTree(treeModel);
    myTree.getEmptyText().setText(IdeBundle.message("errortree.noMessages"));
    myBuilder = new ErrorViewTreeBuilder(myTree, treeModel, myErrorViewStructure);

    myExporterToTextFile = new ErrorViewTextExporter(myErrorViewStructure);
    myOccurrenceNavigatorSupport = new MyOccurrenceNavigatorSupport(myTree);

    myAutoScrollToSourceHandler.install(myTree);
    TreeUtil.installActions(myTree);
    UIUtil.setLineStyleAngled(myTree);
    myTree.setRootVisible(false);
    myTree.setShowsRootHandles(true);
    myTree.setLargeModel(true);

    JScrollPane scrollPane = NewErrorTreeRenderer.install(myTree);
    scrollPane.setBorder(IdeBorderFactory.createBorder(SideBorder.LEFT));
    myMessagePanel.add(scrollPane, BorderLayout.CENTER);

    if (createToolbar) {
      add(createToolbarPanel(rerunAction), BorderLayout.WEST);
    }

    add(myMessagePanel, BorderLayout.CENTER);

    myTree.addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
          navigateToSource(false);
        }
      }
    });

    myTree.addMouseListener(new PopupHandler() {
      @Override
      public void invokePopup(Component comp, int x, int y) {
        popupInvoked(comp, x, y);
      }
    });

    EditSourceOnDoubleClickHandler.install(myTree);
  }

  @NotNull
  protected Tree createTree(@NotNull final DefaultTreeModel treeModel) {
    return new Tree(treeModel) {
      @Override
      public void setRowHeight(int i) {
        super.setRowHeight(0);
        // this is needed in order to make UI calculate the height for each particular row
      }
    };
  }

  protected ErrorViewStructure createErrorViewStructure(Project project, boolean canHideWarnings) {
    return new ErrorViewStructure(project, canHideWarnings);
  }

  @Override
  public void dispose() {
    myIsDisposed = true;
    myErrorViewStructure.clear();
    myUpdateAlarm.cancelAllRequests();
    Disposer.dispose(myUpdateAlarm);
    Disposer.dispose(myBuilder);
  }

  @Override
  public void performCopy(@NotNull DataContext dataContext) {
    List<ErrorTreeNodeDescriptor> descriptors = getSelectedNodeDescriptors();
    if (!descriptors.isEmpty()) {
      CopyPasteManager.getInstance().setContents(new StringSelection(StringUtil.join(descriptors, descriptor -> {
        ErrorTreeElement element = descriptor.getElement();
        return NewErrorTreeRenderer.calcPrefix(element) + StringUtil.join(element.getText(), "\n");
      }, "\n")));
    }
  }

  @Override
  public boolean isCopyEnabled(@NotNull DataContext dataContext) {
    return !getSelectedNodeDescriptors().isEmpty();
  }

  @Override
  public boolean isCopyVisible(@NotNull DataContext dataContext) {
    return true;
  }
  
  @NotNull public StatusText getEmptyText() {
    return myTree.getEmptyText();
  } 

  @Override
  public Object getData(String dataId) {
    if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
      return this;
    }
    if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
      final NavigatableErrorTreeElement selectedMessageElement = getSelectedNavigatableElement();
      return selectedMessageElement != null ? selectedMessageElement.getNavigatable() : null;
    }
    else if (PlatformDataKeys.HELP_ID.is(dataId)) {
      return myHelpId;
    }
    else if (PlatformDataKeys.TREE_EXPANDER.is(dataId)) {
      return myTreeExpander;
    }
    else if (PlatformDataKeys.EXPORTER_TO_TEXT_FILE.is(dataId)) {
      return myExporterToTextFile;
    }
    else if (CURRENT_EXCEPTION_DATA_KEY.is(dataId)) {
      ErrorTreeElement selectedMessageElement = getSelectedErrorTreeElement();
      return selectedMessageElement != null ? selectedMessageElement.getData() : null;
    }
    return null;
  }

  public void selectFirstMessage() {
    final ErrorTreeElement firstError = myErrorViewStructure.getFirstMessage(ErrorTreeElementKind.ERROR);
    if (firstError != null) {
      selectElement(firstError, () -> {
        if (shouldShowFirstErrorInEditor()) {
          navigateToSource(false);
        }
      });
    }
    else {
      ErrorTreeElement firstWarning = myErrorViewStructure.getFirstMessage(ErrorTreeElementKind.WARNING);
      if (firstWarning == null) firstWarning = myErrorViewStructure.getFirstMessage(ErrorTreeElementKind.NOTE);

      if (firstWarning != null) {
        selectElement(firstWarning, null);
      }
      else {
        TreeUtil.selectFirstNode(myTree);
      }
    }
  }

  private void selectElement(final ErrorTreeElement element, final Runnable onDone) {
    myBuilder.select(element, onDone);
  }

  protected boolean shouldShowFirstErrorInEditor() {
    return false;
  }

  public void updateTree() {
    if (!myIsDisposed) {
      myBuilder.updateTree();
    }
  }

  @Override
  public void addMessage(int type, @NotNull String[] text, @Nullable VirtualFile file, int line, int column, @Nullable Object data) {
    addMessage(type, text, null, file, line, column, data);
  }

  @Override
  public void addMessage(int type,
                         @NotNull String[] text,
                         @Nullable VirtualFile underFileGroup,
                         @Nullable VirtualFile file,
                         int line,
                         int column,
                         @Nullable Object data) {
    if (myIsDisposed) {
      return;
    }
    myErrorViewStructure.addMessage(ErrorTreeElementKind.convertMessageFromCompilerErrorType(type), text, underFileGroup, file, line, column, data);
    myBuilder.updateTree();
  }

  @Override
  public void addMessage(int type,
                         @NotNull String[] text,
                         @Nullable String groupName,
                         @NotNull Navigatable navigatable,
                         @Nullable String exportTextPrefix,
                         @Nullable String rendererTextPrefix,
                         @Nullable Object data) {
    if (myIsDisposed) {
      return;
    }
    VirtualFile file = data instanceof VirtualFile ? (VirtualFile)data : null;
    if (file == null && navigatable instanceof OpenFileDescriptor) {
      file = ((OpenFileDescriptor)navigatable).getFile();
    }
    final String exportPrefix = exportTextPrefix == null ? "" : exportTextPrefix;
    final String renderPrefix = rendererTextPrefix == null ? "" : rendererTextPrefix;
    final ErrorTreeElementKind kind = ErrorTreeElementKind.convertMessageFromCompilerErrorType(type);
    myErrorViewStructure.addNavigatableMessage(groupName, navigatable, kind, text, data, exportPrefix, renderPrefix, file);
    myBuilder.updateTree();
  }

  public ErrorViewStructure getErrorViewStructure() {
    return myErrorViewStructure;
  }

  public static String createExportPrefix(int line) {
    return line < 0 ? "" : IdeBundle.message("errortree.prefix.line", line);
  }

  public static String createRendererPrefix(int line, int column) {
    if (line < 0) return "";
    if (column < 0) return "(" + line + ")";
    return "(" + line + ", " + column + ")";
  }

  @Override
  @NotNull
  public JComponent getComponent() {
    return this;
  }

  @Nullable
  private NavigatableErrorTreeElement getSelectedNavigatableElement() {
    final ErrorTreeElement selectedElement = getSelectedErrorTreeElement();
    return selectedElement instanceof NavigatableErrorTreeElement ? (NavigatableErrorTreeElement)selectedElement : null;
  }

  @Nullable
  public ErrorTreeElement getSelectedErrorTreeElement() {
    final ErrorTreeNodeDescriptor treeNodeDescriptor = getSelectedNodeDescriptor();
    return treeNodeDescriptor == null? null : treeNodeDescriptor.getElement();
  }

  @Nullable
  public ErrorTreeNodeDescriptor getSelectedNodeDescriptor() {
    List<ErrorTreeNodeDescriptor> descriptors = getSelectedNodeDescriptors();
    return descriptors.size() == 1 ? descriptors.get(0) : null;
  }

  private List<ErrorTreeNodeDescriptor> getSelectedNodeDescriptors() {
    TreePath[] paths = myTree.getSelectionPaths();
    if (paths == null) {
      return Collections.emptyList();
    }
    List<ErrorTreeNodeDescriptor> result = ContainerUtil.newArrayList();
    for (TreePath path : paths) {
      DefaultMutableTreeNode lastPathNode = (DefaultMutableTreeNode)path.getLastPathComponent();
      Object userObject = lastPathNode.getUserObject();
      if (userObject instanceof ErrorTreeNodeDescriptor) {
        result.add((ErrorTreeNodeDescriptor)userObject);
      }
    }
    return result;
  }

  private void navigateToSource(final boolean focusEditor) {
    NavigatableErrorTreeElement element = getSelectedNavigatableElement();
    if (element == null) {
      return;
    }
    final Navigatable navigatable = element.getNavigatable();
    if (navigatable.canNavigate()) {
      navigatable.navigate(focusEditor);
    }
  }

  public static String getQualifiedName(final VirtualFile file) {
    return file.getPresentableUrl();
  }

  private void popupInvoked(Component component, int x, int y) {
    final TreePath path = myTree.getLeadSelectionPath();
    if (path == null) {
      return;
    }
    DefaultActionGroup group = new DefaultActionGroup();
    if (getData(CommonDataKeys.NAVIGATABLE.getName()) != null) {
      group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE));
    }
    group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_COPY));
    addExtraPopupMenuActions(group);

    ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.COMPILER_MESSAGES_POPUP, group);
    menu.getComponent().show(component, x, y);
  }

  protected void addExtraPopupMenuActions(DefaultActionGroup group) {
  }

  public void setProcessController(ProcessController controller) {
    myProcessController = controller;
  }

  public void stopProcess() {
    myProcessController.stopProcess();
  }

  public boolean canControlProcess() {
    return myProcessController != null;
  }

  public boolean isProcessStopped() {
    return myProcessController.isProcessStopped();
  }

  public void close() {
    MessageView messageView = MessageView.SERVICE.getInstance(myProject);
    Content content = messageView.getContentManager().getContent(this);
    if (content != null) {
      messageView.getContentManager().removeContent(content, true);
    }
  }
  
  public void setProgress(final String s, float fraction) {
    initProgressPanel();
    myProgressText = s;
    myFraction = fraction;
    updateProgress();
  }

  public void setProgressText(final String s) {
    initProgressPanel();
    myProgressText = s;
    updateProgress();
  }
  
  public void setFraction(final float fraction) {
    initProgressPanel();
    myFraction = fraction;
    updateProgress();
  }

  public void clearProgressData() {
    if (myProgressPanel != null) {
      myProgressText = " ";
      myFraction = 0.0f;
      updateProgress();
    }
  }

  private void updateProgress() {
    if (myIsDisposed) {
      return;
    }
    myUpdateAlarm.cancelAllRequests();
    myUpdateAlarm.addRequest(() -> {
      final float fraction = myFraction;
      final String text = myProgressText;
      if (fraction > 0.0f) {
        myProgressLabel.setText((int)(fraction * 100 + 0.5) + "%  " + text);
      }
      else {
        myProgressLabel.setText(text);
      }
    }, 50, ModalityState.NON_MODAL);

  }

  
  private void initProgressPanel() {
    if (myProgressPanel == null) {
      myProgressPanel = new JPanel(new GridLayout(1, 2));
      myProgressLabel = new JLabel();
      myProgressPanel.add(myProgressLabel);
      //JLabel secondLabel = new JLabel();
      //myProgressPanel.add(secondLabel);
      myMessagePanel.add(myProgressPanel, BorderLayout.SOUTH);
      myMessagePanel.validate();
    }
  }

  public void collapseAll() {
    TreeUtil.collapseAll(myTree, 2);
  }


  public void expandAll() {
    TreePath[] selectionPaths = myTree.getSelectionPaths();
    TreePath leadSelectionPath = myTree.getLeadSelectionPath();
    int row = 0;
    while (row < myTree.getRowCount()) {
      myTree.expandRow(row);
      row++;
    }

    if (selectionPaths != null) {
      // restore selection
      myTree.setSelectionPaths(selectionPaths);
    }
    if (leadSelectionPath != null) {
      // scroll to lead selection path
      myTree.scrollPathToVisible(leadSelectionPath);
    }
  }

  private JPanel createToolbarPanel(@Nullable Runnable rerunAction) {
    AnAction closeMessageViewAction = new CloseTabToolbarAction() {
      @Override
      public void actionPerformed(AnActionEvent e) {
        close();
      }
    };

    DefaultActionGroup leftUpdateableActionGroup = new DefaultActionGroup();
    if (rerunAction != null) {
      leftUpdateableActionGroup.add(new RerunAction(rerunAction, closeMessageViewAction));
    }

    leftUpdateableActionGroup.add(new StopAction());
    if (myCreateExitAction) {
      leftUpdateableActionGroup.add(closeMessageViewAction);
    }
    leftUpdateableActionGroup.add(new PreviousOccurenceToolbarAction(this));
    leftUpdateableActionGroup.add(new NextOccurenceToolbarAction(this));
    leftUpdateableActionGroup.add(new ExportToTextFileToolbarAction(myExporterToTextFile));
    leftUpdateableActionGroup.add(new ContextHelpAction(myHelpId));

    DefaultActionGroup rightUpdateableActionGroup = new DefaultActionGroup();
    fillRightToolbarGroup(rightUpdateableActionGroup);

    JPanel toolbarPanel = new JPanel(new BorderLayout());
    ActionManager actionManager = ActionManager.getInstance();
    myLeftToolbar = actionManager.createActionToolbar(ActionPlaces.COMPILER_MESSAGES_TOOLBAR, leftUpdateableActionGroup, false);
    myRightToolbar = actionManager.createActionToolbar(ActionPlaces.COMPILER_MESSAGES_TOOLBAR, rightUpdateableActionGroup, false);
    toolbarPanel.add(myLeftToolbar.getComponent(), BorderLayout.WEST);
    toolbarPanel.add(myRightToolbar.getComponent(), BorderLayout.CENTER);

    return toolbarPanel;
  }

  protected void fillRightToolbarGroup(DefaultActionGroup group) {
    group.add(CommonActionsManager.getInstance().createExpandAllAction(myTreeExpander, this));
    group.add(CommonActionsManager.getInstance().createCollapseAllAction(myTreeExpander, this));
    if (canHideWarnings()) {
      group.add(new HideWarningsAction());
    }
    group.add(myAutoScrollToSourceHandler.createToggleAction());
  }

  @Override
  public OccurenceInfo goNextOccurence() {
    return myOccurrenceNavigatorSupport.goNextOccurence();
  }

  @Override
  public OccurenceInfo goPreviousOccurence() {
    return myOccurrenceNavigatorSupport.goPreviousOccurence();
  }

  @Override
  public boolean hasNextOccurence() {
    return myOccurrenceNavigatorSupport.hasNextOccurence();
  }

  @Override
  public boolean hasPreviousOccurence() {
    return myOccurrenceNavigatorSupport.hasPreviousOccurence();
  }

  @Override
  public String getNextOccurenceActionName() {
    return myOccurrenceNavigatorSupport.getNextOccurenceActionName();
  }

  @Override
  public String getPreviousOccurenceActionName() {
    return myOccurrenceNavigatorSupport.getPreviousOccurenceActionName();
  }

  private class RerunAction extends DumbAwareAction {
    private final Runnable myRerunAction;
    private final AnAction myCloseAction;

    public RerunAction(@NotNull Runnable rerunAction, @NotNull AnAction closeAction) {
      super(IdeBundle.message("action.refresh"), null, AllIcons.Actions.Rerun);
      myRerunAction = rerunAction;
      myCloseAction = closeAction;
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      myCloseAction.actionPerformed(e);
      myRerunAction.run();
    }

    @Override
    public void update(AnActionEvent event) {
      final Presentation presentation = event.getPresentation();
      presentation.setEnabled(canControlProcess() && isProcessStopped());
    }
  }

  private class StopAction extends DumbAwareAction {
    public StopAction() {
      super(IdeBundle.message("action.stop"), null, AllIcons.Actions.Suspend);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      if (canControlProcess()) {
        stopProcess();
      }
      myLeftToolbar.updateActionsImmediately();
      myRightToolbar.updateActionsImmediately();
    }

    @Override
    public void update(AnActionEvent event) {
      Presentation presentation = event.getPresentation();
      presentation.setEnabled(canControlProcess() && !isProcessStopped());
      presentation.setVisible(canControlProcess());
    }
  }

  protected boolean canHideWarnings() {
    return true;
  }

  private class HideWarningsAction extends ToggleAction implements DumbAware {
    public HideWarningsAction() {
      super(IdeBundle.message("action.hide.warnings"), null, AllIcons.General.HideWarnings);
    }

    @Override
    public boolean isSelected(AnActionEvent event) {
      return isHideWarnings();
    }

    @Override
    public void setSelected(AnActionEvent event, boolean flag) {
      if (isHideWarnings() != flag) {
        myConfiguration.setHideWarnings(flag);
        myBuilder.updateTree();
      }
    }
  }

  public boolean isHideWarnings() {
    return myConfiguration.isHideWarnings();
  }

  private class MyTreeExpander implements TreeExpander {
    @Override
    public void expandAll() {
      NewErrorTreeViewPanel.this.expandAll();
    }

    @Override
    public boolean canExpand() {
      return true;
    }

    @Override
    public void collapseAll() {
      NewErrorTreeViewPanel.this.collapseAll();
    }

    @Override
    public boolean canCollapse() {
      return true;
    }
  }

  private static class MyOccurrenceNavigatorSupport extends OccurenceNavigatorSupport {
    public MyOccurrenceNavigatorSupport(final Tree tree) {
      super(tree);
    }

    @Override
    protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
      Object userObject = node.getUserObject();
      if (!(userObject instanceof ErrorTreeNodeDescriptor)) {
        return null;
      }
      final ErrorTreeNodeDescriptor descriptor = (ErrorTreeNodeDescriptor)userObject;
      final ErrorTreeElement element = descriptor.getElement();
      if (element instanceof NavigatableErrorTreeElement) {
        return ((NavigatableErrorTreeElement)element).getNavigatable();
      }
      return null;
    }

    @Override
    public String getNextOccurenceActionName() {
      return IdeBundle.message("action.next.message");
    }

    @Override
    public String getPreviousOccurenceActionName() {
      return IdeBundle.message("action.previous.message");
    }
  }

  @Override
  public List<Object> getGroupChildrenData(final String groupName) {
    return myErrorViewStructure.getGroupChildrenData(groupName);
  }

  @Override
  public void removeGroup(final String name) {
    myErrorViewStructure.removeGroup(name);
  }

  @Override
  public void addFixedHotfixGroup(String text, List<SimpleErrorData> children) {
    myErrorViewStructure.addFixedHotfixGroup(text, children);
  }

  @Override
  public void addHotfixGroup(HotfixData hotfixData, List<SimpleErrorData> children) {
    myErrorViewStructure.addHotfixGroup(hotfixData, children, this);
  }

  @Override
  public void reload() {
    myBuilder.updateTree();
  }
}
