/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.activemq.artemis.utils.collections;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Consumer;

public class UpdatableIterator<E> implements ResettableIterator<E>, RepeatableIterator<E> {

   private static final AtomicReferenceFieldUpdater<UpdatableIterator, RepeatableIteratorWrapper> changedIteratorFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(UpdatableIterator.class, RepeatableIteratorWrapper.class, "changedIterator");
   private volatile RepeatableIteratorWrapper<E> changedIterator;
   private RepeatableIteratorWrapper<E> currentIterator;

   public UpdatableIterator(ResettableIterator<E> iterator) {
      this.currentIterator = new RepeatableIteratorWrapper<>(iterator);
   }

   /**
    * This can be called by another thread.
    * It sets a new iterator, that will be picked up on the next reset.
    *
    * @param iterator the new iterator to update to.
    */
   public void update(ResettableIterator<E> iterator) {
      changedIteratorFieldUpdater.set(this, new RepeatableIteratorWrapper<>(iterator));
   }

   /*
    * ---- ResettableIterator Methods -----
    * All the below ResettableIterator (including reset) methods MUST be called by the same thread,
    * this is as any other use of Iterator.
    */

   /**
    * When reset is called, then if a new iterator has been provided by another thread via update method,
    * then we switch over to using the new iterator.
    *
    * It is important that on nulling off the changedIterator, we atomically compare and set as the
    * changedIterator could be further updated by another thread whilst we are resetting,
    * the subsequent update simply would be picked up on the next reset.
    */
   @Override
   public void reset() {
      RepeatableIteratorWrapper<E> changedIterator = this.changedIterator;
      if (changedIterator != null) {
         currentIterator = changedIterator;
         changedIteratorFieldUpdater.compareAndSet(this, changedIterator, null);
      }
      currentIterator.reset();
   }

   @Override
   public boolean hasNext() {
      return currentIterator.hasNext();
   }

   @Override
   public E next() {
      return currentIterator.next();
   }

   @Override
   public void remove() {
      currentIterator.remove();
   }

   @Override
   public void forEachRemaining(Consumer<? super E> action) {
      currentIterator.forEachRemaining(action);
   }

   @Override
   public void repeat() {
      currentIterator.repeat();
   }
}
