package twilightforest.entity;

import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAIBase;
import net.minecraft.entity.ai.EntityAIFindEntityNearestPlayer;
import net.minecraft.entity.ai.EntityMoveHelper;
import net.minecraft.entity.ai.RandomPositionGenerator;
import net.minecraft.entity.monster.EntityGhast;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityLargeFireball;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import twilightforest.TFFeature;

import java.util.Random;

public class EntityTFTowerGhast extends EntityGhast {
    // 0 = idle, 1 = eyes open / tracking player, 2 = shooting fireball
    private static final DataParameter<Byte> ATTACK_STATUS = EntityDataManager.createKey(EntityTFTowerGhast.class, DataSerializers.BYTE);

    public AIAttack attackAI;
    protected float wanderFactor;
    private int inTrapCounter;

	public EntityTFTowerGhast(World par1World) {
		super(par1World);
        this.setSize(4.0F, 6.0F);

    	this.wanderFactor = 16.0F;
    	this.inTrapCounter = 0;
	}

	@Override
    protected void entityInit() {
        super.entityInit();
        this.dataManager.register(ATTACK_STATUS, (byte) 0);
    }

	@Override
    protected void initEntityAI() {
	    attackAI = new AIAttack(this);
        this.tasks.addTask(5, new AIHomedFly(this));
        this.tasks.addTask(7, new EntityGhast.AILookAround(this));
        this.tasks.addTask(7, attackAI);
        this.targetTasks.addTask(1, new EntityAIFindEntityNearestPlayer(this));
    }

    // [VanillaCopy]-ish mixture of EntityGhast.AIFly and EntityAIStayNearHome
    public static class AIHomedFly extends EntityAIBase {
        private final EntityTFTowerGhast parentEntity;

	    AIHomedFly(EntityTFTowerGhast ghast) {
	        this.parentEntity = ghast;
	        setMutexBits(1);
        }

        // From AIFly, but with extra condition from AIStayNearHome
        @Override
        public boolean shouldExecute() {
            EntityMoveHelper entitymovehelper = this.parentEntity.getMoveHelper();

            if (!entitymovehelper.isUpdating())
            {
                return !this.parentEntity.isWithinHomeDistanceCurrentPosition();
            }
            else
            {
                double d0 = entitymovehelper.getX() - this.parentEntity.posX;
                double d1 = entitymovehelper.getY() - this.parentEntity.posY;
                double d2 = entitymovehelper.getZ() - this.parentEntity.posZ;
                double d3 = d0 * d0 + d1 * d1 + d2 * d2;
                return (d3 < 1.0D || d3 > 3600.0D)
                        && !this.parentEntity.isWithinHomeDistanceCurrentPosition();
            }
        }

        // From AIFly
        @Override
        public boolean continueExecuting()
        {
            return false;
        }

        // From AIStayNearHome but use move helper instead of PathNavigate
        @Override
        public void startExecuting()
        {
            Random random = this.parentEntity.getRNG();
            double d0 = this.parentEntity.posX + (double)((random.nextFloat() * 2.0F - 1.0F) * 16.0F);
            double d1 = this.parentEntity.posY + (double)((random.nextFloat() * 2.0F - 1.0F) * 16.0F);
            double d2 = this.parentEntity.posZ + (double)((random.nextFloat() * 2.0F - 1.0F) * 16.0F);
            this.parentEntity.getMoveHelper().setMoveTo(d0, d1, d2, 1.0D);

            if (this.parentEntity.getDistanceSq(this.parentEntity.getHomePosition()) > 256.0D)
            {
                Vec3d vecToHome = new Vec3d(this.parentEntity.getHomePosition()).subtract(this.parentEntity.getPositionVector()).normalize();

                double targetX = this.parentEntity.posX + vecToHome.xCoord * 16.0F + (double)((this.parentEntity.rand.nextFloat() * 2.0F - 1.0F) * 16.0F);
                double targetY = this.parentEntity.posY + vecToHome.yCoord * 16.0F + (double)((this.parentEntity.rand.nextFloat() * 2.0F - 1.0F) * 16.0F);
                double targetZ = this.parentEntity.posZ + vecToHome.zCoord * 16.0F + (double)((this.parentEntity.rand.nextFloat() * 2.0F - 1.0F) * 16.0F);

                this.parentEntity.getMoveHelper().setMoveTo(targetX, targetY, targetZ, 1.0D);
            }
            else
            {
                this.parentEntity.getMoveHelper().setMoveTo(this.parentEntity.getHomePosition().getX() + 0.5D, this.parentEntity.getHomePosition().getY(), this.parentEntity.getHomePosition().getZ() + 0.5D, 1.0D);
            }
        }
    }

