package common.entity.projectile;

import java.util.List;

import common.block.Block;
import common.enchantment.EnchantmentHelper;
import common.entity.DamageSource;
import common.entity.Entity;
import common.entity.EntityType;
import common.entity.npc.EntityNPC;
import common.entity.types.EntityLiving;
import common.entity.types.IObjectData;
import common.entity.types.IProjectile;
import common.init.Blocks;
import common.init.Items;
import common.init.SoundEvent;
import common.item.ItemStack;
import common.model.ParticleType;
import common.tags.TagObject;
import common.util.BlockPos;
import common.util.BoundingBox;
import common.util.ExtMath;
import common.util.HitPosition;
import common.util.Vec3;
import common.vars.Vars;
import common.world.State;
import common.world.World;

public class EntityArrow extends Entity implements IProjectile, IObjectData
{
    private int xTile = -1;
    private int yTile = -1;
    private int zTile = -1;
    private State inTile;
    private boolean inGround;

    /** 1 if the player can pick up the arrow */
    public int canBePickedUp;

    /** Seems to be some sort of timer for animating an arrow. */
    public int arrowShake;

    /** The owner of this arrow. */
    public Entity shootingEntity;
    private int ticksInGround;
    private int ticksInAir;
    private double damage = 2.0D;

    /** The amount of knockback an arrow applies when it hits a mob. */
    private int knockbackStrength;

    public EntityArrow(World worldIn)
    {
        super(worldIn);
        this.renderDistWeight = 10.0D;
        this.setSize(0.5F, 0.5F);
    }

    public EntityArrow(World worldIn, double x, double y, double z)
    {
        super(worldIn);
        this.renderDistWeight = 10.0D;
        this.setSize(0.5F, 0.5F);
        this.setPosition(x, y, z);
    }

    public EntityArrow(World worldIn, double x, double y, double z, int data)
    {
    	this(worldIn, x, y, z);
    	Entity entity2 = worldIn.getEntityByID(data);
        if(entity2 instanceof EntityLiving) {
            this.shootingEntity = entity2;
        }
    }

    public EntityArrow(World worldIn, EntityLiving shooter, EntityLiving target, float velocity, float innacuracy)
    {
        super(worldIn);
        this.renderDistWeight = 10.0D;
        this.shootingEntity = shooter;

        if (shooter.isPlayer())
        {
            this.canBePickedUp = 1;
        }

        this.posY = shooter.posY + (double)shooter.getEyeHeight() - 0.10000000149011612D;
        double d0 = target.posX - shooter.posX;
        double d1 = target.getEntityBoundingBox().minY + (double)(target.height / 3.0F) - this.posY;
        double d2 = target.posZ - shooter.posZ;
        double d3 = (double)ExtMath.sqrtd(d0 * d0 + d2 * d2);

        if (d3 >= 1.0E-7D)
        {
            float f = (float)(ExtMath.atan2(d2, d0) * 180.0D / Math.PI) - 90.0F;
            float f1 = (float)(-(ExtMath.atan2(d1, d3) * 180.0D / Math.PI));
            double d4 = d0 / d3;
            double d5 = d2 / d3;
            this.setLocationAndAngles(shooter.posX + d4, this.posY, shooter.posZ + d5, f, f1);
            float f2 = (float)(d3 * 0.20000000298023224D);
            this.setThrowableHeading(d0, d1 + (double)f2, d2, velocity, innacuracy);
        }
    }

    public EntityArrow(World worldIn, EntityLiving shooter, float velocity)
    {
        super(worldIn);
        this.renderDistWeight = 10.0D;
        this.shootingEntity = shooter;

        if (shooter.isPlayer())
        {
            this.canBePickedUp = 1;
        }

        this.setSize(0.5F, 0.5F);
        this.setLocationAndAngles(shooter.posX, shooter.posY + (double)shooter.getEyeHeight(), shooter.posZ, shooter.rotYaw, shooter.rotPitch);
        this.posX -= (double)(ExtMath.cos(this.rotYaw / 180.0F * (float)Math.PI) * 0.16F);
        this.posY -= 0.10000000149011612D;
        this.posZ -= (double)(ExtMath.sin(this.rotYaw / 180.0F * (float)Math.PI) * 0.16F);
        this.setPosition(this.posX, this.posY, this.posZ);
        this.motionX = (double)(-ExtMath.sin(this.rotYaw / 180.0F * (float)Math.PI) * ExtMath.cos(this.rotPitch / 180.0F * (float)Math.PI));
        this.motionZ = (double)(ExtMath.cos(this.rotYaw / 180.0F * (float)Math.PI) * ExtMath.cos(this.rotPitch / 180.0F * (float)Math.PI));
        this.motionY = (double)(-ExtMath.sin(this.rotPitch / 180.0F * (float)Math.PI));
        this.setThrowableHeading(this.motionX, this.motionY, this.motionZ, velocity * 1.5F, 1.0F);
    }

