public class BiomeNameHelper {
	
	public static final int MAX_REG_ID = 39;
	public static final List<Integer> VANILLA_MUT = Arrays.asList(129, 130, 131, 132, 133, 134, 140, 149, 151, 155, 156, 157, 158, 160, 162, 163, 164, 165, 166, 167);
	
	

	private static Map<String, BiomeGenBase> biomeMap;
	private static Set<BiomeGenBase> biomeSet;
	
	private final static Map<String, String> nameSimplificationMap = new HashMap<String, String>();
	
	static {
		nameSimplificationMap.put("biomesoplenty.biomes", "BOP");
		nameSimplificationMap.put("extrabiomes.module.summa.biome", "EBXL");
		nameSimplificationMap.put("twilightforest.biomes", "TF");
		nameSimplificationMap.put("bwg4.biomes", "BWG4");
		nameSimplificationMap.put("highlands.biome", "HL");
		nameSimplificationMap.put("net.tropicraft.world.biomes", "TROP");
		nameSimplificationMap.put("xolova.blued00r.divinerpg.generation", "DRPG");
	}
	
	//TODO This better - recognize vanilla biomes some other way
	public static boolean isVanilla(BiomeGenBase biome) {
		return biome.biomeID <= MAX_REG_ID || VANILLA_MUT.contains(biome.biomeID);
	}
	
	
	public static String getBiomeName(BiomeGenBase biome) {
		String name = ((isVanilla(biome)) ? "Vanilla" : biome.getClass().getName()) + '.' + biome.biomeName;
		for (Entry<String,String> entry : nameSimplificationMap.entrySet())
			if (name.startsWith(entry.getKey()))
				return entry.getValue() + '.' + biome.biomeName;
		return name;
	}

	public static BiomeGenBase getBiome(String biomeName) {
		return biomeMap.get(biomeName);
	}

	public static void initBiomeMap() {
		if (biomeMap != null)
			return;
		
		biomeMap = new HashMap<String, BiomeGenBase>();
		biomeSet = new HashSet<BiomeGenBase>();

		for (BiomeGenBase biome : BiomeGenBase.getBiomeGenArray()) {
			if (biome != null) {
				biomeMap.put(getBiomeName(biome), biome);
				biomeSet.add(biome);
			}
		}
	}

	public static Set<BiomeGenBase> getAllBiomes() {
		return biomeSet;
	}

	public static Set<String> getAllBiomeNames() {
		return biomeMap.keySet();
	}
} @Mod(modid = "MSC2", name="Mob Spawn Controls 2", dependencies = "after:*", version=MobSpawnControls.VERSION)
public class MobSpawnControls{

	public static final String VERSION = "1.2.1";
	public static final PacketPipeline DISPATCHER = new PacketPipeline();
	
	@SidedProxy(clientSide = "com.mcf.davidee.msc.forge.ClientProxy", serverSide = "com.mcf.davidee.msc.forge.CommonProxy")
	public static CommonProxy proxy;
	@Instance("MSC2")
	public static MobSpawnControls instance;

	private static FileHandler logHandler = null;
	private static Logger logger = Logger.getLogger("MobSpawnControls");


	public static Logger getLogger() {
		return logger;
	}

	private SpawnConfiguration config, defaultConfig;
	private SpawnFreqTicker ticker;


	@EventHandler 
	public void preInit(FMLPreInitializationEvent event) {
		ModMetadata modMeta = event.getModMetadata();
		modMeta.authorList = Arrays.asList(new String[] { "Davidee" });
		modMeta.autogenerated = false;
		modMeta.credits = "Thanks to Mojang, Forge, and all your support.";
		modMeta.description = "Allows you to control the vanilla spawning system.";
		modMeta.url = "http://www.minecraftforum.net/topic/1909304-/";
	}

