package fi.dy.masa.enderutilities.util.teleport;

import java.util.List;
import java.util.UUID;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityMinecartContainer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.DamageSource;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.BossInfoServer;
import net.minecraft.world.Teleporter;
import net.minecraft.world.World;
import net.minecraft.world.WorldProviderEnd;
import net.minecraft.world.WorldServer;
import net.minecraft.world.end.DragonFightManager;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.living.EnderTeleportEvent;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import fi.dy.masa.enderutilities.EnderUtilities;
import fi.dy.masa.enderutilities.item.base.ItemModule.ModuleType;
import fi.dy.masa.enderutilities.network.PacketHandler;
import fi.dy.masa.enderutilities.network.message.MessageAddEffects;
import fi.dy.masa.enderutilities.util.ChunkLoading;
import fi.dy.masa.enderutilities.util.EntityUtils;
import fi.dy.masa.enderutilities.util.PositionHelper;
import fi.dy.masa.enderutilities.util.PositionUtils;
import fi.dy.masa.enderutilities.util.nbt.TargetData;
import fi.dy.masa.enderutilities.util.nbt.UtilItemModular;

public class TeleportEntity
{
    private static boolean teleportInProgress;

    public static boolean isTeleportInProgress()
    {
        return teleportInProgress;
    }

    public static void addTeleportSoundsAndParticles(World world, double x, double y, double z)
    {
        if (world.isRemote == false)
        {
            PacketHandler.INSTANCE.sendToAllAround(new MessageAddEffects(MessageAddEffects.EFFECT_TELEPORT, MessageAddEffects.PARTICLES | MessageAddEffects.SOUND, x, y, z),
                    new NetworkRegistry.TargetPoint(world.provider.getDimension(), x, y, z, 24.0d));
        }
    }

    public static boolean canTeleportEntity(Entity entity)
    {
        return EntityUtils.doesEntityStackHaveBlacklistedEntities(entity) == false;
    }

    public static boolean teleportEntityRandomly(Entity entity, double maxDist)
    {
        World world = entity.getEntityWorld();

        if (canTeleportEntity(entity) == false || world.isRemote)
        {
            return false;
        }

        double deltaYaw = 0.0d;
        double deltaPitch = 0.0d;
        double x = 0.0d;
        double y = 0.0d;
        double z = 0.0d;
        maxDist = maxDist - (world.rand.nextFloat() * maxDist / 2.0d);

        // Try to find a free spot (non-colliding with blocks)
        for (int i = 0; i < 20; i++)
        {
            deltaYaw = world.rand.nextFloat() * 2d * Math.PI;
            //deltaPitch = ((90.0d - (Math.random() * 180.0d)) / 180.0d) * Math.PI; // free range on the y-direction
            deltaPitch = world.rand.nextFloat() * 0.5d * Math.PI; // only from the same level upwards
            x = entity.posX;
            y = entity.posY;
            z = entity.posZ;
            x += Math.cos(deltaPitch) * Math.cos(deltaYaw) * maxDist;
            z += Math.cos(deltaPitch) * Math.sin(deltaYaw) * maxDist;
            y += Math.sin(deltaPitch) * maxDist;

            if (entity.getEntityBoundingBox() != null && world.getCollisionBoxes(entity, entity.getEntityBoundingBox()).isEmpty())
            {
                // Sound and particles on the original location
                addTeleportSoundsAndParticles(world, entity.posX, entity.posY, entity.posZ);

                entity.setLocationAndAngles(x, y, z, entity.rotationYaw, entity.rotationPitch);

                // Sound and particles on the new, destination location.
                addTeleportSoundsAndParticles(world, x, y, z);
                return true;
            }
        }

        return false;
    }

    public static boolean teleportEntityWithProjectile(Entity entity, Entity projectile, RayTraceResult rayTraceResult, float teleportDamage, boolean allowMounts, boolean allowRiders)
    {
        if (canTeleportEntity(entity) == false)
        {
            return false;
        }

        PositionHelper pos = new PositionHelper(rayTraceResult, projectile);

        // Hit a block, offset the position to not collide with the block
        if (rayTraceResult.typeOfHit == RayTraceResult.Type.BLOCK)
        {
            pos.adjustPositionToTouchFace(entity, rayTraceResult.sideHit);
        }

        Entity entNew = teleportEntity(entity, pos.posX, pos.posY, pos.posZ, projectile.getEntityWorld().provider.getDimension(), allowMounts, allowRiders);

        if (entNew != null)
        {
            if (teleportDamage != 0.0f)
            {
                // Inflict fall damage to the bottom most entity
                Entity bottom = EntityUtils.getBottomEntity(entNew);

                if (bottom instanceof EntityLivingBase)
                {
                    bottom.attackEntityFrom(DamageSource.FALL, teleportDamage);
                }
            }

            entNew.fallDistance = 0.0f;
            return true;
        }

        return false;
    }