    // [VanillaCopy] EntityGhast.AIFireballAttack, edits noted
    public static class AIAttack extends EntityAIBase {
        private final EntityTFTowerGhast parentEntity;
        public int attackTimer;
        public int prevAttackTimer; // TF - add for renderer

        public AIAttack(EntityTFTowerGhast ghast)
        {
            this.parentEntity = ghast;
            setMutexBits(2);
        }

        @Override
        public boolean shouldExecute()
        {
            return this.parentEntity.getAttackTarget() != null;
        }

        @Override
        public void startExecuting()
        {
            this.attackTimer = this.prevAttackTimer = 0;
        }

        @Override
        public void resetTask()
        {
            this.parentEntity.setAttacking(false);
        }

        @Override
        public void updateTask()
        {
            EntityLivingBase entitylivingbase = this.parentEntity.getAttackTarget();
            double d0 = 64.0D;

            if (entitylivingbase.getDistanceSqToEntity(this.parentEntity) < 4096.0D && this.parentEntity.getEntitySenses().canSee(entitylivingbase))
            {
                World world = this.parentEntity.world;
                this.prevAttackTimer = attackTimer;
                ++this.attackTimer;

                if (this.attackTimer == 10)
                {
                    world.playEvent((EntityPlayer)null, 1015, new BlockPos(this.parentEntity), 0);
                }

                if (this.attackTimer == 20 && this.parentEntity.shouldAttack(entitylivingbase))
                {
                    // TF - face target and call custom method
                    this.parentEntity.faceEntity(entitylivingbase, 10F, this.parentEntity.getVerticalFaceSpeed());
                    this.parentEntity.spitFireball();
                    this.prevAttackTimer = attackTimer;
                    this.attackTimer = -40;
                }
            }
            else if (this.attackTimer > 0)
            {
                this.prevAttackTimer = attackTimer;
                --this.attackTimer;
            }

            this.parentEntity.setAttacking(this.attackTimer > 10);
        }
    }

