/*******************************************************************************
*   Copyright 2015 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.model.core;

import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterators;

/**
 * Holds edges for a {@link FactorGraph}
 * <p>
 * @since 0.08
 * @author Christopher Barber
 */
class EdgeStateList extends ArrayList<EdgeState>
{
	private static final long serialVersionUID = 1L;

	/*-------
	 * State
	 */
	
	private final FactorGraph _graph;
	private int _nEdges;
//	private int _nLocalEdges;
//	private int _nOuterEdges;
//	private int _nInnerEdges;
	
	/*--------------
	 * Construction
	 */
	
	EdgeStateList(FactorGraph graph)
	{
		super();
		_graph = graph;
	}
	
	/*------------------
	 * Iterable methods
	 */
	
	@Override
	public Iterator<EdgeState> iterator()
	{
		// Skip over null entries
		return Iterators.filter(super.iterator(), Predicates.notNull());
	}

	/*--------------
	 * List methods
	 */
	
	@NonNullByDefault(false)
	@Override
	public boolean add(EdgeState edge)
	{
		final int index = edge.edgeIndexInParent(_graph);
		if (index == size())
		{
			super.add(edge);
		}
		else
		{
			assert(get(index) == null);
			super.set(index, edge);
		}
		updateCounts(edge, 1);
		return true;
	}
	
	@Override
	public EdgeState set(int index, @Nullable EdgeState edge)
	{
		EdgeState oldEdge = get(index);
		if (oldEdge != null)
		{
			updateCounts(oldEdge, -1);
		}
		if (edge != null)
		{
			updateCounts(edge, 1);
		}
		return super.set(index, edge);
	}

	/*--------------------------
	 * Unsupported List methods
	 * 
	 * These should not be used because they would require edges after the
	 * removed/inserted ones to be reindexed.
	 * 
	 * We only implement them to make sure we fail in an obvious way.
	 */

	@Deprecated
	@Override
	public void add(int index, @Nullable EdgeState element)
	{
		throw unsupported();
	}

	@Deprecated
	@Override
	public boolean addAll(int index, @Nullable Collection<? extends EdgeState> c)
	{
		throw unsupported();
	}
	
	@Deprecated
	@Override
	public EdgeState remove(int index)
	{
		throw unsupported();
	}

	@Deprecated
	@Override
	public boolean remove(@Nullable Object o)
	{
		throw unsupported();
	}

	@Deprecated
	@Override
	public boolean removeAll(@Nullable Collection<?> c)
	{
		throw unsupported();
	}

	@Override
	public boolean retainAll(@Nullable Collection<?> c)
	{
		throw unsupported();
	}
	
	/*-----------------------
	 * EdgeStateList methods
	 */
	
	int allocateIndex()
	{
		int index = size();
		super.add(null);
		return index;
	}
	
	boolean isInnerEdge(EdgeState edge)
	{
		return !edge.isLocal() && edge.getFactorParent(_graph) != _graph;
	}
	
	boolean isOuterEdge(EdgeState edge)
	{
		return !edge.isLocal() && edge.getFactorParent(_graph) == _graph;
	}
	
	int nEdges()
	{
		return _nEdges;
	}
	
//	int nLocalEdges()
//	{
//		return _nLocalEdges;
//	}
//
//	int nOuterEdges()
//	{
//		return _nOuterEdges;
//	}
//
//	int nInnerEdges()
//	{
//		return _nInnerEdges;
//	}
	
	private class EdgeStateCollection extends AbstractCollection<EdgeState>
	{
		private final int _size;
		private final Predicate<? super EdgeState> _predicate;
		
		EdgeStateCollection(int size, Predicate<? super EdgeState> predicate)
		{
			_size = size;
			_predicate = predicate;
		}

		@Override
		public Iterator<EdgeState> iterator()
		{
			return Iterators.filter(EdgeStateList.super.iterator(), _predicate);
		}
		
		@Override
		public int size()
		{
			return _size;
		}
	}

	private class EdgeCollection extends AbstractCollection<Edge>
	{
		private final int _size;
		private final Predicate<? super EdgeState> _predicate;
		
		EdgeCollection(int size, Predicate<? super EdgeState> predicate)
		{
			_size = size;
			_predicate = predicate;
		}

		@Override
		public Iterator<Edge> iterator()
		{
			return new Iterator<Edge> () {

				private final Iterator<EdgeState> _iter = Iterators.filter(EdgeStateList.super.iterator(), _predicate);
				
				@Override
				public boolean hasNext()
				{
					return _iter.hasNext();
				}

				@Override
				public Edge next()
				{
					return new Edge(_graph, _iter.next());
				}

				@Override
				public void remove()
				{
					throw unsupported();
				}
			};
		}
		
		@Override
		public int size()
		{
			return _size;
		}
	}
	
	/**
	 * Returns collection of non-null entries.
	 * @since 0.08
	 */
	Collection<EdgeState> allEdgeState()
	{
		return new EdgeStateCollection(_nEdges, Predicates.notNull());
	}

	Collection<Edge> allEdges()
	{
		return new EdgeCollection(_nEdges, Predicates.notNull());
	}
	
	/*-----------------
	 * Private methods
	 */
	private void updateCounts(EdgeState edge, int increment)
	{
		_nEdges += increment;
//		switch (edge.type(_graph))
//		{
//		case LOCAL:
//			_nLocalEdges += increment;
//			break;
//		case OUTER:
//			_nOuterEdges += increment;
//			break;
//		case INNER:
//			_nInnerEdges += increment;
//			break;
//		}
	}

	private UnsupportedOperationException unsupported()
	{
		return new UnsupportedOperationException();
	}
}
