/*
 * Copyright 2003-2014 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.projectPane.logicalview.highlighting.visitor.updates;


import com.intellij.util.ui.Timer;
import jetbrains.mps.ide.ui.tree.MPSTreeNode;
import jetbrains.mps.project.Project;
import jetbrains.mps.util.Pair;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;

public final class TreeNodeUpdater {
  private final Timer myTimer;
  private final Project myProject;
  private final Semaphore myGuard = new Semaphore(1);
  private Queue<Pair<MPSTreeNode, NodeUpdate>> myUpdates = new ConcurrentLinkedQueue<Pair<MPSTreeNode, NodeUpdate>>();

  public TreeNodeUpdater(Project mpsProject) {
    myProject = mpsProject;
    myTimer = new Timer("ProjectPane Tree Update Thread", 500) {
      @Override
      protected void onTimer() throws InterruptedException {
        process();
      }
    };
    myTimer.setTakeInitialDelay(true);
  }

  final void process() {
    if (!myGuard.tryAcquire()) {
      return;
    }
    try {
      do {
        int batchProcessMax = 20; // do not process more than X at once, not to block any write actions nor UI thread for too long
        final ArrayList<Pair<MPSTreeNode, NodeUpdate>> updates = new ArrayList<Pair<MPSTreeNode, NodeUpdate>>(batchProcessMax);
        Pair<MPSTreeNode, NodeUpdate> u;
        while ((u = myUpdates.poll()) != null && batchProcessMax > 0) {
          if (u.o1.getTree() == null) {
            // no reason to update element which is not in the tree
            continue;
          }
          updates.add(u);
          batchProcessMax--;
        }
        if (updates.isEmpty()) {
          break;
        }
        myProject.getModelAccess().runReadInEDT(new Runnable() {
          @Override
          public void run() {
            final HashSet<MPSTreeNode> toRefresh = new HashSet<MPSTreeNode>();
            for (Pair<MPSTreeNode, NodeUpdate> next : updates) {
              MPSTreeNode node = next.o1;
              if (node.getTree() == null) {
                // once again, no reason to update element which is not in the tree
                continue;
              }
              next.o2.update(node);
              toRefresh.add(node);
            }
            for (MPSTreeNode node : toRefresh) {
              node.updateNodePresentationInTree();
            }
          }
        });
      } while (!myUpdates.isEmpty());
      myTimer.suspend();
    } finally {
      myGuard.release();
    }
  }

  public void addUpdate(MPSTreeNode node, NodeUpdate r) {
    if (!r.needed(node)) return;
    myUpdates.add(new Pair<MPSTreeNode, NodeUpdate>(node, r));
    myTimer.start(); // sic(!), resume() or restart() force timer into 'running' state, effectively skipping initial delay
  }
}
