public class OreRegistryApi {

	/**
	 * Register resources and get the chosen resource for each type.
	 */
	public static IResourceRegistry registry;

	/**
	 * Get information about registered products.
	 */
	public static IResourceInfo info;

} public interface IResource {

	/**
	 * The type of the resource. For examples see {@link ResourceTypes}.
	 */
	String getType();

	/**
	 * Register a product added by your mod for this resource.
	 *
	 * @param productType The type of the product. For examples see {@link ProductTypes}.
	 * @param product     The product provided by your mod.
	 * @return the product that should be used by every mod.
	 */
	IProduct registerProduct(String productType, ItemStack product);

	/**
	 * 
	 * @param productType The type of the product. For examples see {@link ProductTypes}.
	 * @return True if it is already a product registered with this type.
	 */
	boolean hasProduct(String productType);

	/**
	 * @param productType The type of the product. For examples see {@link ProductTypes}.
	 * @return The product of the productType, if one is registered.
	 */
	@Nullable
	IProduct getProduct(String productType);
	
	/**
	 * Returns a read-only map containing product types and their associated products.
	 */
	Map<String, IProduct> getRegisteredProducts();

} public interface IResourceRegistry {

	/**
	 * Register a type of resource added by your mod.
	 */
	IResource registerResource(String resourceType);
	
	/**
	 * Returns a read-only map containing resource names and their associated
	 * resources.
	 */
	Map<String, IResource> getRegisteredResources();
	
	/**
	 * Register a {@link IUnificationHandler}.
	 */
	void registerUnificationHandler(String resourceType, IUnificationHandler unificationHandler);
	
	/**
	 * Returns a read-only map containing all {@link IUnificationHandler}s of the resource of this type.
	 */
	Collection<IUnificationHandler> getUnificationHandlers(String resourceType);
	
	/**
	 * The current state of the registry process.
	 *
	 * You only can register resources and products in the active state.
	 */
	OreRegistryState getState();

} public class ResourceTypes {

	/* MINECRAFT RESOURCES */
	public static final String IRON = "iron";
	public static final String GOLD = "gold";
	public static final String EMERALD = "emerald";
	public static final String DIAMOND = "diamond";
	public static final String LAPIS = "lapis";
	public static final String REDSTONE = "redstone";
	public static final String QUARTZ = "quartz";
	public static final String COAL = "coal";

	/* MODDED RESOURCES */
	public static final String ALUMINIUM = "aluminium";
	public static final String BRASS = "brass";
	public static final String BRONZE = "bronze";
	public static final String COPPER = "copper";
	public static final String LEAD = "lead";
	public static final String NICKEL = "nickel";
	public static final String SILVER = "silver";
	public static final String STEEL = "steel";
	public static final String TIN = "tin";
	public static final String ZINC = "zinc";


} public class Config {

    @Nullable
    public static Configuration config;
    public static boolean unifyItems = true;

    public static void load(Side side) {
        File configFile = OreRegistry.instance.getConfigFile();
        loadConfig(side, configFile);
    }

    private static void loadConfig(Side side, File configFile) {
        ResourceRegistry registry = OreRegistry.registry;
        registry.setState(OreRegistryState.CHOOSE);
        config = new Configuration(configFile, "1.0.0");
        config.load();
        String resourceCategory = "resource";
        String categoryComment = "This config category is used to chose the product variants that should be used by this mod.";
        categoryComment+="\n\n";
        categoryComment+="Every number presents one variant of a product. \n";
        categoryComment+="The value of the property is the number of the variant that is used be this mod.";
        config.setCategoryComment(resourceCategory, categoryComment);
        StringBuilder comment = new StringBuilder();
        for(Map.Entry<String, IResource> resourceEntry : OreRegistryApi.registry.getRegisteredResources().entrySet()){
            IResource resource = resourceEntry.getValue();
            for(Map.Entry<String, IProduct> productEntry : resource.getRegisteredProducts().entrySet()){
                Product product = (Product) productEntry.getValue();
                List<ItemStack> variants = product.getVariants();
                for(int i = 0;i < variants.size();i++){
                    ItemStack itemStack = variants.get(i);
                    Item item = itemStack.getItem();
                    ResourceLocation location = item.getRegistryName();
                    if(location == null){
                        continue;
                    }
                    comment.append(i + " = <" + location.toString() + ':' + itemStack.getItemDamage());
                    NBTTagCompound tagCompound = itemStack.getTagCompound();
                    if(tagCompound != null){
                        comment.append(':' + tagCompound.toString());
                    }
                    comment.append(">\n");
                }
                int variantIndex = config.getInt(productEntry.getKey(), resourceCategory + '.' + resourceEntry.getKey(), 0, 0, variants.size() - 1, comment.toString());
                ProductUtils.chooseProduct(product, variantIndex);
                comment.setLength(0);
            }
        }
    
        unifyItems = config.getBoolean("unify", "tweaks", unifyItems, "If this is true ore registry unifies every item that dropped in the world or is tossed by the player to the chosen variant of his product if the item is a variant of a product.");
        
        config.save();
        MinecraftForge.EVENT_BUS.post(new CompleteChoosingEvent());
        registry.setState(OreRegistryState.INACTIVE);
    }
} public class Constants {

	public static final String NAME = "Ore Registry";
	public static final String MOD_ID = "oreregistry";

	public static final String VERSION = "@VERSION@";
	public static final String BUILD_NUMBER = "@BUILD_NUMBER@";

} public class PacketHandler {

	public static final String channelId = "or";
	private static FMLEventChannel channel;
	
	public PacketHandler() {
		channel = NetworkRegistry.INSTANCE.newEventDrivenChannel(channelId);
		channel.register(this);
	}
	
	public static void sendToClients(EntityPlayer player){
		if (!(player instanceof EntityPlayerMP) || player instanceof FakePlayer) {
			return;
		}
		PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
		Map<String, IResource> resources = OreRegistry.registry.getRegisteredResources();
		buffer.writeVarInt(resources.size());
		for(Entry<String, IResource> resourceEntry : resources.entrySet()){
			IResource resource = resourceEntry.getValue();
			writeResource(buffer, resource);
		}
		channel.sendTo(new FMLProxyPacket(buffer, channelId), (EntityPlayerMP)player);
	}
	
	@SideOnly(Side.CLIENT)
	@SubscribeEvent
	public void onPacket(ClientCustomPacketEvent event) {
		PacketBuffer buffer = new PacketBuffer(event.getPacket().payload());
		
		checkThreadAndEnqueue(buffer, Minecraft.getMinecraft());
	}
	
	@SideOnly(Side.CLIENT)
	private static void checkThreadAndEnqueue(final PacketBuffer buffer, IThreadListener threadListener) {
		if (!threadListener.isCallingFromMinecraftThread()) {
			threadListener.addScheduledTask(() -> {
				EntityPlayer player = Minecraft.getMinecraft().player;
				Preconditions.checkNotNull(player, "Tried to send data to client before the player exists.");
				ResourceRegistry registry = OreRegistry.registry;
				
				registry.setState(OreRegistryState.SYNCHRONIZE);
				int size = buffer.readVarInt();
				while(size > 0){
					size--;
					readResource(buffer);
				}
				
				registry.setState(OreRegistryState.INACTIVE);
			});
		}
	}

	private static void writeResource(PacketBuffer buffer, IResource resource){
		String resourceType = resource.getType();
		buffer.writeString(resourceType);
		Map<String, IProduct> products = resource.getRegisteredProducts();
		buffer.writeVarInt(products.size());
		for(Entry<String, IProduct> productEntry : products.entrySet()){
			String productType = productEntry.getKey();
			try {
				buffer.writeString(productType);
				IProduct product = productEntry.getValue();
				if(product instanceof Product){
					buffer.writeInt(((Product)product).getChosenProductIndex());
				}else{
					buffer.writeInt(0);
				}
			}catch(Exception e){
				Log.error("Failed to synchronise a product of the type " + productType + " with the client.", e);
			}
		}
	}
	
	private static void readResource(PacketBuffer buffer){
		OreRegistry.unusedItems.clear();
		String resourceType = buffer.readString(1024);
		IResource resource = OreRegistry.registry.registerResource(resourceType);
		int products = buffer.readVarInt();
		for(int i = 0;i < products;i++){
			String productType = buffer.readString(1024);
			try{
				IProduct product = resource.getProduct(productType);
				int index = buffer.readInt();
				if(product instanceof Product){
					ProductUtils.chooseProduct((Product) product, index);
				}
			}catch(Exception e){
				Log.error("Failed to synchronise a product of the type " + productType + " with the server.", e);
			}
		}
	}
} public class ResourceInfo implements IResourceInfo {
	private final Map<Item, List<IResource>> resourceItems = new IdentityHashMap<>();

	@Nullable
	@Override
	public IProductInfo getProductInfo(ItemStack itemStack) {
		Preconditions.checkNotNull(itemStack, "itemStack must not be null");
		Preconditions.checkArgument(!itemStack.isEmpty(), "itemStack must not be empty");

		Item item = itemStack.getItem();
		List<IResource> resources = resourceItems.get(item);
		if (resources != null) {
			for (IResource resource : resources) {
				Map<String, IProduct> registeredProducts = resource.getRegisteredProducts();
				for (Entry<String, IProduct> entry : registeredProducts.entrySet()) {
					IProduct product = entry.getValue();
					for(ItemStack variant : product.getVariants()) {
						if (ProductUtils.needUnification(itemStack, variant)) {
							String productType = entry.getKey();
							ItemStack chosenProduct = product.getChosenProduct();
							return new ProductInfo(resource.getType(), productType, ProductUtils.needUnification(itemStack, chosenProduct));
						}
					}
				}
			}
		}
		return null;
	}
	
	@Override
	public ItemStack tryUnifyItem(ItemStack itemToUnify) {
		IProductInfo productInfo = getProductInfo(itemToUnify);
		if(productInfo != null){
			IResource resource = OreRegistry.registry.getResource(productInfo.getResourceType());
			if(resource == null){
				return ItemStack.EMPTY;
			}
			IProduct product = resource.getProduct(productInfo.getProductType());
			if(product == null){
				return ItemStack.EMPTY;
			}
			ItemStack unifiedItem = product.getChosenProduct();
			if(ProductUtils.needUnification(itemToUnify, unifiedItem)){
				return ItemStack.EMPTY;
			}
			unifiedItem.setCount(itemToUnify.getCount());
			for(IUnificationHandler handler : OreRegistryApi.registry.getUnificationHandlers(resource.getType())){
				handler.onUnifyItem(itemToUnify, unifiedItem, product);
			}
			return unifiedItem;
		}
		return ItemStack.EMPTY;
	}
	
	public void registerResourceItem(ItemStack itemStack, IResource resource) {
		Item item = itemStack.getItem();
		List<IResource> resources = resourceItems.computeIfAbsent(item, k -> new ArrayList<>());
		resources.add(resource);
	}
} public final class ResourceRegistry implements IResourceRegistry {

	private final Map<String, IResource> resources = new HashMap<>();
	private final Multimap<String, IUnificationHandler> unificationHandlers = HashMultimap.create();
	private OreRegistryState state;

	public ResourceRegistry() {
		this.state = OreRegistryState.ACTIVE;
	}

	@Override
	public IResource registerResource(String resourceType) {
		Preconditions.checkNotNull(resourceType, "resourceType must not be null");

		if (hasResource(resourceType)) {
			return getResource(resourceType);
		}

		Resource resource = new Resource(resourceType);
		addResource(resource);
		return resource;
	}

	@Override
	public Map<String, IResource> getRegisteredResources() {
		return Collections.unmodifiableMap(resources);
	}
	
	@Override
	public void registerUnificationHandler(String resourceType, IUnificationHandler unificationHandler) {
		unificationHandlers.put(resourceType, unificationHandler);
	}
	
	@Override
	public Collection<IUnificationHandler> getUnificationHandlers(String resourceType) {
		return unificationHandlers.get(resourceType);
	}
	
	@Override
	public OreRegistryState getState() {
		return state;
	}

	/* INTERNAL */
	@Nullable
	public IResource getResource(String type){
		return resources.get(type);
	}

	public boolean hasResource(String type){
		return getResource(type) != null;
	}

	public void addResource(IResource resource){
		if(state != OreRegistryState.ACTIVE){
			return;
		}
		String type = resource.getType();
		if(!resources.containsKey(type)){
			resources.put(type, resource);
		}
	}

	public void setState(OreRegistryState state) {
		this.state = state;
	}

} public static final String BLOCK = "block"; public static final String DUST = "dust"; public static final String GEM = "gem"; public static final String INGOT = "ingot"; public static final String NUGGET = "nugget"; public static final String ORE = "ore";
import javax.annotation.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.EnumDyeColor;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import oreregistry.api.OreRegistryApi;
import oreregistry.api.registry.IResource;
import oreregistry.api.registry.IResourceRegistry;
import oreregistry.api.registry.ResourceTypes;
import oreregistry.config.Config;
import oreregistry.config.Constants;
import oreregistry.network.PacketHandler;
import oreregistry.util.ResourceInfo;
import oreregistry.util.ResourceRegistry;
import static oreregistry.api.registry.ProductTypes.BLOCK;
import static oreregistry.api.registry.ProductTypes.DUST;
import static oreregistry.api.registry.ProductTypes.GEM;
import static oreregistry.api.registry.ProductTypes.INGOT;
import static oreregistry.api.registry.ProductTypes.NUGGET;
import static oreregistry.api.registry.ProductTypes.ORE;
/*
 * Copyright (c) 2017 Nedelosk, Mezz
 *
 * This work (the MOD) is licensed under the "MIT" License, see LICENSE for details.
 */
package oreregistry;






@Mod(modid = Constants.MOD_ID, name = Constants.NAME, version = Constants.VERSION, acceptedMinecraftVersions = "[1.11]")
public class OreRegistry {

	@Nullable
	@Mod.Instance(Constants.MOD_ID)
	public static OreRegistry instance;

	public static final ResourceRegistry registry;
	public static final ResourceInfo helper;
	public static final List<ItemStack> unusedItems = new ArrayList<>();
	@Nullable
	public static File configFile;

	static {
		OreRegistryApi.registry = registry = new ResourceRegistry();
		OreRegistryApi.info = helper = new ResourceInfo();
	}

	@Mod.EventHandler
	public void preInit(FMLPreInitializationEvent event) {
		registerVanilla(OreRegistryApi.registry);
		MinecraftForge.EVENT_BUS.register(new EventHandler());
		new PacketHandler();
		configFile = event.getSuggestedConfigurationFile();
	}

	@Mod.EventHandler
	public void init(FMLInitializationEvent event) {
		Config.load(event.getSide());
	}

	private void registerVanilla(IResourceRegistry resourceRegistry) {
		final IResource iron = resourceRegistry.registerResource(ResourceTypes.IRON);