package jas.common.command;

import jas.common.JustAnotherSpawner;
import jas.common.spawner.CountInfo;
import jas.common.spawner.CustomSpawner;
import jas.common.spawner.Tags;
import jas.common.spawner.biome.group.BiomeHelper;
import jas.common.spawner.biome.structure.StructureHandler;
import jas.common.spawner.creature.entry.BiomeSpawnListRegistry;
import jas.common.spawner.creature.entry.SpawnListEntry;
import jas.common.spawner.creature.handler.LivingGroupRegistry;
import jas.common.spawner.creature.handler.LivingHandler;
import jas.common.spawner.creature.handler.LivingHandlerRegistry;
import jas.common.spawner.creature.handler.LivingHelper;
import jas.common.spawner.creature.type.CreatureType;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import net.minecraft.command.ICommandSender;
import net.minecraft.command.WrongUsageException;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ChatComponentText;
import net.minecraft.world.biome.BiomeGenBase;

public class CommandCanSpawnHere extends CommandJasBase {

    /*
     * Number of trials to run canSpawnHere to rule out random chance of success/failure.
     * 
     * This was chosen such that to give a ~95% accuracy to the minimum accepted deviation in chance which is 1%. Any
     * entity that spawns at least 1% of the time should be detected properly >=95% of the time.
     * 
     * CHANCE_OF_SUCCESS = 0.95 >= = 1-(1-TRIAL_CHANCE/100)^SIMULATION_TRIALS. TRIAL_CHANCE is chance out of 100 that
     * the entity would spawn.
     */
    private static final int SIMULATION_TRIALS = 300;

    public String getCommandName() {
        return "canspawnhere";
    }

    /**
     * Return the required permission level for this command.
     */
    public int getRequiredPermissionLevel() {
        return 2;
    }

    @Override
    public String getCommandUsage(ICommandSender commandSender) {
        return "commands.jascanspawnhere.usage";
    }

    @Override
    public void process(ICommandSender commandSender, String[] stringArgs) {
        if (stringArgs.length == 0 || stringArgs.length > 2) {
            throw new WrongUsageException("commands.jascanspawnhere.usage", new Object[0]);
        }

        EntityPlayer targetPlayer = stringArgs.length == 1 ? getPlayer(commandSender,
                commandSender.getCommandSenderName()) : getPlayer(commandSender, stringArgs[0]);
        String entityName = stringArgs.length == 1 ? stringArgs[0] : stringArgs[1];
        if (!isValidEntityName(entityName)) {
            throw new WrongUsageException("commands.jascanspawnhere.entitynotfound", new Object[0]);
        }

		EntityLiving entity = getTargetEntity(entityName, targetPlayer);
		LivingGroupRegistry groupRegistry = JustAnotherSpawner.worldSettings().livingGroupRegistry();
		LivingHandlerRegistry handlerRegistry = JustAnotherSpawner.worldSettings().livingHandlerRegistry();
		List<LivingHandler> livingHandlers = handlerRegistry.getLivingHandlers(groupRegistry.EntityClasstoJASName
				.get(entity.getClass()));
		if (livingHandlers.isEmpty()) {
			throw new WrongUsageException("commands.jascanspawnhere.entityhasnogroups", new Object[0]);
		}
		for (LivingHandler livingHandler : livingHandlers) {
			CreatureType livingType = JustAnotherSpawner.worldSettings().creatureTypeRegistry()
					.getCreatureType(livingHandler.creatureTypeID);
			if (livingType == null) {
				commandSender.addChatMessage(new ChatComponentText(String.format(
						"Entity %s is of type NONE and thus will never spawn.", entityName)));
				return;
			}
			CountInfo countInfo = CustomSpawner.determineCountInfo(entity.worldObj);

			/* Get local spawnlist. Reminder: Biomes are only used when a structure is absent or empty */
			boolean isBiome = false;
			List<SpawnListEntry> spawnlistentries = new ArrayList<SpawnListEntry>(3);
			String locationName = getMatchingStructureSpawnListEntries(livingHandler.livingID, entity, spawnlistentries);
			String structureName = locationName;
			if (spawnlistentries.isEmpty()) {
				isBiome = true;
				locationName = getMatchingBiomeSpawnListEntries(livingHandler.livingID, entity, livingType,
						spawnlistentries);
			}

			if (spawnlistentries.isEmpty()) {
				spawnlistentries.add(null);
			}

			StringBuilder resultMessage = new StringBuilder();
			if (livingHandlers.size() > 1) {
				resultMessage.append("{Group ").append(livingHandler.livingID).append(": ");
			}
			Iterator<SpawnListEntry> iterator = spawnlistentries.iterator();
			while (iterator.hasNext()) {
				SpawnListEntry spawnListEntry = iterator.next();
				if (spawnlistentries.size() > 1) {
					resultMessage.append("{");
				}
				resultMessage.append(
						canEntitySpawnHere(targetPlayer, entity, livingHandler, livingType, spawnListEntry, entityName,
								countInfo)).append(": ");
				resultMessage.append(canEntityTypeSpawnHere(targetPlayer, entity, livingType, countInfo)).append(" ");
				resultMessage.append(canLivingHandlerSpawnHere(targetPlayer, entity, livingHandler, countInfo)).append(
						" ");

				if (!isBiome) {
					resultMessage.append(canSpawnListSpawnHere(targetPlayer, entity, livingHandler, spawnListEntry,
							locationName, false, countInfo));
				} else {
					/* If structureName is !null a structure is present but the spawnlist was empty so default to biome */
					if (structureName != null) {
						resultMessage.append("\u00A7b").append("Empty S: ").append(structureName)
								.append(" spawnlist defaults to biome. ").append("\u00A7r");
					}
					resultMessage.append(canSpawnListSpawnHere(targetPlayer, entity, livingHandler, spawnListEntry,
							locationName, true, countInfo));
				}

				if (spawnlistentries.size() > 1) {
					resultMessage.append("}");
					if (iterator.hasNext()) {
						resultMessage.append(" ");
					}
				}
			}
			if (livingHandlers.size() > 1) {
				resultMessage.append("}");
			}
			commandSender.addChatMessage(new ChatComponentText(resultMessage.toString()));
		}
	}