	@EventHandler 
	public void load(FMLInitializationEvent event) {
		logger.setLevel(Level.ALL);

		try {
			File logfile = new File(proxy.getMinecraftDirectory(),"MobSpawnControls.log");
			if ((logfile.exists() || logfile.createNewFile()) && logfile.canWrite() && logHandler == null)	{
				logHandler = new FileHandler(logfile.getPath());
				logHandler.setFormatter(new MSCLogFormatter());
				logger.addHandler(logHandler);
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		logger.info("Mob Spawn Controls initializing...");

		ticker = new SpawnFreqTicker();
		DISPATCHER.initialize();
	}

	@EventHandler 
	public void modsLoaded(FMLPostInitializationEvent event) {
		BiomeClassLoader.loadBiomeClasses();
		logger.info("Generating default creature type map...");
		MobHelper.populateDefaultMap();
		logger.info("Mapping biomes...");
		BiomeNameHelper.initBiomeMap();
		logger.info("Creating default spawn configuration...");
		defaultConfig = new SpawnConfiguration(new File(new File(proxy.getMinecraftDirectory(),"config"), "default_msc"));
		defaultConfig.load();
		defaultConfig.save();
	}
	
	@SubscribeEvent
	public void onServerTick(ServerTickEvent event) {
		ticker.tick();
	}

	@EventHandler  
	public void serverStarting(FMLServerStartingEvent event) {
		MinecraftServer server = event.getServer();
		WorldServer world = server.worldServerForDimension(0);
		if (world != null && world.getSaveHandler() instanceof SaveHandler){
			logger.info("Creating spawn configuration for World \"" + world.getSaveHandler().getWorldDirectoryName() + "\"");
			config = new SpawnConfiguration(new File(((SaveHandler)world.getSaveHandler()).getWorldDirectory(),"msc"), defaultConfig);
			config.load();
			config.save();
		}
	}

	@EventHandler 
	public void serverStopping(FMLServerStoppingEvent event) {
		logger.info("Unloading spawn configuration");
		MSCLogFormatter.log.clear();
		config = null;
	}

	public SpawnConfiguration getConfigNoThrow() {
		return config;
	}

	public SpawnConfiguration getConfig() throws RuntimeException {
		if (config != null)
			return config;
		throw new RuntimeException("MSC: Null Spawn Config");
	}


} public class Utils {
	
	public static int parseIntWithMinMax(String s, int min, int max) throws NumberFormatException {
		int i = Integer.parseInt(s);
		if (i < min)
			return min;
		if (i > max)
			return max;
		return i;

	}

	public static int parseIntDMinMax(String s, int _default, int min, int max) {
		try {
			return parseIntWithMinMax(s, min, max);
		} catch (NumberFormatException e) {
			return _default;
		}
	}

	public static <E> Set<E> asSet(E... arr) {
		Set<E> set = new HashSet<E>();
		for (E obj : arr)
			set.add(obj);
		return set;
	}

	public static <E> List<E> asList(E... arr) {
		List<E> list = new ArrayList<E>();
		for (E obj : arr)
			list.add(obj);
		return list;
	}

	public static <T> void copyInto(List<T> source, List<T> dest) {
		dest.clear();
		for (T obj : source)
			dest.add(obj);
	}

	public static void writeLine(BufferedWriter writer, String line) throws IOException {
		writer.write(line);
		writer.newLine();
	}
} public class ModConfig {

	private final File saveFile;
	private final List<Class<? extends EntityLiving>> entityClasses;

	public final ModContainer container;
	public final ModConfigParser parser;
	public final String fileName, configName;

	private List<BiomeGroup> groups;
	private CreatureTypeMap typeMap;
	private SpawnMap active, _default;
	private boolean readOnly = false;
	private int lineNum;
	
	public int weight, min, max;

	//Constructor called to create the default config
	public ModConfig(ModContainer container, File dir) {
		this.container = container;
		configName = (container == null) ? "Vanilla" : container.getModId();
		fileName = configName.replaceAll("[\\\\/:*?\"<>|]", "");
		saveFile = new File(dir, fileName + ".cfg");
		entityClasses = ModEntityRecognizer.getEntityClasses(container);

		groups = new ArrayList<BiomeGroup>();
		typeMap = new CreatureTypeMap(entityClasses);
		_default = new SpawnMap(entityClasses);
		active = new SpawnMap(_default);
		weight = 8;
		min = max = 4;
		
		parser = new ModConfigParser(this);
	}

	//Constructor called to create the world config
	public ModConfig(ModContainer container, File folder, ModConfig c) {
		this.container = container;
		configName = (container == null) ? "Vanilla" : container.getModId();
		fileName = configName.replaceAll("[\\\\/:*?\"<>|]", "");
		saveFile = new File(folder, fileName + ".cfg");
		entityClasses = c.entityClasses;

		//Copy everything over from the default config
		groups = new ArrayList<BiomeGroup>();
		for (BiomeGroup g : c.groups)
			groups.add(g.clone());
		typeMap = new CreatureTypeMap(c.typeMap);
		_default = new SpawnMap(c.active);
		active = new SpawnMap(_default);
		weight = c.weight;
		min = c.min;
		max = c.max;
		
		parser = new ModConfigParser(this);
	}

	public SpawnMap getSpawnMap() {
		return active;
	}

	public CreatureTypeMap getTypeMap() {
		return typeMap;
	}
	
	public List<BiomeGroup> getBiomeGroups() {
		return groups;
	}

	//Not sure if this will ever be used
	//If so, perhaps reset the groups and creature type map
	//to that of the default container?
	public void reset() {
		typeMap.generateDefault(entityClasses);
		active = new SpawnMap(_default);
	}

	public void save() {
		if (readOnly)
			return;
		BufferedWriter writer = null;
		try {
			writer = new BufferedWriter(new FileWriter(saveFile));
			Utils.writeLine(writer, "# Mob Spawn Controls Configuration for \""
					+ configName + "\"");
			Utils.writeLine(writer, "#");
			Utils.writeLine(writer, "?ReadOnly=" + readOnly);
			Utils.writeLine(writer, "#");
			Utils.writeLine(writer, "?ItemWeight=" + weight);
			Utils.writeLine(writer, "?MinCount=" + min);
			Utils.writeLine(writer, "?MaxCount=" + max);
			Utils.writeLine(writer, "#");
			Utils.writeLine(writer, "# Creature Type Configuration");

			for (EnumCreatureType type : EnumCreatureType.values())
				Utils.writeLine(writer, getTypeString(type));

			Utils.writeLine(writer, getTypeString(null));
			Utils.writeLine(writer, "#");
			Utils.writeLine(writer, "# Biome Groupings");
			//Write groups
			for (BiomeGroup group : groups)
				Utils.writeLine(writer, '&' + group.toString());
			Utils.writeLine(writer, "#");
			Utils.writeLine(writer, "# Spawn Settings");
			Utils.writeLine(writer, "#");
			//Write config
			active.writeConfig(writer, configName + '.');

		} catch (IOException e) {
			MobSpawnControls.getLogger().throwing("ModConfig", "save", e);
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (IOException e) { }
			}
		}
	}

	public void load() {
		if (!saveFile.exists())
			return;

		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new FileReader(saveFile));
			groups.clear();
			active.resetGroupsList();
			lineNum = 1;
			while (reader.ready()) {
				String line = reader.readLine();
				if (!line.startsWith("#")) { //Lines starting with # are comments
					if (line.startsWith("~")) //Type setting
						setTypeSettings(line.substring(1));
					else if (line.startsWith("?")) //Variable setting
						readVariable(line.substring(1));
					else if (line.startsWith("&")) //Group Setting
						readGroup(line.substring(1));
					else //Assume biome setting
						readBiomeSettings(line);
				}
				++lineNum;
			}
		} catch (IOException e) {
			MobSpawnControls.getLogger().throwing("ModConfig", "load", e);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) { }
			}
		}
	}

	//Current groups cannot reference groups that have not yet been read.
	//This is not so much a bug as it is a safety measure, which prevents groups from
	//referencing each other in a chain and causing a recursion crash
	private void readGroup(String line) {
		BiomeGroup group = parser.readBiomeGroup(line);
		
		if (group == null) 
			warn(parser.log.get(0), '&' + line);
		else {
			for (String s : parser.log)
				warn(s, '&' + line);
			
			groups.add(group);
			active.addGroup(group);
		}
	}

	private void readBiomeSettings(String line) {
		parser.readBiomeSettings(line);
		for (String s : parser.log)
			warn(s, line);
	}

	//Should really implement a map for this
	private void readVariable(String line) {
		int index = line.indexOf('=');
		if (index != -1) {
			String var = line.substring(0, index);
			String value = line.substring(index + 1);
			if (var.equalsIgnoreCase("readonly"))
				readOnly = "true".equalsIgnoreCase(value);
			if (var.equalsIgnoreCase("itemweight"))
				weight = Utils.parseIntDMinMax(value, weight, 1, 100);
			if (var.equalsIgnoreCase("mincount"))
				min = Utils.parseIntDMinMax(value, min, 1, 10);
			if (var.equalsIgnoreCase("maxcount")) {
				max = Utils.parseIntDMinMax(value, max, 1, 15);
				if (min > max)
					max = min;
			}
		} else
			warn("Expected character '='", "?" + line);
	}

	//Used to parse Creature Type settings
	private void setTypeSettings(String line) {
		parser.setTypeSettings(line);
		for (String s : parser.log)
			warn(s, '~' + line);
	}

	private void warn(String message, String line) {
		MobSpawnControls.getLogger().warning(message + " in " + fileName + ".cfg:" + lineNum + " \"" + line + "\"");
	}
	
	public BiomeGroup getBiomeGroup(String name) {
		for (BiomeGroup group : groups)
			if (group.getName().equalsIgnoreCase(name))
				return group;
		return null;
	}

	//Used to write creature type config
	private String getTypeString(EnumCreatureType type) {
		String s = "~" + (type == null ? "UNKNOWN" : type) + ":";
		List<String> names = getEntityNames(type);
		for (int i = 0; i < names.size(); ++i) {
			s += names.get(i);
			if (i != names.size() - 1) //Last one does not get a comma
				s += ",";
		}
		return s;
	}

	//Return the simplified Entity name
	public String getEntityName(Class<? extends EntityLiving> cls) {
		String s = (String) EntityList.classToStringMapping.get(cls);
		return (container == null) ? s : s.replace(configName + ".", "");
	}
	
	public Class<? extends EntityLiving> getEntityClass(String s, EnumCreatureType type) {
		Class<? extends EntityLiving> clazz = getEntityClass(s);
		if (clazz != null && typeMap.get(clazz) != type) //Mapped to different type
			clazz = null;
		return clazz;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public Class<? extends EntityLiving> getEntityClass(String s) {
		//Check for vanilla and Forge-registered entities
		String entityName = s;
		if (container != null)
			entityName = configName + "." + s;

		Class c = (Class) EntityList.stringToClassMapping.get(entityName);
		if (ModEntityRecognizer.isValidEntityClass(c)) {
			if (entityClasses.contains(c)) 
				return c;
		}

		//Check for non Forge-registered entities
		if (container != null) { //Don't check again for vanilla
			c = (Class) EntityList.stringToClassMapping.get(s);
			if (ModEntityRecognizer.isValidEntityClass(c)) {
				if (entityClasses.contains(c)) 
					return c;
			}
		}
		return null;
	}


	//Methods used for networking/gui
	public List<String> getEntityNames(EnumCreatureType type) {
		List<String> strings = new ArrayList<String>();
		List<Class<? extends EntityLiving>> cls = typeMap.getEntitiesOfType(type);
		for (Class<? extends EntityLiving> c : cls)
			strings.add(getEntityName(c));
		Collections.sort(strings);
		return strings;
	}

	public void processGroupCommand(String command) {
		String[] args = command.split(",");
		String id = args[0];
		if (id.equalsIgnoreCase("ren")) { //Rename group
			String old = args[1], newN = args[2];
			for (Iterator<BiomeGroup> it = groups.iterator(); it.hasNext();) {
				BiomeGroup cur = it.next();
				if (cur.getName().equalsIgnoreCase(old)) {
					cur.setName(newN);
					break;
				}
			}
			active.renameGroup(old, newN);
		}
		if (id.equalsIgnoreCase("del")) { //Delete group
			String name = args[1];
			for (Iterator<BiomeGroup> it = groups.iterator(); it.hasNext();)
				if (it.next().getName().equalsIgnoreCase(name)) {
					it.remove();
					break;
				}
			active.removeGroup(name);
		}
		if (id.equalsIgnoreCase("add")) { //Add group
			String name = args[1];
			BiomeGroup group = new BiomeGroup(name);
			groups.add(group);
			active.addGroup(group);
		}
	}

} public abstract class MSCPacket extends AbstractPacket {
	
	public static final String PACKET_ID = "msc2";

	public enum PacketType{

		HANDSHAKE(HSPacket.class),
		ACCESS_DENIED(ADPacket.class),
		REQUEST(RequestPacket.class),
		MOD_LIST(ModListPacket.class),
		GROUPS(GroupsPacket.class),
		CREATURE_TYPE(CreatureTypePacket.class),
		BIOME_LIST(BiomeListPacket.class),
		BIOME_SETTING(BiomeSettingPacket.class),
		ENTITY_LIST(EntityListPacket.class),
		ENTITY_SETTING(EntitySettingPacket.class),
		SETTINGS(SettingsPacket.class),
		EVALUATED_BIOME(EvaluatedBiomePacket.class),
		EVALUATED_GROUP(EvaluatedGroupPacket.class),
		DEBUG(DebugPacket.class);

		private Class<? extends MSCPacket> packetType;

		private PacketType(Class<? extends MSCPacket> cls) {
			this.packetType = cls;
		}

		private MSCPacket make() {
			try {
				return packetType.newInstance();
			} catch (Exception e) {
				MobSpawnControls.getLogger().severe("Error during packet creation");
				throw new RuntimeException(e);
			}
		}
		
		public static int ordinalForClass(Class<? extends MSCPacket> cls) throws IllegalArgumentException {
			PacketType[] values = PacketType.values();
			for (int i = 0; i < values.length; ++i)
				if (values[i].packetType == cls)
					return i;
			throw new IllegalArgumentException("Unknown class " + cls);
		}
	}

	public abstract MSCPacket readData(Object... data);

	//Packet creation
	public static MSCPacket getRequestPacket(PacketType type) {
		return getPacket(PacketType.REQUEST, UnsignedBytes.checkedCast(type.ordinal()));
	}

	public static MSCPacket getRequestPacket(PacketType type, String data) {
		return getPacket(PacketType.REQUEST, UnsignedBytes.checkedCast(type.ordinal()), data);
	}

	public static MSCPacket getPacket(PacketType type, Object... data) {
		return type.make().readData(data);
	}
	
	//Helper methods for the PacketPipeline
	public static MSCPacket readPacket(ChannelHandlerContext ctx, ByteBuf payload) throws IOException {
		int type = UnsignedBytes.toInt(payload.readByte());
		PacketType pType = PacketType.values()[type];
		MSCPacket packet = pType.make();
		packet.decodeFrom(ctx, payload.slice());
		return packet;
	}
	
	public static void writePacket(MSCPacket pkt, ChannelHandlerContext ctx, ByteBuf to) throws IOException {
		to.writeByte(UnsignedBytes.checkedCast(PacketType.ordinalForClass(pkt.getClass())));
		pkt.encodeInto(ctx, to);
	}
	
	
	//String utility methods
	public static void writeString(String str, ByteBuf buffer) {
		ByteBufUtils.writeUTF8String(buffer, str);
	}
	
	public static String readString(ByteBuf buffer) {
		return ByteBufUtils.readUTF8String(buffer);
	}

	public static void writeStringArray(String[] arr, ByteBuf to) {
		to.writeInt(arr.length);
		for (String s : arr)
			writeString(s, to);
	}

	public static String[] readStringArray(ByteBuf from) {
		String[] arr = new String[from.readInt()];
		for (int i = 0; i < arr.length; ++i)
			arr[i] = readString(from);
		return arr;
	}
} public enum PacketType{

	HANDSHAKE(HSPacket.class),
	ACCESS_DENIED(ADPacket.class),
	REQUEST(RequestPacket.class),
	MOD_LIST(ModListPacket.class),
	GROUPS(GroupsPacket.class),
	CREATURE_TYPE(CreatureTypePacket.class),
	BIOME_LIST(BiomeListPacket.class),
	BIOME_SETTING(BiomeSettingPacket.class),
	ENTITY_LIST(EntityListPacket.class),
	ENTITY_SETTING(EntitySettingPacket.class),
	SETTINGS(SettingsPacket.class),
	EVALUATED_BIOME(EvaluatedBiomePacket.class),
	EVALUATED_GROUP(EvaluatedGroupPacket.class),
	DEBUG(DebugPacket.class);

	private Class<? extends MSCPacket> packetType;

	private PacketType(Class<? extends MSCPacket> cls) {
		this.packetType = cls;
	}

	private MSCPacket make() {
		try {
			return packetType.newInstance();
		} catch (Exception e) {
			MobSpawnControls.getLogger().severe("Error during packet creation");
			throw new RuntimeException(e);
		}
	}
		
	public static int ordinalForClass(Class<? extends MSCPacket> cls) throws IllegalArgumentException {
		PacketType[] values = PacketType.values();
		for (int i = 0; i < values.length; ++i)
			if (values[i].packetType == cls)
				return i;
		throw new IllegalArgumentException("Unknown class " + cls);
	}
} public static class EntityEntry {

	public String entity;
	public int weight;
	public int min;
	public int max;

	public EntityEntry(String entity) {
		this.entity = entity;
		min = 4;
		max = 4;
	}

	public EntityEntry(String entity, int w, int min, int max) {
		this.entity = entity;
		this.weight = w;
		this.min = min;
		this.max = max;
	}

} public static class BiomeEntry {
	public String biome;
	public int weight;
	public int min;
	public int max;
		
	public BiomeEntry (String biome) {
		this.biome = biome;
		min = 4;
		max = 4;
	}
		
	public BiomeEntry(String biome, int w, int min, int max) {
		this.biome = biome;
		this.weight = w;
		this.min = min;
		this.max = max;
	}
		
} public class BiomeReflector {
	
	private static final int MONSTER_INDEX = 72;
	
	//TODO Cache
	public static List<SpawnListEntry> reflectList(BiomeGenBase biome, EnumCreatureType type) {
		try {
			int ordin = (type == EnumCreatureType.waterCreature) ? 2 : (type == EnumCreatureType.ambient) ? 3 : type.ordinal();
			return reflect(biome,ordin);
		}
		catch(Exception e){
			MobSpawnControls.getLogger().severe("Unable to reflect list for biome " + BiomeNameHelper.getBiomeName(biome) + " of type " + type);
			MobSpawnControls.getLogger().throwing("BiomeReflector", "reflectList", e);
			throw new RuntimeException(e);
		}
	}
	
	@SuppressWarnings("unchecked")
	private static List<SpawnListEntry> reflect(BiomeGenBase biome, int ordinal) throws Exception {
		Field f = BiomeGenBase.class.getDeclaredFields()[MONSTER_INDEX+ordinal];
		f.setAccessible(true);
		return (List<SpawnListEntry>)f.get(biome);
	}
} public class SpawnList {
	private List<SpawnListEntry> m, c, w, a;

	public SpawnList() {
		m = new ArrayList<SpawnListEntry>();
		c = new ArrayList<SpawnListEntry>();
		w = new ArrayList<SpawnListEntry>();
		a = new ArrayList<SpawnListEntry>();
	}

	public SpawnList(List<SpawnListEntry> m, List<SpawnListEntry> c, List<SpawnListEntry> w, List<SpawnListEntry> a) {
		this.m = copySpawnListEntries(m);
		this.c = copySpawnListEntries(c);
		this.w = copySpawnListEntries(w);
		this.a = copySpawnListEntries(a);
	}

	public SpawnList(SpawnList entryList) {
		this.m = copySpawnListEntries(entryList.m);
		this.c = copySpawnListEntries(entryList.c);
		this.w = copySpawnListEntries(entryList.w);
		this.a = copySpawnListEntries(entryList.a);
	}

	@SuppressWarnings("rawtypes")
	public SpawnListEntry getEntityEntry(Class entityClass, EnumCreatureType type) {
		return getEntityEntry(entityClass, getSpawnList(type));
	}

	@SuppressWarnings("rawtypes")
	private static SpawnListEntry getEntityEntry(Class entityClass, List<SpawnListEntry> list) {
		for (SpawnListEntry e : list) 
			if (e.entityClass == entityClass)
				return e;
		return null;
	}

	@SuppressWarnings("rawtypes")
	public void removeEntityEntry(Class entityClass, EnumCreatureType type) {
		removeEntityEntry(entityClass, getSpawnList(type));
	}

	@SuppressWarnings("rawtypes")
	private void removeEntityEntry(Class entityClass, List<SpawnListEntry> list) {
		for (Iterator<SpawnListEntry> it = list.iterator(); it.hasNext(); ) 
			if (it.next().entityClass == entityClass) {
				it.remove();
				break;
			}
	}

	public void clear() {
		m.clear();
		c.clear();
		w.clear();
		a.clear();
	}

	public List<SpawnListEntry> getSpawnList(EnumCreatureType type) {
		switch(type){
		case monster:
			return m;
		case creature:
			return c;
		case waterCreature:
			return w;
		case ambient:
			return a;
		default:
			return null;
		}
	}

	public boolean isEmpty() {
		return m.isEmpty() && c.isEmpty() && w.isEmpty() && a.isEmpty();
	}

	public static List<SpawnListEntry> copySpawnListEntries(List<SpawnListEntry> list) {
		List<SpawnListEntry> newList = new ArrayList<SpawnListEntry>(list.size()+1);
		for (SpawnListEntry e : list)
			newList.add(copyEntry(e));
		return newList;
	}

	public static SpawnListEntry copyEntry(SpawnListEntry e) {
		return new SpawnListEntry(e.entityClass,e.itemWeight,e.minGroupCount,e.maxGroupCount);
	}

	public static String entryToString(SpawnListEntry e) {
		return EntityList.classToStringMapping.get(e.entityClass) + "(" + e.itemWeight + "-" + e.minGroupCount + "-" + e.maxGroupCount + ")";
	}


	public static String entriesToString(List<SpawnListEntry> e, String toIgnore) {
		String s = "";
		for (Iterator<SpawnListEntry> it = e.iterator(); it.hasNext(); ){
			s += entryToString(it.next()).replace(toIgnore,"");
			if (it.hasNext())
				s += ",";
		}
		return s;
	}

	public static List<Class<? extends EntityLiving>> getDisabledEntities(List<SpawnListEntry> spawning, List<Class<? extends EntityLiving>> list) {
		List<Class<? extends EntityLiving>> disabled = new ArrayList<Class<? extends EntityLiving>>();
		for (Class<? extends EntityLiving> clazz : list) {
			boolean found = false;

			for (SpawnListEntry e : spawning) {
				if (e.entityClass == clazz) {
					found = true;
					break;
				}
			}
			if (!found)
				disabled.add(clazz);
		}
		return disabled;
	}
}
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EnumCreatureType;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.biome.BiomeGenBase.SpawnListEntry;
import com.mcf.davidee.msc.BiomeNameHelper;
import com.mcf.davidee.msc.MobSpawnControls;
import com.mcf.davidee.msc.Utils;
import com.mcf.davidee.msc.config.ModConfig;
import com.mcf.davidee.msc.packet.MSCPacket;
import com.mcf.davidee.msc.packet.MSCPacket.PacketType;
import com.mcf.davidee.msc.packet.settings.BiomeSettingPacket.EntityEntry;
import com.mcf.davidee.msc.packet.settings.EntitySettingPacket.BiomeEntry;
import com.mcf.davidee.msc.reflect.BiomeReflector;
import com.mcf.davidee.msc.spawning.SpawnList;
package com.mcf.davidee.msc.grouping;




