package de.hpi.is.md.hybrid.impl.preprocessed;

import static de.hpi.is.md.util.CollectionUtils.mutableSingleton;

import de.hpi.is.md.hybrid.PositionListIndex;
import de.hpi.is.md.util.CollectionUtils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.IntSets;
import java.util.Arrays;
import java.util.Iterator;
import java.util.function.BiConsumer;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class ArrayPositionListIndex implements PositionListIndex {

	private static final long serialVersionUID = 4441131985840473264L;
	@NonNull
	private final IntSet[] array;

	public static BuilderImpl builder() {
		Int2ObjectOpenHashMap<IntSet> map = new Int2ObjectOpenHashMap<>();
		return new BuilderImpl(map);
	}

	@Override
	public IntSet get(int valueId) {
		if (valueId >= array.length) {
			return IntSets.EMPTY_SET;
		}
		return array[valueId];
	}

	@Override
	public Iterator<Cluster> iterator() {
		return new ClusterIterator();
	}

	@RequiredArgsConstructor
	private static class BuilderImpl implements Builder {

		@NonNull
		private final Int2ObjectMap<IntSet> map;
		private int maxValue;

		private static <T> BiConsumer<Integer, T> set(T[] array) {
			return (k, v) -> array[k.intValue()] = v;
		}

		@Override
		public void add(int recordId, int value) {
			updateMaxValue(value);
			IntSet set = mutableSingleton(recordId);
			map.merge(value, set, CollectionUtils::merge);
		}

		@Override
		public PositionListIndex build() {
			IntSet[] array = new IntSet[maxValue + 1];
			Arrays.fill(array, IntSets.EMPTY_SET);
			map.forEach(set(array));
			return new ArrayPositionListIndex(array);
		}

		private void updateMaxValue(int value) {
			maxValue = Math.max(value, maxValue);
		}
	}

	private class ClusterIterator implements Iterator<Cluster> {

		private int currentIndex = 0;

		@Override
		public boolean hasNext() {
			return currentIndex < array.length;
		}

		@Override
		public Cluster next() {
			return new Cluster(currentIndex, array[currentIndex++]);
		}
	}
}