	private boolean isValidEntityName(String entityName) {
		LivingGroupRegistry livingGroupRegistry = JustAnotherSpawner.worldSettings().livingGroupRegistry();
		for (String mapping : livingGroupRegistry.JASNametoEntityClass.keySet()) {
			if (entityName.equals(mapping)) {
				return true;
			}
		}
		return false;
	}

    private EntityLiving getTargetEntity(String entityName, EntityPlayer targetPlayer) {
        EntityLiving entity;
        try {
        	LivingGroupRegistry livingGroupRegistry = JustAnotherSpawner.worldSettings().livingGroupRegistry();
        	
            @SuppressWarnings("unchecked")
            Class<? extends EntityLiving> entityClass = livingGroupRegistry.JASNametoEntityClass.get(entityName);
            entity = (EntityLiving) LivingHelper.instantiateEntity(entityClass, targetPlayer.worldObj);
        } catch (Exception exception) {
            throw new WrongUsageException("commands.jascanspawnhere.cannotinstantiateentity", new Object[0]);
        }
        entity.setLocationAndAngles(targetPlayer.posX, targetPlayer.posY, targetPlayer.posZ, targetPlayer.rotationYaw,
                targetPlayer.rotationPitch);
        return entity;
    }

    private String getMatchingStructureSpawnListEntries(String livingGroupID, EntityLiving entity,
            Collection<SpawnListEntry> matchingSpawnListEntries) {
        String structureName;
        for (StructureHandler StructureHandler : JustAnotherSpawner.worldSettings().structureHandlerRegistry()
                .handlers()) {
            structureName = StructureHandler.getStructure(entity.worldObj, (int) entity.posX, (int) entity.posY,
                    (int) entity.posZ);
            if (structureName != null) {
                for (String structureKey : StructureHandler.getStructureKeys()) {
                    if (structureName.equals(structureKey)) {
                        for (SpawnListEntry entry : StructureHandler.getStructureSpawnList(structureKey)) {
                            if (livingGroupID.equals(entry.livingGroupID)) {
                                matchingSpawnListEntries.add(entry);
                            }
                        }
                        return structureKey;
                    }
                }
            }
        }
        return null;
    }

    private String getMatchingBiomeSpawnListEntries(String livingGroupID, Entity entity, CreatureType livingType,
            Collection<SpawnListEntry> matchingSpawnListEntries) {
        BiomeGenBase biome = entity.worldObj.getBiomeGenForCoords((int) entity.posX, (int) entity.posZ);
        String packageBiome = BiomeHelper.getPackageName(biome);

        BiomeSpawnListRegistry biomeSpawnListRegistry = JustAnotherSpawner.worldSettings().biomeSpawnListRegistry();
        for (SpawnListEntry spawnListEntry : biomeSpawnListRegistry.getSpawnListFor(livingType.typeID, packageBiome)) {
            if (spawnListEntry.livingGroupID.equals(livingGroupID)) {
                matchingSpawnListEntries.add(spawnListEntry);
            }
        }
        String shortName = JustAnotherSpawner.worldSettings().biomeGroupRegistry().biomePckgToMapping().get(packageBiome);
        return shortName == null ? biome.biomeName : shortName;
    }

