package openmods.structured;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.collect.TreeMultimap;
import gnu.trove.impl.Constants;
import gnu.trove.map.hash.TIntIntHashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import org.apache.commons.lang3.mutable.MutableInt;

public abstract class StructuredData<C extends IStructureContainer<E>, E extends IStructureElement> {
	protected static final int NULL = -1;

	protected final SortedMap<Integer, E> elements = Maps.newTreeMap();
	protected final SortedMap<Integer, C> containers = Maps.newTreeMap();
	protected final TreeMultimap<Integer, Integer> containerToElement = TreeMultimap.create();
	protected final TIntIntHashMap elementToContainer = new TIntIntHashMap(Constants.DEFAULT_CAPACITY, Constants.DEFAULT_LOAD_FACTOR, NULL, NULL);

	public boolean isEmpty() {
		return elements.isEmpty() && containers.isEmpty();
	}

	protected final IStructureObserver<C, E> observer;

	public StructuredData(IStructureObserver<C, E> observer) {
		this.observer = observer;
	}

	public StructuredData() {
		this(new StructureObserver<C, E>());
	}

	public void removeAll() {
		for (Map.Entry<Integer, C> c : containers.entrySet()) {
			final int containerId = c.getKey();
			final C container = c.getValue();
			observer.onContainerRemoved(containerId, container);

			for (Integer elementId : containerToElement.get(containerId)) {
				E element = elements.get(elementId);
				Preconditions.checkNotNull(element);
				observer.onElementRemoved(containerId, container, elementId, element);
			}
		}

		elements.clear();
		containers.clear();
		containerToElement.clear();
		elementToContainer.clear();
	}

	protected SortedSet<Integer> removeContainer(int containerId) {
		Preconditions.checkArgument(containerToElement.containsKey(containerId), "Container %s doesn't exists", containerId);
		SortedSet<Integer> removedElements = containerToElement.removeAll(containerId);

		final C container = containers.remove(containerId);
		observer.onContainerRemoved(containerId, container);

		for (Integer elementId : removedElements) {
			final E element = elements.remove(elementId);
			elementToContainer.remove(elementId);
			observer.onElementRemoved(containerId, container, elementId, element);
		}

		return removedElements;
	}

	protected int addContainer(final int containerId, final C container, int firstElementId) {
		final MutableInt nextElementId = new MutableInt(firstElementId);

		container.createElements(element -> {
			final int elementId = nextElementId.intValue();
			nextElementId.increment();

			elements.put(elementId, element);
			containerToElement.put(containerId, elementId);
			elementToContainer.put(elementId, containerId);

			observer.onElementAdded(containerId, container, elementId, element);

			return elementId;
		});

		containers.put(containerId, container);
		observer.onContainerAdded(containerId, container);

		return nextElementId.intValue();
	}
}