/**
 * License: GPL
 * <p>
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 * License 2 as published by the Free Software Foundation.
 * <p>
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 * <p>
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package net.imglib2.util;

import java.util.Arrays;
import java.util.stream.IntStream;

import gnu.trove.set.TLongSet;
import gnu.trove.set.hash.TLongHashSet;
import net.imglib2.AbstractWrappedInterval;
import net.imglib2.Interval;
import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessible;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.converter.AbstractConvertedRandomAccess;
import net.imglib2.img.array.ArrayImg;
import net.imglib2.img.array.ArrayImgs;
import net.imglib2.img.basictypeaccess.array.LongArray;
import net.imglib2.img.cell.CellGrid;
import net.imglib2.type.numeric.integer.LongType;

/**
 * A {@link RandomAccessible} that tracks all blocks that have been accessed by
 * random accesses.
 *
 * @author Philipp Hanslovsky
 */
public class AccessedBlocksRandomAccessible<T> extends AbstractWrappedInterval<RandomAccessibleInterval<T>>
		implements RandomAccessibleInterval<T>
{
	private final TLongSet visitedBlocks;

	private final int[] blockSize;

	private final long[] blockGridDimensions;

	public AccessedBlocksRandomAccessible(final RandomAccessibleInterval<T> source, final CellGrid grid)
	{
		this(
				source,
				IntStream.range(0, grid.numDimensions()).map(grid::cellDimension).toArray(),
				grid.getGridDimensions()
		    );
	}

	public AccessedBlocksRandomAccessible(final RandomAccessibleInterval<T> source, final int[] blockSize, final
	long[] blockGridDimensions)
	{
		super(source);
		this.visitedBlocks = new TLongHashSet();
		this.blockSize = blockSize;
		this.blockGridDimensions = blockGridDimensions;
	}

	public void clear()
	{
		this.visitedBlocks.clear();
	}

	protected void addBlockId(final long id)
	{
		this.visitedBlocks.add(id);
	}

	public long[] listBlocks()
	{
		return visitedBlocks.toArray();
	}

	public CellGrid getGrid()
	{
		return new CellGrid(Intervals.dimensionsAsLongArray(getSource()), blockSize);
	}

	@Override
	public RandomAccess<T> randomAccess()
	{
		return new TrackingRandomAccess(getSource().randomAccess());
	}

	@Override
	public RandomAccess<T> randomAccess(final Interval interval)
	{
		return new TrackingRandomAccess(getSource().randomAccess(interval));
	}

	public class TrackingRandomAccess extends AbstractConvertedRandomAccess<T, T>
	{

		private final long[] blockGridPosition;

		public TrackingRandomAccess(final RandomAccess<T> source)
		{
			super(source);
			this.blockGridPosition = new long[source.numDimensions()];
		}

		@Override
		public T get()
		{
			Arrays.setAll(blockGridPosition, d -> source.getLongPosition(d) / blockSize[d]);
			addBlockId(IntervalIndexer.positionToIndex(blockGridPosition, blockGridDimensions));
			return source.get();
		}

		@Override
		public AbstractConvertedRandomAccess<T, T> copy()
		{
			return new TrackingRandomAccess(source.copyRandomAccess());
		}

	}

	public static void main(final String[] args)
	{
		final long[] dimensions = {10, 7};
		final int[]  blockSize  = {5, 3};

		final ArrayImg<LongType, LongArray> dummy = ArrayImgs.longs(dimensions);

		final AccessedBlocksRandomAccessible<LongType> tracker = new AccessedBlocksRandomAccessible<>(
				dummy,
				new CellGrid(dimensions, blockSize)
		);

		System.out.println(Arrays.toString(tracker.listBlocks()));
		final RandomAccess<LongType> ra = tracker.randomAccess();
		System.out.println(Arrays.toString(tracker.listBlocks()));
		ra.get();
		System.out.println(Arrays.toString(tracker.listBlocks()));
		ra.move(4, 0);
		ra.get();
		System.out.println(Arrays.toString(tracker.listBlocks()));
		ra.fwd(0);
		ra.get();
		System.out.println(Arrays.toString(tracker.listBlocks()));
		ra.move(6, 1);
		ra.get();
		System.out.println(Arrays.toString(tracker.listBlocks()));

	}


}