    public static Entity teleportEntityUsingModularItem(Entity entity, ItemStack stack, boolean allowMounts, boolean allowRiders)
    {
        return teleportEntityUsingItem(entity, UtilItemModular.getSelectedModuleStack(stack, ModuleType.TYPE_LINKCRYSTAL), allowMounts, allowRiders);
    }

    public static Entity teleportEntityUsingItem(Entity entity, ItemStack stack, boolean allowMounts, boolean allowRiders)
    {
        TargetData target = TargetData.getTargetFromItem(stack);

        if (target != null)
        {
            return teleportEntityUsingTarget(entity, target, true, allowMounts, allowRiders);
        }

        return null;
    }

    public static Entity teleportEntityUsingTarget(Entity entity, TargetData target, boolean adjustTargetPosition, boolean allowMounts, boolean allowRiders)
    {
        if (target == null || entity == null)
        {
            return null;
        }

        if (adjustTargetPosition)
        {
            target = PositionUtils.adjustTargetPosition(target, entity);
        }

        if (target.hasRotation)
        {
            entity.setLocationAndAngles(entity.posX, entity.posY, entity.posZ, target.yaw, target.pitch);
        }

        return teleportEntity(entity, target.dPosX, target.dPosY, target.dPosZ, target.dimension, allowMounts, allowRiders);
    }

    public static Entity teleportEntity(Entity entityIn, double x, double y, double z, int dimDst, boolean allowMounts, boolean allowRiders)
    {
        if (entityIn == null || entityIn.getEntityWorld() == null || entityIn.getEntityWorld().isRemote) { return null; }
        if (allowMounts == false && entityIn.isRiding()) { return null; }
        if (allowRiders == false && entityIn.isBeingRidden()) { return null; }
        if (canTeleportEntity(entityIn) == false) { return null; }

        UUID uuidOriginal = entityIn.getUniqueID();
        Entity entity = EntityUtils.getBottomEntity(entityIn);
        List<Entity> passengers = null;
        boolean ridden = false;
        boolean reCreate = EntityUtils.doesEntityStackHavePlayers(entityIn);
        // This method gets called recursively when teleporting the passengers.
        // It is always called on the bottom entity on all of the passengers however, so we can
        // recognize the original target entity from a 'stack' with this check.
        // Note also that if the original target was already the bottom most entity, then
        // this extra logic isn't even necessary for it.
        boolean isOriginal = entity != entityIn;

        // Teleport all the entities in this 'stack', starting from the bottom most entity
        ridden = entity.isBeingRidden();

        if (ridden)
        {
            passengers = entity.getPassengers();

            for (Entity passenger : passengers)
            {
                passenger.dismountRidingEntity();
            }
        }

        Entity teleported = teleportEntity(entity, x, y, z, dimDst, reCreate);

        if (teleported == null)
        {
            return null;
        }

        teleported.fallDistance = 0.0f;

        if (ridden)
        {
            for (Entity passenger : passengers)
            {
                Entity teleportedPassenger = teleportEntity(passenger, x, y, z, dimDst, allowMounts, allowRiders);

                if (teleportedPassenger != null)
                {
                    teleportedPassenger.startRiding(teleported, true);
                }
            }
        }

        if (isOriginal)
        {
            teleported = EntityUtils.findEntityFromStackByUUID(teleported, uuidOriginal);
        }

        return teleported;
    }

    private static Entity teleportEntity(Entity entity, double x, double y, double z, int dimDst, boolean forceRecreate)
    {
        if (entity == null || entity.isEntityAlive() == false || canTeleportEntity(entity) == false || entity.getEntityWorld().isRemote)
        {
            return null;
        }

        // Post the event and check if the teleport should be allowed
        if (entity instanceof EntityLivingBase)
        {
            EnderTeleportEvent event = new EnderTeleportEvent((EntityLivingBase) entity, x, y, z, 0.0f);

            if (MinecraftForge.EVENT_BUS.post(event))
            {
                return null;
            }
        }

        // Sound and particles on the original location
        addTeleportSoundsAndParticles(entity.getEntityWorld(), entity.posX, entity.posY, entity.posZ);

        if (entity instanceof EntityLiving)
        {
            ((EntityLiving) entity).setMoveForward(0.0f);
            ((EntityLiving) entity).getNavigator().clearPath();
        }

        if (entity.getEntityWorld().provider.getDimension() != dimDst)
        {
            entity = teleportEntityToDimension(entity, x, y, z, dimDst);
        }
        else
        {
            entity = teleportEntityInsideSameDimension(entity, x, y, z);
        }

        if (entity != null)
        {
            // Final position
            addTeleportSoundsAndParticles(entity.getEntityWorld(), x, y, z);
        }

        return entity;
    }