public class SpawnMap {

	private List<GroupSetting> groups;
	private Map<BiomeGenBase,BiomeSetting> biomeMap;
	private SpawnList master;

	public SpawnMap(List<Class<? extends EntityLiving>> validClasses) {
		groups = new ArrayList<GroupSetting>();
		biomeMap = new TreeMap<BiomeGenBase,BiomeSetting>(new BiomeComparator());
		master = new SpawnList();

		for (BiomeGenBase biome : BiomeNameHelper.getAllBiomes()){
			if (biome != null)
				biomeMap.put(biome, new BiomeSetting(biome,validClasses));
		}
	}

	public SpawnMap(SpawnMap other) {
		groups = new ArrayList<GroupSetting>();
		biomeMap = new TreeMap<BiomeGenBase,BiomeSetting>(new BiomeComparator());
		master = new SpawnList(other.master);

		for (GroupSetting gs : other.groups)
			groups.add(new GroupSetting(gs));
		for (Entry<BiomeGenBase,BiomeSetting> entry : other.biomeMap.entrySet())
			biomeMap.put(entry.getKey(), new BiomeSetting(entry.getValue()));
	}

	public void evaluate() {
		for (BiomeSetting s : biomeMap.values())
			s.prepareForMerge();
		for (GroupSetting s : groups)
			s.mergeInto(biomeMap);
	}

	public void sync(boolean masterEnabled) {
		if (masterEnabled){
			for (BiomeGenBase biome : biomeMap.keySet()){
				for (EnumCreatureType type : EnumCreatureType.values()){
					BiomeReflector.reflectList(biome, type).addAll(master.getSpawnList(type));
				}
			}
		}
		else{
			for (BiomeSetting s : biomeMap.values())
				s.sync();
		}
	}

	public SpawnList getMasterList() {
		return master;
	}

	public void addGroup(BiomeGroup group) {
		groups.add(new GroupSetting(group));
	}
	
	public MSCPacket getEvaluatedBiomePacket(String mod, String biome) {