/*
 * Copyright 2012-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.jetpad.model.event;


import jetbrains.jetpad.base.Registration;
import jetbrains.jetpad.base.ThrowableHandlers;

import java.util.ArrayList;
import java.util.List;

/**
 * Reusable container for listeners.
 * It supports:
 * - managing listeners
 * - firing events
 */
public class Listeners<ListenerT> {
  private List<Object> myListeners = null;
  private int myFireDepth;
  private int myListenersCount;

  public boolean isEmpty() {
    return myListeners == null || myListeners.isEmpty();
  }

  public Registration add(final ListenerT l) {
    if (isEmpty()) {
      beforeFirstAdded();
    }

    if (myFireDepth > 0) {
      myListeners.add(new ListenerOp<>(l, true));
    } else {
      if (myListeners == null) {
        myListeners = new ArrayList<>(1);
      }
      myListeners.add(l);
      myListenersCount++;
    }
    return new Registration() {
      @Override
      protected void doRemove() {
        if (myFireDepth > 0) {
          myListeners.add(new ListenerOp<>(l, false));
        } else {
          myListeners.remove(l);
          myListenersCount--;
        }

        if (isEmpty()) {
          afterLastRemoved();
        }
      }
    };
  }

  public void fire(final ListenerCaller<ListenerT> h) {
    if (isEmpty()) return;
    beforeFire();
    //exception can be thrown from ThrowableHandlers.handle()
    try {
      int size = myListenersCount;
      for (int i = 0; i < size; i++) {
        ListenerT l = (ListenerT) myListeners.get(i);

        if (isRemoved(l)) continue;

        try {
          h.call(l);
        } catch (Throwable t) {
          ThrowableHandlers.handle(t);
        }
      }
    } finally {
      afterFire();
    }
  }

  private boolean isRemoved(ListenerT l) {
    int size = myListeners.size();
    for (int i = myListenersCount; i < size; i++) {
      ListenerOp<ListenerT> op = (ListenerOp<ListenerT>) myListeners.get(i);
      if (!op.add && op.listener == l) return true;
    }
    return false;
  }

  protected void beforeFirstAdded() {
  }

  protected void afterLastRemoved() {
  }

  private void beforeFire() {
    myFireDepth++;
  }

  private void afterFire() {
    myFireDepth--;
    if (myFireDepth == 0) {
      List<Object> opsList = myListeners.subList(myListenersCount, myListeners.size());
      Object[] ops = opsList.toArray();
      opsList.clear();
      for (Object o : ops) {
        ListenerOp<ListenerT> op = (ListenerOp<ListenerT>) o;
        if (op.add) {
          myListeners.add(op.listener);
          myListenersCount++;
        } else {
          myListeners.remove(op.listener);
          myListenersCount--;
        }
      }
    }
  }

  int size() {
    return myListeners == null ? 0 : myListeners.size();
  }

  private static class ListenerOp<ListenerT> {
    final ListenerT listener;
    final boolean add;

    ListenerOp(ListenerT listener, boolean add) {
      this.listener = listener;
      this.add = add;
    }
  }
}
@@ -755,16 +755,43 @@
 ayList;%0A
+import java.util.Iterator;%0A
 import j
@@ -1925,345 +1925,915 @@
     
-//exception can be thrown from ThrowableHandlers.handle()%0A    try %7B%0A      int size = myListenersCount;%0A      for (int i = 0; i %3C size; i++) %7B%0A        ListenerT l = (ListenerT) myListeners.get(i);%0A%0A        if (isRemoved(l)) continue;%0A%0A        try %7B%0A          h.call(l);%0A        %7D catch (Throwable t) %7B%0A          ThrowableHandlers.handle(t
+try %7B%0A      Iterator%3CListenerT%3E it = listenersIterator();%0A      while (it.hasNext()) %7B%0A        ListenerT l = it.next();%0A        try %7B%0A          h.call(l);%0A        %7D catch (Throwable t) %7B%0A          ThrowableHandlers.handle(t);%0A        %7D%0A      %7D%0A    %7D finally %7B%0A      afterFire();%0A    %7D%0A  %7D%0A%0A  private Iterator%3CListenerT%3E listenersIterator() %7B%0A    final int size = myListenersCount;%0A    return new Iterator%3CListenerT%3E() %7B%0A      private int myIndex = 0;%0A%0A      private void moveToNext() %7B%0A        while (myIndex %3C size && isRemoved((ListenerT) myListeners.get(myIndex))) %7B%0A          myIndex++;%0A        %7D%0A      %7D%0A%0A      @Override%0A      public boolean hasNext() %7B%0A        moveToNext();%0A        return myIndex %3C size;%0A      %7D%0A%0A      @Override%0A      public ListenerT next() %7B%0A        if (myIndex %3E= size) %7B%0A          throw new IllegalStateException();%0A        %7D%0A        return (ListenerT) myListeners.get(myIndex++
 );%0A 
@@ -2841,60 +2841,121 @@
     
-  
 %7D
+%0A
 %0A      
-%7D%0A    %7D finally %7B%0A      afterFire();%0A    %7D
+@Override%0A      public void remove() %7B%0A        throw new UnsupportedOperationException();%0A      %7D%0A    %7D;
 %0A  %7D

introduce listenersIterator