/*******************************************************************************
*   Copyright 2014 Analog Devices, Inc.
*
*   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 com.analog.lyric.dimple.events;

import java.util.concurrent.atomic.AtomicReference;

import net.jcip.annotations.NotThreadSafe;

import org.eclipse.jdt.annotation.Nullable;

import com.analog.lyric.collect.UnmodifiableReleasableIterator;

/**
 * Iterates over Dimple event sources, which are also Dimple option holders.
 * <p>
 * This provides the iterator returned by both the
 * {@link com.analog.lyric.dimple.options.DimpleOptionHolder#getOptionDelegates DimpleOptionHolder.getOptionDelegates}
 * and {@link com.analog.lyric.dimple.events.DimpleEventListener#eventSources DimpleEventListener.eventSources}
 * methods. It is used for both option lookup and event dispatching.
 * <p>
 * The iterator visits objects starting from an initial <em>source</em> using the following recursive ordering:
 * <ol>
 * <li>Visit <em>source</em>
 * <li>Visit <em>source</em>'s corresponding {@linkplain IDimpleEventSource#getModelEventSource model event source}
 * if not null and not the same as the <em>source</em> itself. For instance, if <em>source</em> were a Dimple solver
 * variable, this step would visit the corresponding model variable.
 * <li>If the <em>source</em> has a non-null {@linkplain IDimpleEventSource#getEventParent() parent}, then
 * recursively visit's sources starting with the parent.
 * <li>Otherwise, if the <em>source</em> has no parent, but visited a <em>model source</em> in step 2 with
 * a non-null parent, then the algorithm will recursively visit sources from the model's parent.
 * </ol>
 * Or written in pseudocode:
 * <blockquote>
 * <pre>
 * void visitSources(IDimpleEventSource source)
 * {
 *     visit(source);
 * 
 *     IDimpleEventSource modelSource = source.getModelEventSource();
 *     boolean hasModel = modelSource != null && modelSource != source;
 *     if (hasModel)
 *         visit(modelSource);
 * 
 *     IDimpleEventSource parent = source.getEventParent();
 *     if (parent == null && hasModel)
 *         parent = model.getEventParent();
 * 
 *     if (parent != null)
 *     {
 *         visitSources(parent);
 *     }
 * }
 * </pre>
 * </blockquote>
 * <p>
 * @since 0.07
 * @author Christopher Barber
 */
@NotThreadSafe
public class EventSourceIterator extends UnmodifiableReleasableIterator<IDimpleEventSource>
{
	private @Nullable IDimpleEventSource _next;
	private @Nullable IDimpleEventSource _prev;
	
	private static final AtomicReference<EventSourceIterator> _reusableInstance = new AtomicReference<>();
	
	/*--------------
	 * Construction
	 */
	
	private EventSourceIterator()
	{
	}
	
	/**
	 * Create iterator starting with given source.
	 * <p>
	 * Most users should instead use {@link IDimpleEventSource#getOptionDelegates()}.
	 * <p>
	 * @param source if null will create an empty iterator.
	 * @since 0.07
	 */
	public static EventSourceIterator create(@Nullable IDimpleEventSource source)
	{
		EventSourceIterator iter = _reusableInstance.getAndSet(null);
		if (iter == null)
		{
			iter = new EventSourceIterator();
		}
		iter.reset(source);
		return iter;
	}
	
	/*------------------
	 * Iterator methods
	 */
	
	@Override
	public boolean hasNext()
	{
		return _next != null;
	}

	@Override
	public @Nullable IDimpleEventSource next()
	{
		IDimpleEventSource source = _next;
		
		if (source != null)
		{
			// Find next source.
			
			final IDimpleEventSource prev = _prev;
			if (prev != null)
			{
				_next = prev.getEventParent();
				_prev = null;
				if (_next == null)
				{
					// If no more parents, use parent of corresponding model object
					IModelEventSource modelSource = prev.getModelEventSource();
					if (modelSource != prev && modelSource != null)
					{
						_next = modelSource.getEventParent();
					}
				}
			}
			else
			{
				IModelEventSource modelSource = source.getModelEventSource();
				if (modelSource != source)
				{
					_next = modelSource;
					_prev = source;
				}
				else
				{
					_next = source.getEventParent();
				}
			}
		}

		return source;
	}

	/*----------------------------
	 * ReleasableIterator methods
	 */
	
	@Override
	public void release()
	{
		reset(null);
		_reusableInstance.set(this);
	}

	/**
	 * Resets the iterator to start iteration at given {@code source}.
	 * 
	 * @param source is the first source to visit. If null, the iterator will be empty.
	 * @since 0.07
	 */
	public void reset(@Nullable IDimpleEventSource source)
	{
		_next = source;
		_prev = null;
	}
	
}