	@Override
    protected void applyEntityAttributes() {
        super.applyEntityAttributes();
        this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30.0D);
        this.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(64.0D);
    }

    @Override
    protected float getSoundVolume()
    {
        return 0.5F;
    }
    
    @Override
    public int getTalkInterval()
    {
        return 160;
    }

    @Override
    public int getMaxSpawnedInChunk()
    {
        return 8;
    }
    
	@Override
    public void onLivingUpdate()
    {
    	// age when in light, like mobs
        if (getBrightness(1F) > 0.5F)
        {
            this.entityAge += 2;
        }
        
        if (this.rand.nextBoolean())
        {
            this.world.spawnParticle(EnumParticleTypes.REDSTONE, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height - 0.25D, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, 0, 0, 0);
        }

        super.onLivingUpdate();
    }

    @Override
    protected void updateAITasks()
    {
        findHome();
        
        if (this.inTrapCounter > 0)
        {
        	this.inTrapCounter--;
        	setAttackTarget(null);
        }

        int status = isAttacking() ? 2 : getAttackTarget() != null ? 1 : 0;

        dataManager.set(ATTACK_STATUS, (byte) status);
    }

    public int getAttackStatus() {
	    return dataManager.get(ATTACK_STATUS);
    }

    protected boolean shouldAttack(EntityLivingBase living) {
	    return true;
    }
    
	/**
	 * Something is deeply wrong with the calculations based off of this value, so let's set it high enough that it's ignored.
	 */
	@Override
    public int getVerticalFaceSpeed()
    {
        return 500;
    }

	protected void spitFireball() {
        Vec3d vec3d = this.getLook(1.0F);
        double d2 = getAttackTarget().posX - (this.posX + vec3d.xCoord * 4.0D);
        double d3 = getAttackTarget().getEntityBoundingBox().minY + (double)(getAttackTarget().height / 2.0F) - (0.5D + this.posY + (double)(this.height / 2.0F));
        double d4 = getAttackTarget().posZ - (this.posZ + vec3d.zCoord * 4.0D);
        world.playEvent((EntityPlayer)null, 1016, new BlockPos(this), 0);
        EntityLargeFireball entitylargefireball = new EntityLargeFireball(world, this, d2, d3, d4);
        entitylargefireball.explosionPower = this.getFireballStrength();
        entitylargefireball.posX = this.posX + vec3d.xCoord * 4.0D;
        entitylargefireball.posY = this.posY + (double)(this.height / 2.0F) + 0.5D;
        entitylargefireball.posZ = this.posZ + vec3d.zCoord * 4.0D;
        world.spawnEntity(entitylargefireball);
		
		// when we attack, there is a 1-in-6 chance we decide to stop attacking
		if (rand.nextInt(6) == 0)
		{
			setAttackTarget(null);
		}
	}

    @Override
    public boolean getCanSpawnHere()
    {
        return this.world.checkNoEntityCollision(getEntityBoundingBox())
        		&& this.world.getCollisionBoxes(this, getEntityBoundingBox()).isEmpty()
        		&& !this.world.containsAnyLiquid(getEntityBoundingBox())
        		&& this.world.getDifficulty() != EnumDifficulty.PEACEFUL
        		&& this.isValidLightLevel();
    }
    
    /**
     * Checks to make sure the light is not too bright where the mob is spawning
     */
    protected boolean isValidLightLevel()
    {
        return true;
    }

    private void findHome()
    {
    	if (!this.hasHome())
    	{
    		int chunkX = MathHelper.floor(this.posX) >> 4;
    		int chunkZ = MathHelper.floor(this.posZ) >> 4;

    		TFFeature nearFeature = TFFeature.getFeatureForRegion(chunkX, chunkZ, this.world);

    		if (nearFeature != TFFeature.darkTower)
    		{
    			this.detachHome();
    			this.entityAge += 5;
    		}
    		else
    		{
    			BlockPos cc = TFFeature.getNearestCenterXYZ(MathHelper.floor(this.posX), MathHelper.floor(this.posZ), world);
    			this.setHomePosAndDistance(cc.up(128), 64);
    		}
    	}
    }
    
    public void setInTrap()
    {
    	this.inTrapCounter = 10;
    }

    // [VanillaCopy] Home fields and methods from EntityCreature, changes noted
    private BlockPos homePosition = BlockPos.ORIGIN;
    private float maximumHomeDistance = -1.0F;

    public boolean isWithinHomeDistanceCurrentPosition()
    {
        return this.isWithinHomeDistanceFromPosition(new BlockPos(this));
    }

    public boolean isWithinHomeDistanceFromPosition(BlockPos pos)
    {
        // TF - restrict valid y levels
        // Towers are so large, a simple radius doesn't really work, so we make it more of a cylinder
        return this.maximumHomeDistance == -1.0F
                ? true
                : pos.getY() > 64 && pos.getY() < 210 && this.homePosition.distanceSq(pos) < (double)(this.maximumHomeDistance * this.maximumHomeDistance);
    }

    public void setHomePosAndDistance(BlockPos pos, int distance)
    {
        this.homePosition = pos;
        this.maximumHomeDistance = (float)distance;
    }

    public BlockPos getHomePosition()
    {
        return this.homePosition;
    }

    public float getMaximumHomeDistance()
    {
        return this.maximumHomeDistance;
    }

    public void detachHome()
    {
        this.maximumHomeDistance = -1.0F;
    }

    public boolean hasHome()
    {
        return this.maximumHomeDistance != -1.0F;
    }
    // End copy
}