    protected void entityInit()
    {
        this.dataWatcher.addObject(16, Byte.valueOf((byte)0));
    }

    /**
     * Similar to setArrowHeading, it's point the throwable entity to a x, y, z direction.
     */
    public void setThrowableHeading(double x, double y, double z, float velocity, float inaccuracy)
    {
        float f = ExtMath.sqrtd(x * x + y * y + z * z);
        x = x / (double)f;
        y = y / (double)f;
        z = z / (double)f;
        x = x + this.rand.gaussian() * (double)(this.rand.chance() ? -1 : 1) * 0.007499999832361937D * (double)inaccuracy;
        y = y + this.rand.gaussian() * (double)(this.rand.chance() ? -1 : 1) * 0.007499999832361937D * (double)inaccuracy;
        z = z + this.rand.gaussian() * (double)(this.rand.chance() ? -1 : 1) * 0.007499999832361937D * (double)inaccuracy;
        x = x * (double)velocity;
        y = y * (double)velocity;
        z = z * (double)velocity;
        this.motionX = x;
        this.motionY = y;
        this.motionZ = z;
        float f1 = ExtMath.sqrtd(x * x + z * z);
        this.prevYaw = this.rotYaw = (float)(ExtMath.atan2(x, z) * 180.0D / Math.PI);
        this.prevPitch = this.rotPitch = (float)(ExtMath.atan2(y, (double)f1) * 180.0D / Math.PI);
        this.ticksInGround = 0;
    }

    public void setClientPosition(double x, double y, double z, float yaw, float pitch, boolean teleport)
    {
        this.setPosition(x, y, z);
        this.setRotation(yaw, pitch);
    }

    /**
     * Sets the velocity to the args. Args: x, y, z
     */
    public void setVelocity(double x, double y, double z)
    {
        this.motionX = x;
        this.motionY = y;
        this.motionZ = z;

        if (this.prevPitch == 0.0F && this.prevYaw == 0.0F)
        {
            float f = ExtMath.sqrtd(x * x + z * z);
            this.prevYaw = this.rotYaw = (float)(ExtMath.atan2(x, z) * 180.0D / Math.PI);
            this.prevPitch = this.rotPitch = (float)(ExtMath.atan2(y, (double)f) * 180.0D / Math.PI);
            this.prevPitch = this.rotPitch;
            this.prevYaw = this.rotYaw;
            this.setLocationAndAngles(this.posX, this.posY, this.posZ, this.rotYaw, this.rotPitch);
            this.ticksInGround = 0;
        }
    }

