import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class MostlyLocalList<E> {
  private final int threadLocalAllocatorSize;
  private volatile ArrayList<E> globalList = new ArrayList<>();
  private final ThreadLocal<ArrayList<E>> threadLocalList;
  
  @SuppressWarnings("rawtypes")
  private static final AtomicReferenceFieldUpdater<MostlyLocalList, ArrayList> UPDATER =
      AtomicReferenceFieldUpdater.newUpdater(MostlyLocalList.class, ArrayList.class, "globalBuffer");
  
  public MostlyLocalList(final int threadLocalAllocatorSize) {
    if (threadLocalAllocatorSize < 1)
      throw new IllegalArgumentException("threadLocalAllocatorSize < 1");
    this.threadLocalAllocatorSize = threadLocalAllocatorSize;
    this.threadLocalList = new ThreadLocal<ArrayList<E>>() {
      @Override
      protected ArrayList<E> initialValue() {
        return new ArrayList<>(threadLocalAllocatorSize);
      }
    };
  }
  
  public void add(E element) {
    ArrayList<E> localList = threadLocalList.get();
    localList.add(element);
    if (localList.size() == threadLocalAllocatorSize) {
      flush(localList);
    }
  }
  
  private void flush(ArrayList<E> localList) {
    for(;;) {
      ArrayList<E> globalList = this.globalList;
      ArrayList<E>  newGlobalList = new ArrayList<>(localList.size() + globalList.size());
      newGlobalList.addAll(globalList);
      newGlobalList.addAll(localList);
      if (UPDATER.compareAndSet(this, globalList, newGlobalList)) {
        localList.clear();
        return;
      }
    }
  }

  public void flush() {
    flush(threadLocalList.get());
    threadLocalList.remove();
  }
  
  public Iterator<E> iterator() {
    final ArrayList<E> globaList = this.globalList;
    return new Iterator<E>() {
      private int index;
      
      @Override
      public boolean hasNext() {
        return index < globaList.size();
      }
      @Override
      public E next() {
        return globaList.get(index++);
      }
      @Override
      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }
}