    private String canEntitySpawnHere(EntityPlayer targetPlayer, EntityLiving entity, LivingHandler livingHandler,
            CreatureType livingType, SpawnListEntry spawnListEntry, String entityName, CountInfo countInfo) {
        String successMessage = "\u00A7a".concat(entityName).concat(" can spawn").concat("\u00A7r");
        String failureMessage = "\u00A7c".concat(entityName).concat(" cannot spawn").concat("\u00A7r");
        /* Check Entity Type */
        {
            boolean tempSpawning = targetPlayer.preventEntitySpawning;
            targetPlayer.preventEntitySpawning = false;
			Tags tags = new Tags(entity.worldObj, countInfo, (int) entity.posX, (int) entity.posY, (int) entity.posZ);

			boolean canTypeSpawn = livingType.canSpawnAtLocation(entity.worldObj, tags, (int) entity.posX, (int) entity.posY,
					(int) entity.posZ);
            targetPlayer.preventEntitySpawning = tempSpawning;

            if (canTypeSpawn == false) {
                return failureMessage;
            }
        }

        boolean tempSpawning = targetPlayer.preventEntitySpawning;
        targetPlayer.preventEntitySpawning = false;
        boolean canSpawn = false;
        if (spawnListEntry != null) {
            for (int i = 0; i < SIMULATION_TRIALS; i++) {
                if (livingHandler.getCanSpawnHere(entity, spawnListEntry, countInfo)) {
                    canSpawn = true;
                    break;
                }
            }
        }
        targetPlayer.preventEntitySpawning = tempSpawning;
        if (canSpawn) {
            return successMessage;
        } else {
            return failureMessage;
        }
    }

    private String canEntityTypeSpawnHere(EntityPlayer targetPlayer, EntityLiving entity, CreatureType livingType, CountInfo countInfo) {
		Tags tags = new Tags(entity.worldObj, countInfo, (int) entity.posX, (int) entity.posY, (int) entity.posZ);

        boolean tempSpawning = targetPlayer.preventEntitySpawning;
        targetPlayer.preventEntitySpawning = false;
        boolean canSpawn = false;
		for (int i = 0; i < SIMULATION_TRIALS; i++) {
			if (livingType.canSpawnAtLocation(entity.worldObj, tags, (int) entity.posX, (int) entity.posY,
					(int) entity.posZ)) {
				canSpawn = true;
				break;
			}
		}
        targetPlayer.preventEntitySpawning = tempSpawning;

        if (canSpawn) {
            return "\u00A7a".concat(livingType.typeID).concat(" can spawn.").concat("\u00A7r");
        } else {
            return "\u00A7c".concat(livingType.typeID).concat(" cannot spawn.").concat("\u00A7r");
        }
    }

    private String canLivingHandlerSpawnHere(EntityPlayer targetPlayer, EntityLiving entity, LivingHandler livingHandler, CountInfo countInfo) {
        boolean tempSpawning = targetPlayer.preventEntitySpawning;
        targetPlayer.preventEntitySpawning = false;

        boolean canSpawn = false;
        for (int i = 0; i < SIMULATION_TRIALS; i++) {
            if (livingHandler.isValidLiving(entity, countInfo)) {
                canSpawn = true;
                break;
            }
        }
        targetPlayer.preventEntitySpawning = tempSpawning;

        if (canSpawn) {
            return "\u00A7a".concat("Livinghandler can spawn.").concat("\u00A7r");
        } else {
            return "\u00A7c".concat("Livinghandler cannot spawn.").concat("\u00A7r");
        }
    }

    private String canSpawnListSpawnHere(EntityPlayer targetPlayer, EntityLiving entity, LivingHandler livingHandler,
            SpawnListEntry spawnListEntry, String locationName, boolean isBiome, CountInfo countInfo) {
        if (spawnListEntry == null) {
            return "\u00A7c".concat("Entity not in B:").concat(locationName).concat(" spawnlist").concat("\u00A7r");
        }

        if (!spawnListEntry.getOptionalSpawning().isPresent()) {
            return "\u00A7a".concat("No ").concat(isBiome ? "B:" : "S:").concat(locationName).concat(" tags.")
                    .concat("\u00A7r");
        }

        boolean tempSpawning = targetPlayer.preventEntitySpawning;
        targetPlayer.preventEntitySpawning = false;
        boolean canSpawn = false;
        for (int i = 0; i < SIMULATION_TRIALS; i++) {
            if (livingHandler.isValidSpawnList(entity, spawnListEntry, countInfo)) {
                canSpawn = true;
                break;
            }
        }
        targetPlayer.preventEntitySpawning = tempSpawning;

        if (canSpawn) {
            return "\u00A7a".concat("Can spawn in ").concat(isBiome ? "B:" : "S:").concat(locationName).concat(".")
                    .concat("\u00A7r");
        } else {
            return "\u00A7c".concat("Cannot spawn in ").concat(isBiome ? "B:" : "S:").concat(locationName).concat(".")
                    .concat("\u00A7r");
        }
    }

    /**
     * Adds the strings available in this command to the given list of tab completion options.
     */
    @Override
    public List<String> getTabCompletions(ICommandSender commandSender, String[] stringArgs) {
        stringArgs = correctedParseArgs(stringArgs, false);
        List<String> tabCompletions = new ArrayList<String>();
        if (stringArgs.length == 1) {
            addPlayerUsernames(tabCompletions);
            addEntityNames(tabCompletions);
        } else if (stringArgs.length == 2) {
            addEntityNames(tabCompletions);
        }

        if (!tabCompletions.isEmpty()) {
            return getStringsMatchingLastWord(stringArgs, tabCompletions);
        } else {
            return tabCompletions;
        }
    }
}