    /**
     * Called to update the entity's position/logic.
     */
    public void onUpdate()
    {
        super.onUpdate();

        if (this.prevPitch == 0.0F && this.prevYaw == 0.0F)
        {
            float f = ExtMath.sqrtd(this.motionX * this.motionX + this.motionZ * this.motionZ);
            this.prevYaw = this.rotYaw = (float)(ExtMath.atan2(this.motionX, this.motionZ) * 180.0D / Math.PI);
            this.prevPitch = this.rotPitch = (float)(ExtMath.atan2(this.motionY, (double)f) * 180.0D / Math.PI);
        }

        BlockPos blockpos = new BlockPos(this.xTile, this.yTile, this.zTile);
        State iblockstate = this.worldObj.getState(blockpos);
        Block block = iblockstate.getBlock();

        if (block != Blocks.air)
        {
            block.setBlockBoundsBasedOnState(this.worldObj, blockpos);
            BoundingBox axisalignedbb = block.getCollisionBoundingBox(this.worldObj, blockpos, iblockstate);

            if (axisalignedbb != null && axisalignedbb.isVecInside(new Vec3(this.posX, this.posY, this.posZ)))
            {
                this.inGround = true;
            }
        }

        if (this.arrowShake > 0)
        {
            --this.arrowShake;
        }

        if (this.inGround)
        {
            if (iblockstate == this.inTile)
            {
                ++this.ticksInGround;

                if (this.ticksInGround >= 1200)
                {
                    this.setDead();
                }
            }
            else
            {
                this.inGround = false;
                this.motionX *= (double)(this.rand.floatv() * 0.2F);
                this.motionY *= (double)(this.rand.floatv() * 0.2F);
                this.motionZ *= (double)(this.rand.floatv() * 0.2F);
                this.ticksInGround = 0;
                this.ticksInAir = 0;
            }
        }
        else
        {
            ++this.ticksInAir;
            Vec3 vec31 = new Vec3(this.posX, this.posY, this.posZ);
            Vec3 vec3 = new Vec3(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ);
            HitPosition movingobjectposition = this.worldObj.rayTraceBlocks(vec31, vec3, false, true, false);
            vec31 = new Vec3(this.posX, this.posY, this.posZ);
            vec3 = new Vec3(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ);

            if (movingobjectposition != null)
            {
                vec3 = new Vec3(movingobjectposition.vec.xCoord, movingobjectposition.vec.yCoord, movingobjectposition.vec.zCoord);
            }

            Entity entity = null;
            List<Entity> list = this.worldObj.getEntitiesWithinAABBExcludingEntity(this, this.getEntityBoundingBox().addCoord(this.motionX, this.motionY, this.motionZ).expand(1.0D, 1.0D, 1.0D));
            double d0 = 0.0D;

            for (int i = 0; i < list.size(); ++i)
            {
                Entity entity1 = (Entity)list.get(i);

                if (entity1.canBeCollidedWith() && (entity1 != this.shootingEntity || this.ticksInAir >= 5))
                {
                    float f1 = 0.3F;
                    BoundingBox axisalignedbb1 = entity1.getEntityBoundingBox().expand((double)f1, (double)f1, (double)f1);
                    HitPosition movingobjectposition1 = axisalignedbb1.calculateIntercept(vec31, vec3);

                    if (movingobjectposition1 != null)
                    {
                        double d1 = vec31.squareDistanceTo(movingobjectposition1.vec);

                        if (d1 < d0 || d0 == 0.0D)
                        {
                            entity = entity1;
                            d0 = d1;
                        }
                    }
                }
            }

            if (entity != null)
            {
                movingobjectposition = new HitPosition(entity);
            }

//            if (movingobjectposition != null && movingobjectposition.entity != null && movingobjectposition.entity.isPlayer())
//            {
//                EntityNPC entityplayer = (EntityNPC)movingobjectposition.entity;
//
//                if (entityplayer.creative || this.shootingEntity.isPlayer() && !((EntityNPC)this.shootingEntity).canAttackPlayer(entityplayer))
//                {
//                    movingobjectposition = null;
//                }
//            }

            if (movingobjectposition != null)
            {
                if (movingobjectposition.entity != null)
                {
                    float f2 = ExtMath.sqrtd(this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ);
                    int l = ExtMath.ceild((double)f2 * this.damage);

                    if (this.getIsCritical())
                    {
                        l += this.rand.zrange(l / 2 + 2);
                    }

                    DamageSource damagesource;

                    if (this.shootingEntity == null)
                    {
                        damagesource = DamageSource.causeShotDamage(this, this);
                    }
                    else
                    {
                        damagesource = DamageSource.causeShotDamage(this, this.shootingEntity);
                    }

                    if (this.isBurning()) // && !(movingobjectposition.entityHit instanceof EntityAITakePlace))
                    {
                        movingobjectposition.entity.setFire(5);
                    }

                    if ((this.worldObj.client || Vars.damageArrow) && movingobjectposition.entity.attackEntityFrom(damagesource, l))
                    {
                        if (movingobjectposition.entity instanceof EntityLiving)
                        {
                            EntityLiving entitylivingbase = (EntityLiving)movingobjectposition.entity;

                            if (!this.worldObj.client)
                            {
                                entitylivingbase.setArrowCountInEntity(entitylivingbase.getArrowCountInEntity() + 1);
                            }

                            if (this.knockbackStrength > 0)
                            {
                                float f7 = ExtMath.sqrtd(this.motionX * this.motionX + this.motionZ * this.motionZ);

                                if (f7 > 0.0F)
                                {
                                    movingobjectposition.entity.addKnockback(this.motionX * (double)this.knockbackStrength * 0.6000000238418579D / (double)f7, 0.1D, this.motionZ * (double)this.knockbackStrength * 0.6000000238418579D / (double)f7);
                                }
                            }

                            if (this.shootingEntity instanceof EntityLiving)
                            {
                                EnchantmentHelper.applyThornEnchantments(entitylivingbase, this.shootingEntity);
                                EnchantmentHelper.applyArthropodEnchantments((EntityLiving)this.shootingEntity, entitylivingbase);
                            }

//                            if (this.shootingEntity != null && movingobjectposition.entityHit != this.shootingEntity && movingobjectposition.entityHit.isPlayer() && this.shootingEntity.isPlayer())
//                            {
//                                ((EntityNPCMP)this.shootingEntity).netHandler.sendPacket(new S29PacketSoundEffect(
//                                		"random.successful_hit", shootingEntity.posX, 
//                                		shootingEntity.posY + (double)shootingEntity.getEyeHeight(), shootingEntity.posZ, 0.18F, 0.45F));
//                            }
                        }

                        this.playSound(SoundEvent.BOWHIT, 1.0F);

//                        if (!(movingobjectposition.entityHit instanceof EntityAITakePlace))
//                        {
                        this.setDead();
//                        }
                    }
                    else
                    {
                        this.motionX *= -0.10000000149011612D;
                        this.motionY *= -0.10000000149011612D;
                        this.motionZ *= -0.10000000149011612D;
                        this.rotYaw += 180.0F;
                        this.prevYaw += 180.0F;
                        this.ticksInAir = 0;
                    }
                }
                else
                {
                    BlockPos blockpos1 = movingobjectposition.block;
                    this.xTile = blockpos1.getX();
                    this.yTile = blockpos1.getY();
                    this.zTile = blockpos1.getZ();
                    State iblockstate1 = this.worldObj.getState(blockpos1);
                    this.inTile = iblockstate1;
                    this.motionX = (double)((float)(movingobjectposition.vec.xCoord - this.posX));
                    this.motionY = (double)((float)(movingobjectposition.vec.yCoord - this.posY));
                    this.motionZ = (double)((float)(movingobjectposition.vec.zCoord - this.posZ));
                    float f5 = ExtMath.sqrtd(this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ);
                    this.posX -= this.motionX / (double)f5 * 0.05000000074505806D;
                    this.posY -= this.motionY / (double)f5 * 0.05000000074505806D;
                    this.posZ -= this.motionZ / (double)f5 * 0.05000000074505806D;
                    this.playSound(SoundEvent.BOWHIT, 1.0F);
                    this.inGround = true;
                    this.arrowShake = 7;
                    this.setIsCritical(false);

                    if (this.inTile.getBlock() != Blocks.air)
                    {
                        this.inTile.getBlock().onEntityCollidedWithBlock(this.worldObj, blockpos1, iblockstate1, this);
                    }
                }
            }

            if (this.getIsCritical())
            {
                for (int k = 0; k < 4; ++k)
                {
                    this.worldObj.spawnParticle(ParticleType.CRIT, this.posX + this.motionX * (double)k / 4.0D, this.posY + this.motionY * (double)k / 4.0D, this.posZ + this.motionZ * (double)k / 4.0D, -this.motionX, -this.motionY + 0.2D, -this.motionZ);
                }
            }

            this.posX += this.motionX;
            this.posY += this.motionY;
            this.posZ += this.motionZ;
            float f3 = ExtMath.sqrtd(this.motionX * this.motionX + this.motionZ * this.motionZ);
            this.rotYaw = (float)(ExtMath.atan2(this.motionX, this.motionZ) * 180.0D / Math.PI);

            for (this.rotPitch = (float)(ExtMath.atan2(this.motionY, (double)f3) * 180.0D / Math.PI); this.rotPitch - this.prevPitch < -180.0F; this.prevPitch -= 360.0F)
            {
                ;
            }

            while (this.rotPitch - this.prevPitch >= 180.0F)
            {
                this.prevPitch += 360.0F;
            }

            while (this.rotYaw - this.prevYaw < -180.0F)
            {
                this.prevYaw -= 360.0F;
            }

            while (this.rotYaw - this.prevYaw >= 180.0F)
            {
                this.prevYaw += 360.0F;
            }

            this.rotPitch = this.prevPitch + (this.rotPitch - this.prevPitch) * 0.2F;
            this.rotYaw = this.prevYaw + (this.rotYaw - this.prevYaw) * 0.2F;
            float f4 = 0.99F;
            float f6 = 0.05F;

            if (this.isInLiquid())
            {
                for (int i1 = 0; i1 < 4; ++i1)
                {
                    float f8 = 0.25F;
                    this.worldObj.spawnParticle(ParticleType.WATER_BUBBLE, this.posX - this.motionX * (double)f8, this.posY - this.motionY * (double)f8, this.posZ - this.motionZ * (double)f8, this.motionX, this.motionY, this.motionZ);
                }

                f4 = 0.6F;
            }

            if (this.isWet())
            {
                this.extinguish();
            }

            this.motionX *= (double)f4;
            this.motionY *= (double)f4;
            this.motionZ *= (double)f4;
            this.motionY -= (double)f6;
            this.setPosition(this.posX, this.posY, this.posZ);
            this.doBlockCollisions();
        }
    }

