package io.activej.dataflow.inject;

import io.activej.common.function.SupplierEx;
import io.activej.csp.supplier.ChannelSupplier;
import io.activej.datastream.supplier.StreamSupplier;
import io.activej.inject.Key;
import io.activej.inject.binding.BindingType;
import io.activej.inject.module.AbstractModule;
import io.activej.promise.Promise;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;

public final class DatasetIdModule extends AbstractModule {

	private DatasetIdModule() {
	}

	public static DatasetIdModule create() {
		return new DatasetIdModule();
	}

	@Override
	protected void configure() {
		DatasetIds datasetIds = new DatasetIds();
		bind(DatasetIds.class).toInstance(datasetIds);
		transform(Object.class, (bindings, scope, key, binding) -> {
			Object qualifier = key.getQualifier();
			if (qualifier instanceof DatasetId datasetId) {
				String id = datasetId.value();
				Class<?> rawType = key.getRawType();

				if (binding.getType() != BindingType.TRANSIENT &&
					(
						rawType == Iterator.class ||
						rawType == Supplier.class ||
						rawType == SupplierEx.class ||
						rawType == Stream.class ||
						rawType == StreamSupplier.class ||
						rawType == ChannelSupplier.class ||
						rawType == Promise.class
					)
				) {
					throw new IllegalStateException(
						"Provided dataset '" + datasetId.value() + "' is a single use dataset. " +
						"Maybe mark it as @Transient?");
				}

				Key<?> previousKey = datasetIds.keys.put(id, key);
				if (previousKey != null && !previousKey.equals(key)) {
					throw new IllegalStateException("More than one items provided for dataset id '" + id + "'");
				}
			}
			return binding;
		});
	}

	public static final class DatasetIds {
		final Map<String, Key<?>> keys = new HashMap<>();

		@SuppressWarnings("unchecked")
		public <T> Key<T> getKeyForId(String id) {
			Key<?> key = keys.get(id);
			if (key == null) {
				throw new IllegalStateException("No key for dataset id '" + id + "'");
			}
			return (Key<T>) key;
		}
	}
}
