/*******************************************************************************
 *   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.benchmarking.utils.doublespace;

import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNull;

import com.analog.lyric.benchmarking.utils.functional.BinaryOp;
import com.analog.lyric.benchmarking.utils.functional.FoldFunction;
import com.analog.lyric.benchmarking.utils.functional.Functions;
import com.analog.lyric.benchmarking.utils.functional.IterFunctionWithCoordinates;
import com.analog.lyric.benchmarking.utils.functional.TransformFunction;

/**
 * Provides a skeletal implementation of the DoubleSpace interface.
 */
public abstract class AbstractDoubleSpace implements DoubleSpace
{

	private final Set<Entry> _entrySet = Collections.unmodifiableSet(new AbstractSet<Entry>()
	{

		@Override
		public Iterator<Entry> iterator()
		{
			return new EntryIterator();
		}

		@Override
		public int size()
		{
			return getCardinality();
		}

	});

	protected final JointIndexer _jointIndexer;

	private final Collection<Double> _valuesCollection = Collections.unmodifiableCollection(new AbstractCollection<Double>()
	{

		@Override
		public Iterator<Double> iterator()
		{
			return new ValueIterator();
		}

		@Override
		public int size()
		{
			return getCardinality();
		}

	});

	/**
	 * Constructor that must be invoked by derived classes.
	 * 
	 * @param indexer
	 *            The provided joint indexer describes the domain of the space.
	 */
	protected AbstractDoubleSpace(final JointIndexer indexer)
	{
		_jointIndexer = indexer;
	}

	@Override
	public DoubleSpace add(DoubleSpace rhs)
	{
		return binaryOp(rhs, Functions.add);
	}

	@Override
	public DoubleSpace binaryOp(DoubleSpace rhs, BinaryOp op)
	{
		final int dimensions = getDimensionsCount();
		if (dimensions != rhs.getDimensionsCount())
		{
			throw new IllegalArgumentException("dimensions must match.");
		}
		for (int dimension = 0; dimension < dimensions; dimension++)
		{
			if (getIndexer(dimension).getCardinality() != rhs.getIndexer(dimension).getCardinality())
			{
				throw new IllegalArgumentException("dimensions must match.");
			}
		}
		final Iterator<int[]> it_coords_this = coordinatesSet().iterator();
		final Iterator<int[]> it_coords_that = rhs.coordinatesSet().iterator();
		while (it_coords_this.hasNext() && it_coords_that.hasNext())
		{
			final int[] coords_this = it_coords_this.next();
			final int[] coords_that = it_coords_that.next();
			put(op.apply(get(coords_this), rhs.get(coords_that)), coords_this);
		}
		return this;
	}

	@Override
	public Set<int[]> coordinatesSet()
	{
		return _jointIndexer.coordinatesSet();
	}

	@Override
	public Set<Entry> entrySet()
	{
		return _entrySet;
	}

	@NonNull // FIXME - workaround for Eclipse JDT bug (467610?)
	@Override
	public <T> T fold(T init, FoldFunction<T> fn)
	{
		T acc = init;
		for (final int[] coordinates : coordinatesSet())
		{
			final double value = get(coordinates);
			acc = fn.apply(acc, value);
		}
		return acc;
	}

	@Override
	public int[] getDimensions()
	{
		final int[] orders = new int[getDimensionsCount()];
		for (int dimension = 0; dimension < orders.length; dimension++)
		{
			orders[dimension] = getIndexer(dimension).getCardinality();
		}
		return orders;
	}

	@Override
	public int getDimensionsCount()
	{
		return _jointIndexer.getDimensionsCount();
	}

	@Override
	public Indexer getIndexer(int dimension)
	{
		return _jointIndexer.getIndexer(dimension);
	}

	@Override
	public void iter(IterFunctionWithCoordinates fn)
	{
		for (final int[] coordinates : coordinatesSet())
		{
			final double value = get(coordinates);
			fn.apply(value, coordinates);
		}
	}

	@Override
	public DoubleSpace transform(TransformFunction transformer)
	{
		for (final int[] coords : coordinatesSet())
		{
			put(transformer.apply(get(coords)), coords);
		}
		return this;
	}

	@Override
	public Collection<Double> values()
	{
		return _valuesCollection;
	}

	@Override
	public DoubleSpace view(Indexer... indexers)
	{
		return new IndexedDoubleSpace(this, indexers);
	}

	private final class EntryIterator implements Iterator<Entry>
	{
		private final Iterator<int[]> _coordinatesIterator;

		public EntryIterator()
		{
			_coordinatesIterator = coordinatesSet().iterator();
		}

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

		@Override
		public Entry next()
		{
			final int[] coordinates = _coordinatesIterator.next();
			return new Entry(AbstractDoubleSpace.this, coordinates);
		}

		@Override
		public void remove()
		{
			throw new UnsupportedOperationException();
		}
	}

	private final class ValueIterator implements Iterator<Double>
	{
		private final Iterator<int[]> _coordinatesIterator;

		public ValueIterator()
		{
			_coordinatesIterator = coordinatesSet().iterator();
		}

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

		@Override
		public Double next()
		{
			final int[] coordinates = _coordinatesIterator.next();
			final double value = get(coordinates);
			return value;
		}

		@Override
		public void remove()
		{
			throw new UnsupportedOperationException();
		}
	}
}
