package gregtech.common.entities;

import java.util.List;
import java.util.UUID;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.monster.EntityEnderman;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.play.server.S2BPacketChangeGameState;
import net.minecraft.potion.Potion;
import net.minecraft.util.*;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.util.FakePlayerFactory;

import com.mojang.authlib.GameProfile;

import gregtech.api.enums.ParticleFX;
import gregtech.api.objects.ItemData;
import gregtech.api.util.GT_OreDictUnificator;
import gregtech.api.util.GT_Utility;
import gregtech.api.util.WorldSpawnedEventBuilder;

public class GT_Entity_Arrow extends EntityArrow {

    private int mHitBlockX = -1;
    private int mHitBlockY = -1;
    private int mHitBlockZ = -1;
    private Block mHitBlock = Blocks.air;
    private int mHitBlockMeta = 0;
    private boolean inGround = false;
    private int mTicksAlive = 0;
    private int ticksInAir = 0;
    private int mKnockback = 0;
    private ItemStack mArrow = null;

    public GT_Entity_Arrow(World aWorld) {
        super(aWorld);
    }

    public GT_Entity_Arrow(World aWorld, double aX, double aY, double aZ) {
        super(aWorld, aX, aY, aZ);
    }

    public GT_Entity_Arrow(World aWorld, EntityLivingBase aEntity, float aSpeed) {
        super(aWorld, aEntity, aSpeed);
    }

    public GT_Entity_Arrow(EntityArrow aArrow, ItemStack aStack) {
        super(aArrow.worldObj);
        NBTTagCompound tNBT = new NBTTagCompound();
        aArrow.writeToNBT(tNBT);
        readFromNBT(tNBT);
        setArrowItem(aStack);
    }

