package thebetweenlands.common.registries;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

import javax.annotation.Nullable;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import net.minecraft.util.ResourceLocation;
import thebetweenlands.api.storage.IDeferredStorageOperation;
import thebetweenlands.api.storage.ILocalStorage;
import thebetweenlands.api.storage.IWorldStorage;
import thebetweenlands.api.storage.LocalRegion;
import thebetweenlands.api.storage.StorageID;
import thebetweenlands.common.lib.ModInfo;
import thebetweenlands.common.world.storage.SharedLootPoolStorage;
import thebetweenlands.common.world.storage.location.LocationChiromawMatriarchNest;
import thebetweenlands.common.world.storage.location.LocationCragrockTower;
import thebetweenlands.common.world.storage.location.LocationGuarded;
import thebetweenlands.common.world.storage.location.LocationPortal;
import thebetweenlands.common.world.storage.location.LocationSludgeWormDungeon;
import thebetweenlands.common.world.storage.location.LocationSpiritTree;
import thebetweenlands.common.world.storage.location.LocationStorage;
import thebetweenlands.common.world.storage.location.LocationTokenBucket;
import thebetweenlands.common.world.storage.operation.DeferredLinkOperation;

public class StorageRegistry {
	private static final BiMap<ResourceLocation, Class<? extends ILocalStorage>> STORAGE_MAP = HashBiMap.create();
	private static final Map<ResourceLocation, Factory<?>> FACTORIES = new HashMap<>();

	private static final BiMap<ResourceLocation, Class<? extends IDeferredStorageOperation>> DEFERRED_MAP = HashBiMap.create();
	private static final Map<ResourceLocation, Supplier<? extends IDeferredStorageOperation>> DEFERRED_FACTORIES = new HashMap<>();
	
	public static interface Factory<T extends ILocalStorage> {
		public T create(IWorldStorage worldStorage, StorageID id, @Nullable LocalRegion region);
	}

	public static void preInit() {
		register(new ResourceLocation(ModInfo.ID, "shared_loot_pool_storage"), SharedLootPoolStorage.class, SharedLootPoolStorage::new);
		register(new ResourceLocation(ModInfo.ID, "location_storage"), LocationStorage.class, LocationStorage::new);
		register(new ResourceLocation(ModInfo.ID, "cragrock_tower"), LocationCragrockTower.class, LocationCragrockTower::new);
		register(new ResourceLocation(ModInfo.ID, "location_guarded"), LocationGuarded.class, LocationGuarded::new);
		register(new ResourceLocation(ModInfo.ID, "portal"), LocationPortal.class, LocationPortal::new);
		register(new ResourceLocation(ModInfo.ID, "spirit_tree"), LocationSpiritTree.class, LocationSpiritTree::new);
		register(new ResourceLocation(ModInfo.ID, "sludge_worm_dungeon"), LocationSludgeWormDungeon.class, LocationSludgeWormDungeon::new);
		register(new ResourceLocation(ModInfo.ID, "chiromaw_matriarch_nest"), LocationChiromawMatriarchNest.class, LocationChiromawMatriarchNest::new);
		register(new ResourceLocation(ModInfo.ID, "token_bucket"), LocationTokenBucket.class, LocationTokenBucket::new);
		
		register(new ResourceLocation(ModInfo.ID, "deferred_link"), DeferredLinkOperation.class, DeferredLinkOperation::new);
	}

	/**
	 * Registers a local storage
	 * @param cls
	 * @param id
	 * @param factory
	 */
	public static <T extends ILocalStorage> void register(ResourceLocation id, Class<T> cls, Factory<T> factory) {
		STORAGE_MAP.put(id, cls);
		FACTORIES.put(id, factory);
	}
	
	/**
	 * Returns the local storage class for the specified ID
	 * @param type
	 * @return
	 */
	public static Class<? extends ILocalStorage> getStorageType(ResourceLocation id) {
		return STORAGE_MAP.get(id);
	}

	/**
	 * Returns the factory for the specified id
	 * @param id
	 * @return
	 */
	public static Factory<? extends ILocalStorage> getStorageFactory(ResourceLocation id) {
		return FACTORIES.get(id);
	}

	/**
	 * Returns the ID of a local storage class
	 * @param storage
	 * @return
	 */
	public static ResourceLocation getStorageId(Class<? extends ILocalStorage> storage) {
		return STORAGE_MAP.inverse().get(storage);
	}
	
	/**
	 * Registers a deferred storage operation
	 * @param cls
	 * @param id
	 * @param factory
	 */
	public static <T extends IDeferredStorageOperation> void register(ResourceLocation id, Class<T> cls, Supplier<T> factory) {
		DEFERRED_MAP.put(id, cls);
		DEFERRED_FACTORIES.put(id, factory);
	}
	
	/**
	 * Returns the local storage class for the specified ID
	 * @param type
	 * @return
	 */
	public static Class<? extends IDeferredStorageOperation> getDeferredOperationType(ResourceLocation id) {
		return DEFERRED_MAP.get(id);
	}

	/**
	 * Returns the factory for the specified id
	 * @param id
	 * @return
	 */
	public static Supplier<? extends IDeferredStorageOperation> getDeferredOperationFactory(ResourceLocation id) {
		return DEFERRED_FACTORIES.get(id);
	}

	/**
	 * Returns the ID of a deferred storage operation class
	 * @param operation
	 * @return
	 */
	public static ResourceLocation getDeferredOperationId(Class<? extends IDeferredStorageOperation> operation) {
		return DEFERRED_MAP.inverse().get(operation);
	}
}