    /**
     * (abstract) Protected helper method to write subclass entity data to NBT.
     */
    public void writeEntity(TagObject tagCompound)
    {
        tagCompound.setShort("xTile", (short)this.xTile);
        tagCompound.setShort("yTile", (short)this.yTile);
        tagCompound.setShort("zTile", (short)this.zTile);
        tagCompound.setShort("life", (short)this.ticksInGround);
        if(this.inTile != null) {
	        tagCompound.setString("inTile", this.inTile.getId());
        }
        tagCompound.setByte("shake", (byte)this.arrowShake);
        tagCompound.setBool("inGround", this.inGround);
        tagCompound.setByte("pickup", (byte)this.canBePickedUp);
        tagCompound.setDouble("damage", this.damage);
    }

    /**
     * (abstract) Protected helper method to read subclass entity data from NBT.
     */
    public void readEntity(TagObject tagCompund)
    {
        this.xTile = tagCompund.getShort("xTile");
        this.yTile = tagCompund.getShort("yTile");
        this.zTile = tagCompund.getShort("zTile");
        this.ticksInGround = tagCompund.getShort("life");

        if (tagCompund.hasString("inTile"))
        {
            this.inTile = State.getState(tagCompund.getString("inTile"), null);
        }
        else
        {
            this.inTile = null;
        }

        this.arrowShake = tagCompund.getByte("shake") & 255;
        this.inGround = tagCompund.getBool("inGround");

        this.damage = tagCompund.getDouble("damage");

        this.canBePickedUp = tagCompund.getByte("pickup");
    }

