package com.intellij.vcs.log.graphmodel.impl;

import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.vcs.log.VcsCommit;
import com.intellij.vcs.log.VcsRef;
import com.intellij.vcs.log.compressedlist.UpdateRequest;
import com.intellij.vcs.log.graph.Graph;
import com.intellij.vcs.log.graph.elements.Node;
import com.intellij.vcs.log.graph.mutable.GraphAppendBuilder;
import com.intellij.vcs.log.graph.mutable.MutableGraph;
import com.intellij.vcs.log.graphmodel.FragmentManager;
import com.intellij.vcs.log.graphmodel.GraphModel;
import com.intellij.vcs.log.graphmodel.fragment.FragmentManagerImpl;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
 * @author erokhins
 */
public class GraphModelImpl implements GraphModel {
  private final MutableGraph graph;
  private final Collection<VcsRef> myRefs;
  private final FragmentManagerImpl fragmentManager;
  private final BranchVisibleNodes visibleNodes;
  private final List<Consumer<UpdateRequest>> listeners = new ArrayList<Consumer<UpdateRequest>>();
  private final GraphBranchShowFixer branchShowFixer;

  private Function<Node, Boolean> isStartedBranchVisibilityNode = new Function<Node, Boolean>() {
    @NotNull
    @Override
    public Boolean fun(@NotNull Node key) {
      return true;
    }
  };

  public GraphModelImpl(MutableGraph graph, Collection<VcsRef> allRefs) {
    this.graph = graph;
    myRefs = allRefs;
    this.fragmentManager = new FragmentManagerImpl(graph, new FragmentManagerImpl.CallBackFunction() {
      @Override
      public UpdateRequest runIntermediateUpdate(@NotNull Node upNode, @NotNull Node downNode) {
        return GraphModelImpl.this.updateIntermediate(upNode, downNode);
      }

      @Override
      public void fullUpdate() {
        GraphModelImpl.this.fullUpdate();
      }
    });
    this.visibleNodes = new BranchVisibleNodes(graph);
    visibleNodes.setVisibleNodes(visibleNodes.generateVisibleBranchesNodes(isStartedBranchVisibilityNode));
    branchShowFixer = new GraphBranchShowFixer(graph, fragmentManager);
    graph.setGraphDecorator(new GraphDecoratorImpl(fragmentManager.getGraphPreDecorator(), new Function<Node, Boolean>() {
      @NotNull
      @Override
      public Boolean fun(@NotNull Node key) {
        return visibleNodes.isVisibleNode(key);
      }
    }));
    graph.updateVisibleRows();
  }

  @NotNull
  private UpdateRequest updateIntermediate(@NotNull Node upNode, @NotNull Node downNode) {
    int upRowIndex = upNode.getRowIndex();
    int downRowIndex = downNode.getRowIndex();
    graph.updateVisibleRows();

    UpdateRequest updateRequest = UpdateRequest.buildFromToInterval(upRowIndex, downRowIndex, upNode.getRowIndex(), downNode.getRowIndex());
    callUpdateListener(updateRequest);
    return updateRequest;
  }

  private void fullUpdate() {
    int oldSize = graph.getNodeRows().size();
    graph.updateVisibleRows();
    UpdateRequest updateRequest = UpdateRequest.buildFromToInterval(0, oldSize - 1, 0, graph.getNodeRows().size() - 1);
    callUpdateListener(updateRequest);
  }

  @NotNull
  @Override
  public Graph getGraph() {
    return graph;
  }

  @Override
  public void appendCommitsToGraph(@NotNull List<? extends VcsCommit> commitParentses) {
    int oldSize = graph.getNodeRows().size();
    new GraphAppendBuilder(graph, myRefs).appendToGraph(commitParentses);
    visibleNodes.setVisibleNodes(visibleNodes.generateVisibleBranchesNodes(isStartedBranchVisibilityNode));
    graph.updateVisibleRows();

    UpdateRequest updateRequest = UpdateRequest.buildFromToInterval(0, oldSize - 1, 0, graph.getNodeRows().size() - 1);
    callUpdateListener(updateRequest);
  }

  @Override
  public void setVisibleBranchesNodes(@NotNull Function<Node, Boolean> isStartedNode) {
    this.isStartedBranchVisibilityNode = isStartedNode;
    Set<Node> prevVisibleNodes = visibleNodes.getVisibleNodes();
    Set<Node> newVisibleNodes = visibleNodes.generateVisibleBranchesNodes(isStartedNode);
    branchShowFixer.fixCrashBranches(prevVisibleNodes, newVisibleNodes);
    visibleNodes.setVisibleNodes(newVisibleNodes);
    fullUpdate();
  }

  @NotNull
  @Override
  public FragmentManager getFragmentManager() {
    return fragmentManager;
  }

  private void callUpdateListener(@NotNull UpdateRequest updateRequest) {
    for (Consumer<UpdateRequest> listener : listeners) {
      listener.consume(updateRequest);
    }
  }

  @Override
  public void addUpdateListener(@NotNull Consumer<UpdateRequest> listener) {
    listeners.add(listener);
  }

  @Override
  public void removeAllListeners() {
    listeners.clear();
  }
}

@@ -2987,24 +2987,167 @@
 sibleRows();
+%0A%0A    int newSize = graph.getNodeRows().size();%0A    int newTo = newSize == 0 ? 0 : newSize - 1;%0A    int oldTo = oldSize == 0 ? 0 : oldSize - 1;
 %0A    UpdateR
@@ -3213,51 +3213,20 @@
  old
-Size - 1, 0, graph.getNodeRows().size() - 1
+To, 0, newTo
 );%0A 

Support empty log (which can happen after filtering)