    private static Entity teleportEntityInsideSameDimension(Entity entity, double x, double y, double z)
    {
        // Load the chunk first
        entity.getEntityWorld().getChunk((int) Math.floor(x / 16D), (int) Math.floor(z / 16D));

        entity.setLocationAndAngles(x, y, z, entity.rotationYaw, entity.rotationPitch);
        entity.setPositionAndUpdate(x, y, z);
        return entity;
    }

    private static Entity teleportEntityToDimension(Entity entity, double x, double y, double z, int dimDst)
    {
        MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
        WorldServer worldDst = server.getWorld(dimDst);

        teleportInProgress = true;

        if (worldDst == null || ForgeHooks.onTravelToDimension(entity, dimDst) == false)
        {
            teleportInProgress = false;
            return null;
        }

        teleportInProgress = false;

        // Load the chunk first
        int chunkX = (int) Math.floor(x / 16D);
        int chunkZ = (int) Math.floor(z / 16D);
        ChunkLoading.getInstance().loadChunkForcedWithModTicket(dimDst, chunkX, chunkZ, 10);

        if (entity instanceof EntityPlayerMP)
        {
            EntityPlayerMP player = (EntityPlayerMP) entity;
            World worldOld = player.getEntityWorld();
            DummyTeleporter teleporter = new DummyTeleporter(worldDst);

            player.setLocationAndAngles(x, y, z, player.rotationYaw, player.rotationPitch);
            server.getPlayerList().transferPlayerToDimension(player, dimDst, teleporter);

            // Teleporting FROM The End, remove the boss bar that would otherwise get stuck on
            if (worldOld.provider instanceof WorldProviderEnd)
            {
                removeDragonBossBarHack(player, (WorldProviderEnd) worldOld.provider);
            }

            player.setPositionAndUpdate(x, y, z);
            worldDst.updateEntityWithOptionalForce(player, false);
            player.addExperienceLevel(0);
            player.setPlayerHealthUpdated();
            // TODO update food level?
        }
        else
        {
            WorldServer worldSrc = (WorldServer) entity.getEntityWorld();

            // FIXME ugly special case to prevent the chest minecart etc from duping items
            if (entity instanceof EntityMinecartContainer)
            {
                ((EntityMinecartContainer) entity).setDropItemsWhenDead(false);
            }

            Entity entityNew = EntityList.newEntity(entity.getClass(), worldDst);

            if (entityNew != null)
            {
                EntityUtils.copyDataFromOld(entityNew, entity);
                entityNew.setLocationAndAngles(x, y, z, entity.rotationYaw, entity.rotationPitch);

                boolean flag = entityNew.forceSpawn;
                entityNew.forceSpawn = true;
                worldDst.spawnEntity(entityNew);
                entityNew.forceSpawn = flag;

                worldDst.updateEntityWithOptionalForce(entityNew, false);
                entity.isDead = true;

                worldSrc.removeEntity(entity);
                worldSrc.updateEntityWithOptionalForce(entity, false);

                worldSrc.resetUpdateEntityTick();
                worldDst.resetUpdateEntityTick();
            }

            entity = entityNew;
        }

        return entity;
    }

    private static void removeDragonBossBarHack(EntityPlayerMP player, WorldProviderEnd provider)
    {
        // Somewhat ugly way to clear the Boss Info stuff when teleporting FROM The End
        DragonFightManager manager = provider.getDragonFightManager();

        if (manager != null)
        {
            try
            {
                BossInfoServer bossInfo = ObfuscationReflectionHelper.getPrivateValue(DragonFightManager.class, manager, "field_186109_c");

                if (bossInfo != null)
                {
                    bossInfo.removePlayer(player);
                }
            }
            catch (Exception e)
            {
                EnderUtilities.logger.warn("TP: Failed to get DragonFightManager#bossInfo");
            }
        }
    }

    private static class DummyTeleporter extends Teleporter
    {
        public DummyTeleporter(WorldServer world)
        {
            super(world);
        }

        @Override
        public boolean makePortal(Entity entityIn)
        {
            return true;
        }

        @Override
        public boolean placeInExistingPortal(Entity entityIn, float rotationYaw)
        {
            return true;
        }

        @Override
        public void removeStalePortalLocations(long worldTime)
        {
            // NO-OP
        }

        @Override
        public void placeInPortal(Entity entityIn, float rotationYaw)
        {
        }
    }
}