    @Override
    public void onUpdate() {
        onEntityUpdate();
        if ((this.mArrow == null) && (!this.worldObj.isRemote)) {
            setDead();
            return;
        }
        Entity tShootingEntity = this.shootingEntity;
        if ((this.prevRotationPitch == 0.0F) && (this.prevRotationYaw == 0.0F)) {
            float f = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ);
            this.prevRotationYaw = (this.rotationYaw = (float) (Math.atan2(this.motionX, this.motionZ) * 180.0D
                / Math.PI));
            this.prevRotationPitch = (this.rotationPitch = (float) (Math.atan2(this.motionY, f) * 180.0D / Math.PI));
        }
        if (this.mTicksAlive++ == 3000) {
            setDead();
        }
        Block tBlock = this.worldObj.getBlock(this.mHitBlockX, this.mHitBlockY, this.mHitBlockZ);
        if (tBlock.getMaterial() != Material.air) {
            tBlock.setBlockBoundsBasedOnState(this.worldObj, this.mHitBlockX, this.mHitBlockY, this.mHitBlockZ);
            AxisAlignedBB axisalignedbb = tBlock
                .getCollisionBoundingBoxFromPool(this.worldObj, this.mHitBlockX, this.mHitBlockY, this.mHitBlockZ);
            if ((axisalignedbb != null)
                && (axisalignedbb.isVecInside(Vec3.createVectorHelper(this.posX, this.posY, this.posZ)))) {
                this.inGround = true;
            }
        }
        if (this.arrowShake > 0) {
            this.arrowShake -= 1;
        }
        if (this.inGround) {
            int j = this.worldObj.getBlockMetadata(this.mHitBlockX, this.mHitBlockY, this.mHitBlockZ);
            if ((tBlock != this.mHitBlock) || (j != this.mHitBlockMeta)) {
                this.inGround = false;
                this.motionX *= this.rand.nextFloat() * 0.2F;
                this.motionY *= this.rand.nextFloat() * 0.2F;
                this.motionZ *= this.rand.nextFloat() * 0.2F;
                this.mTicksAlive = 0;
                this.ticksInAir = 0;
            }
        } else {
            this.ticksInAir += 1;
            Vec3 vec31 = Vec3.createVectorHelper(this.posX, this.posY, this.posZ);
            Vec3 vec3 = Vec3
                .createVectorHelper(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ);
            MovingObjectPosition tVector = this.worldObj.func_147447_a(vec31, vec3, false, true, false);
            vec31 = Vec3.createVectorHelper(this.posX, this.posY, this.posZ);
            vec3 = Vec3
                .createVectorHelper(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ);
            if (tVector != null) {
                vec3 = Vec3.createVectorHelper(tVector.hitVec.xCoord, tVector.hitVec.yCoord, tVector.hitVec.zCoord);
            }
            Entity tHitEntity = null;
            List<Entity> tAllPotentiallyHitEntities = this.worldObj.getEntitiesWithinAABBExcludingEntity(
                this,
                this.boundingBox.addCoord(this.motionX, this.motionY, this.motionZ)
                    .expand(1.0D, 1.0D, 1.0D));
            double tLargestDistance = Double.MAX_VALUE;
            for (Entity tAllPotentiallyHitEntity : tAllPotentiallyHitEntities) {
                Entity entity1 = tAllPotentiallyHitEntity;
                if ((entity1.canBeCollidedWith()) && ((entity1 != tShootingEntity) || (this.ticksInAir >= 5))) {
                    AxisAlignedBB axisalignedbb1 = entity1.boundingBox.expand(0.3D, 0.3D, 0.3D);
                    MovingObjectPosition movingobjectposition1 = axisalignedbb1.calculateIntercept(vec31, vec3);
                    if (movingobjectposition1 != null) {
                        double tDistance = vec31.distanceTo(movingobjectposition1.hitVec);
                        if (tDistance < tLargestDistance) {
                            tHitEntity = entity1;
                            tLargestDistance = tDistance;
                        }
                    }
                }
            }
            if (tHitEntity != null) {
                tVector = new MovingObjectPosition(tHitEntity);
            }
            if ((tVector != null) && ((tVector.entityHit instanceof EntityPlayer entityplayer))) {
                if ((entityplayer.capabilities.disableDamage) || (((tShootingEntity instanceof EntityPlayer))
                    && (!((EntityPlayer) tShootingEntity).canAttackPlayer(entityplayer)))) {
                    tVector = null;
                }
            }
            if (tVector != null) {
                if (tVector.entityHit != null) {
                    ItemData tData = GT_OreDictUnificator.getItemData(this.mArrow);

                    float tMagicDamage = (tVector.entityHit instanceof EntityLivingBase)
                        ? EnchantmentHelper
                            .func_152377_a(this.mArrow, ((EntityLivingBase) tVector.entityHit).getCreatureAttribute())
                        : 0.0F;
                    float tDamage = MathHelper.ceiling_double_int(
                        MathHelper.sqrt_double(
                            this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ)
                            * (getDamage()
                                + ((tData != null) && (tData.mMaterial != null) && (tData.mMaterial.mMaterial != null)
                                    ? tData.mMaterial.mMaterial.mToolQuality / 2.0F - 1.0F
                                    : 0.0F)));
                    if (getIsCritical()) {
                        tDamage += this.rand.nextInt((int) (tDamage / 2.0D + 2.0D));
                    }
                    int tFireDamage = (isBurning() ? 5 : 0)
                        + 4 * EnchantmentHelper.getEnchantmentLevel(Enchantment.fireAspect.effectId, this.mArrow);
                    int tKnockback = this.mKnockback
                        + EnchantmentHelper.getEnchantmentLevel(Enchantment.knockback.effectId, this.mArrow);
                    int tHitTimer = -1;

                    int[] tDamages = onHitEntity(
                        tVector.entityHit,
                        tShootingEntity == null ? this : tShootingEntity,
                        this.mArrow == null ? new ItemStack(Items.arrow, 1) : this.mArrow,
                        (int) (tDamage * 2.0F),
                        (int) (tMagicDamage * 2.0F),
                        tKnockback,
                        tFireDamage,
                        tHitTimer);
                    if (tDamages != null) {
                        tDamage = tDamages[0] / 2.0F;
                        tMagicDamage = tDamages[1] / 2.0F;
                        tKnockback = tDamages[2];
                        tFireDamage = tDamages[3];
                        tHitTimer = tDamages[4];
                        if ((tFireDamage > 0) && (!(tVector.entityHit instanceof EntityEnderman))) {
                            tVector.entityHit.setFire(tFireDamage);
                        }
                        if ((!(tHitEntity instanceof EntityPlayer))
                            && (EnchantmentHelper.getEnchantmentLevel(Enchantment.looting.effectId, this.mArrow) > 0)) {
                            EntityPlayer tPlayer = null;
                            if ((this.worldObj instanceof WorldServer)) {
                                tPlayer = FakePlayerFactory.get(
                                    (WorldServer) this.worldObj,
                                    new GameProfile(
                                        new UUID(0L, 0L),
                                        (tShootingEntity instanceof EntityLivingBase)
                                            ? tShootingEntity.getCommandSenderName()
                                            : "Arrow"));
                            }
                            if (tPlayer != null) {
                                tPlayer.inventory.currentItem = 0;
                                tPlayer.inventory.setInventorySlotContents(0, getArrowItem());
                                tShootingEntity = tPlayer;
                                tPlayer.setDead();
                            }
                        }
                        DamageSource tDamageSource = DamageSource
                            .causeArrowDamage(this, tShootingEntity == null ? this : tShootingEntity);
                        if ((tDamage + tMagicDamage > 0.0F)
                            && (tVector.entityHit.attackEntityFrom(tDamageSource, tDamage + tMagicDamage))) {
                            if ((tVector.entityHit instanceof EntityLivingBase tHitLivingEntity)) {
                                if (tHitTimer >= 0) {
                                    tVector.entityHit.hurtResistantTime = tHitTimer;
                                }
                                if (((tVector.entityHit instanceof EntityCreeper)) && (EnchantmentHelper
                                    .getEnchantmentLevel(Enchantment.fireAspect.effectId, this.mArrow) > 0)) {
                                    ((EntityCreeper) tVector.entityHit).func_146079_cb();
                                }
                                if (!this.worldObj.isRemote) {
                                    tHitLivingEntity
                                        .setArrowCountInEntity(tHitLivingEntity.getArrowCountInEntity() + 1);
                                }
                                if (tKnockback > 0) {
                                    float tKnockbackDivider = MathHelper
                                        .sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ);
                                    if (tKnockbackDivider > 0.0F) {
                                        tHitLivingEntity.addVelocity(
                                            this.motionX * tKnockback * 0.6000000238418579D / tKnockbackDivider,
                                            0.1D,
                                            this.motionZ * tKnockback * 0.6000000238418579D / tKnockbackDivider);
                                    }
                                }
                                GT_Utility.GT_EnchantmentHelper.applyBullshitA(
                                    tHitLivingEntity,
                                    tShootingEntity == null ? this : tShootingEntity,
                                    this.mArrow);
                                GT_Utility.GT_EnchantmentHelper.applyBullshitB(
                                    (tShootingEntity instanceof EntityLivingBase) ? (EntityLivingBase) tShootingEntity
                                        : null,
                                    tHitLivingEntity,
                                    this.mArrow);
                                if ((tShootingEntity != null) && (tHitLivingEntity != tShootingEntity)
                                    && ((tHitLivingEntity instanceof EntityPlayer))
                                    && ((tShootingEntity instanceof EntityPlayerMP))) {
                                    ((EntityPlayerMP) tShootingEntity).playerNetServerHandler
                                        .sendPacket(new S2BPacketChangeGameState(6, 0.0F));
                                }
                            }
                            if (((tShootingEntity instanceof EntityPlayer)) && (tMagicDamage > 0.0F)) {
                                ((EntityPlayer) tShootingEntity).onEnchantmentCritical(tVector.entityHit);
                            }
                            if ((!(tVector.entityHit instanceof EntityEnderman))
                                || (((EntityEnderman) tVector.entityHit).getActivePotionEffect(Potion.weakness)
                                    != null)) {
                                if (tFireDamage > 0) {
                                    tVector.entityHit.setFire(tFireDamage);
                                }
                                playSound("random.bowhit", 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
                                setDead();
                            }
                        } else {
                            this.motionX *= -0.1000000014901161D;
                            this.motionY *= -0.1000000014901161D;
                            this.motionZ *= -0.1000000014901161D;
                            this.rotationYaw += 180.0F;
                            this.prevRotationYaw += 180.0F;
                            this.ticksInAir = 0;
                        }
                    }
                } else {
                    this.mHitBlockX = tVector.blockX;
                    this.mHitBlockY = tVector.blockY;
                    this.mHitBlockZ = tVector.blockZ;
                    this.mHitBlock = this.worldObj.getBlock(this.mHitBlockX, this.mHitBlockY, this.mHitBlockZ);
                    this.mHitBlockMeta = this.worldObj
                        .getBlockMetadata(this.mHitBlockX, this.mHitBlockY, this.mHitBlockZ);
                    this.motionX = ((float) (tVector.hitVec.xCoord - this.posX));
                    this.motionY = ((float) (tVector.hitVec.yCoord - this.posY));
                    this.motionZ = ((float) (tVector.hitVec.zCoord - this.posZ));
                    float f2 = MathHelper.sqrt_double(
                        this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ);
                    this.posX -= this.motionX / f2 * 0.0500000007450581D;
                    this.posY -= this.motionY / f2 * 0.0500000007450581D;
                    this.posZ -= this.motionZ / f2 * 0.0500000007450581D;
                    playSound("random.bowhit", 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
                    this.inGround = true;
                    this.arrowShake = 7;
                    setIsCritical(false);
                    if (this.mHitBlock.getMaterial() != Material.air) {
                        this.mHitBlock.onEntityCollidedWithBlock(
                            this.worldObj,
                            this.mHitBlockX,
                            this.mHitBlockY,
                            this.mHitBlockZ,
                            this);
                    }
                    if ((!this.worldObj.isRemote)
                        && (EnchantmentHelper.getEnchantmentLevel(Enchantment.fireAspect.effectId, this.mArrow) > 2)) {
                        GT_Utility
                            .setCoordsOnFire(this.worldObj, this.mHitBlockX, this.mHitBlockY, this.mHitBlockZ, true);
                    }
                    if (breaksOnImpact()) {
                        setDead();
                    }
                }
            }
            WorldSpawnedEventBuilder.ParticleEventBuilder events = new WorldSpawnedEventBuilder.ParticleEventBuilder()
                .setWorld(this.worldObj);

            if (getIsCritical()) {
                events.setIdentifier(ParticleFX.CRIT)
                    .setMotion(-this.motionX, -this.motionY + 0.2D, -this.motionZ).<WorldSpawnedEventBuilder
                        .ParticleEventBuilder>times(
                            4,
                            (x, i) -> x
                                .setPosition(
                                    this.posX + this.motionX * i / 4.0D,
                                    this.posY + this.motionY * i / 4.0D,
                                    this.posZ + this.motionZ * i / 4.0D)
                                .run());
            }
            this.posX += this.motionX;
            this.posY += this.motionY;
            this.posZ += this.motionZ;

            this.rotationYaw = ((float) (Math.atan2(this.motionX, this.motionZ) * 180.0D / Math.PI));
            for (this.rotationPitch = ((float) (Math
                .atan2(this.motionY, MathHelper.sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ))
                * 180.0D
                / Math.PI)); this.rotationPitch - this.prevRotationPitch < -180.0F; this.prevRotationPitch -= 360.0F) {}
            while (this.rotationPitch - this.prevRotationPitch >= 180.0F) {
                this.prevRotationPitch += 360.0F;
            }
            while (this.rotationYaw - this.prevRotationYaw < -180.0F) {
                this.prevRotationYaw -= 360.0F;
            }
            while (this.rotationYaw - this.prevRotationYaw >= 180.0F) {
                this.prevRotationYaw += 360.0F;
            }
            this.rotationPitch = (this.prevRotationPitch + (this.rotationPitch - this.prevRotationPitch) * 0.2F);
            this.rotationYaw = (this.prevRotationYaw + (this.rotationYaw - this.prevRotationYaw) * 0.2F);
            float tFrictionMultiplier = 0.99F;
            if (isInWater()) {
                events.setMotion(-this.motionX, -this.motionY + 0.2D, -this.motionZ)
                    .setIdentifier(ParticleFX.BUBBLE)
                    .setPosition(
                        this.posX - this.motionX * 0.25D,
                        this.posY - this.motionY * 0.25D,
                        this.posZ - this.motionZ * 0.25D)
                    .times(4, Runnable::run);
                tFrictionMultiplier = 0.8F;
            }
            if (isWet()) {
                extinguish();
            }
            this.motionX *= tFrictionMultiplier;
            this.motionY *= tFrictionMultiplier;
            this.motionZ *= tFrictionMultiplier;
            this.motionY -= 0.0500000007450581D;
            setPosition(this.posX, this.posY, this.posZ);
            func_145775_I();
        }
    }

    @Override
    public void writeEntityToNBT(NBTTagCompound aNBT) {
        super.writeEntityToNBT(aNBT);
        aNBT.setShort("xTile", (short) this.mHitBlockX);
        aNBT.setShort("yTile", (short) this.mHitBlockY);
        aNBT.setShort("zTile", (short) this.mHitBlockZ);
        aNBT.setShort("life", (short) this.mTicksAlive);
        aNBT.setByte("inTile", (byte) Block.getIdFromBlock(this.mHitBlock));
        aNBT.setByte("inData", (byte) this.mHitBlockMeta);
        aNBT.setByte("shake", (byte) this.arrowShake);
        aNBT.setByte("inGround", (byte) (this.inGround ? 1 : 0));
        aNBT.setByte("pickup", (byte) this.canBePickedUp);
        aNBT.setDouble("damage", getDamage());
        aNBT.setTag("mArrow", this.mArrow == null ? null : this.mArrow.writeToNBT(new NBTTagCompound()));
    }

    @Override
    public void readEntityFromNBT(NBTTagCompound aNBT) {
        super.readEntityFromNBT(aNBT);
        this.mHitBlockX = aNBT.getShort("xTile");
        this.mHitBlockY = aNBT.getShort("yTile");
        this.mHitBlockZ = aNBT.getShort("zTile");
        this.mTicksAlive = aNBT.getShort("life");
        this.mHitBlock = Block.getBlockById(aNBT.getByte("inTile") & 0xFF);
        this.mHitBlockMeta = (aNBT.getByte("inData") & 0xFF);
        this.arrowShake = (aNBT.getByte("shake") & 0xFF);
        this.inGround = (aNBT.getByte("inGround") == 1);
        setDamage(aNBT.getDouble("damage"));
        this.canBePickedUp = aNBT.getByte("pickup");
        this.mArrow = GT_Utility.loadItem(aNBT, "mArrow");
    }

    @Override
    public void onCollideWithPlayer(EntityPlayer aPlayer) {
        if ((!this.worldObj.isRemote) && (this.inGround)
            && (this.arrowShake <= 0)
            && (this.canBePickedUp == 1)
            && (aPlayer.inventory.addItemStackToInventory(getArrowItem()))) {
            playSound("random.pop", 0.2F, ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F);
            aPlayer.onItemPickup(this, 1);
            setDead();
        }
    }

    public int[] onHitEntity(Entity aHitEntity, Entity aShootingEntity, ItemStack aArrow, int aRegularDamage,
        int aMagicDamage, int aKnockback, int aFireDamage, int aHitTimer) {
        return new int[] { aRegularDamage, aMagicDamage, aKnockback, aFireDamage, aHitTimer };
    }

    public ItemStack getArrowItem() {
        return GT_Utility.copyOrNull(this.mArrow);
    }

    public void setArrowItem(ItemStack aStack) {
        this.mArrow = GT_Utility.updateItemStack(GT_Utility.copyAmount(1L, aStack));
    }

    public boolean breaksOnImpact() {
        return false;
    }

    @Override
    public void setKnockbackStrength(int aKnockback) {
        this.mKnockback = aKnockback;
    }
}