    /**
     * Called by a player entity when they collide with an entity
     */
    public void onCollideWithPlayer(EntityNPC entityIn)
    {
        if (!this.worldObj.client && this.inGround && this.arrowShake <= 0)
        {
            boolean flag = this.canBePickedUp == 1; // || this.canBePickedUp == 2 && entityIn.creative;

            if (this.canBePickedUp == 1 && !entityIn.inventory.addItemStackToInventory(new ItemStack(Items.arrow, 1)))
            {
                flag = false;
            }

            if (flag)
            {
                this.playSound(SoundEvent.POP, 0.2F);
                entityIn.onItemPickup(this, 1);
                this.setDead();
            }
        }
    }

    /**
     * returns if this entity triggers Block.onEntityWalking on the blocks they walk on. used for spiders and wolves to
     * prevent them from trampling crops
     */
    protected boolean canTriggerWalking()
    {
        return false;
    }

    public void setDamage(double damageIn)
    {
        this.damage = damageIn;
    }

    public double getDamage()
    {
        return this.damage;
    }

    /**
     * Sets the amount of knockback the arrow applies when it hits a mob.
     */
    public void setKnockbackStrength(int knockbackStrengthIn)
    {
        this.knockbackStrength = knockbackStrengthIn;
    }

    /**
     * If returns false, the item will not inflict any damage against entities.
     */
    public boolean canAttackWithItem()
    {
        return false;
    }

    public float getEyeHeight()
    {
        return 0.0F;
    }

    /**
     * Whether the arrow has a stream of critical hit particles flying behind it.
     */
    public void setIsCritical(boolean critical)
    {
        byte b0 = this.dataWatcher.getWatchableObjectByte(16);

        if (critical)
        {
            this.dataWatcher.updateObject(16, Byte.valueOf((byte)(b0 | 1)));
        }
        else
        {
            this.dataWatcher.updateObject(16, Byte.valueOf((byte)(b0 & -2)));
        }
    }

    /**
     * Whether the arrow has a stream of critical hit particles flying behind it.
     */
    public boolean getIsCritical()
    {
        byte b0 = this.dataWatcher.getWatchableObjectByte(16);
        return (b0 & 1) != 0;
    }
    
    public int getTrackingRange() {
    	return 64;
    }
    
    public int getUpdateFrequency() {
    	return 20;
    }
    
    public boolean isSendingVeloUpdates() {
    	return false;
    }
	
	public int getPacketData() {
		return this.shootingEntity != null ? this.shootingEntity.getId() : this.getId();
	}
	
	public boolean hasSpawnVelocity() {
		return true;
	}
    
    public EntityType getType() {
    	return EntityType.PROJECTILE;
    }
}
