package thebetweenlands.common.entity.mobs;

import javax.annotation.Nullable;

import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.IEntityLivingData;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAIAttackMelee;
import net.minecraft.entity.ai.EntityAIBase;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAILookIdle;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.ai.EntityAISwimming;
import net.minecraft.entity.ai.EntityAIWander;
import net.minecraft.entity.ai.EntityAIWatchClosest;
import net.minecraft.entity.monster.EntityMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import thebetweenlands.api.entity.IEntityBL;
import thebetweenlands.common.registries.ItemRegistry;
import thebetweenlands.common.registries.LootTableRegistry;
import thebetweenlands.common.registries.SoundRegistry;

public class EntityCryptCrawler extends EntityMob implements IEntityBL {
	protected static final int MUTEX_BLOCKING  = 0b01000;
	protected static final int MUTEX_ATTACKING = 0b10000;
	
	private static final byte EVENT_SHIELD_BLOCKED = 80;
	
	private static final DataParameter<Boolean> IS_BIPED = EntityDataManager.createKey(EntityCryptCrawler.class, DataSerializers.BOOLEAN);
	private static final DataParameter<Boolean> IS_STANDING = EntityDataManager.createKey(EntityCryptCrawler.class, DataSerializers.BOOLEAN);
	private static final DataParameter<Boolean> IS_CHIEF = EntityDataManager.createKey(EntityCryptCrawler.class, DataSerializers.BOOLEAN);
	private static final DataParameter<Boolean> IS_BLOCKING = EntityDataManager.<Boolean>createKey(EntityCryptCrawler.class, DataSerializers.BOOLEAN);
	
	public float standingAngle, prevStandingAngle;

	protected boolean recentlyBlockedAttack;
	
	public EntityCryptCrawler(World world) {
		super(world);
		setSize(0.5F, 1.0F); //Width must be < 1 otherwise path finder will not path through one block wide gaps!!
		stepHeight = 1F;
	}

	@Override
	protected void entityInit() {
		super.entityInit();
		dataManager.register(IS_STANDING, false);
		dataManager.register(IS_BIPED, false);
		dataManager.register(IS_CHIEF, false);
		dataManager.register(IS_BLOCKING, false);
	}

	public boolean isStanding() {
		return dataManager.get(IS_STANDING);
	}

	private void setIsStanding(boolean standing) {
		dataManager.set(IS_STANDING, standing);
	}

	public boolean isBiped() {
		return dataManager.get(IS_BIPED);
	}

	public void setIsBiped(boolean standing) {
		dataManager.set(IS_BIPED, standing);
	}

	public boolean isChief() {
		return dataManager.get(IS_CHIEF);
	}

	public void setIsChief(boolean chief) {
		dataManager.set(IS_CHIEF, chief);
	}

	public boolean isBlocking() {
		return dataManager.get(IS_BLOCKING);
	}

	private void setIsBlocking(boolean blocking) {
		dataManager.set(IS_BLOCKING, blocking);
	}

	@Override
	protected void initEntityAI() {
		tasks.addTask(1, new EntityAISwimming(this));
		tasks.addTask(2, new EntityCryptCrawler.AIShieldCharge(this)); //Shield charge AI interrupts shield block AI and attack AI
		tasks.addTask(3, new EntityCryptCrawler.AIShieldBlock(this));
		tasks.addTask(4, new EntityCryptCrawler.AICryptCrawlerAttack(this));
		tasks.addTask(5, new EntityAIWander(this, 1D));
		tasks.addTask(6, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
		tasks.addTask(7, new EntityAILookIdle(this));
		
		targetTasks.addTask(0, new EntityAINearestAttackableTarget<>(this, EntityPlayer.class, 3, true, true, null).setUnseenMemoryTicks(120));
		targetTasks.addTask(3, new EntityAIHurtByTarget(this, true));
	}

	@Override
	protected void applyEntityAttributes() {
		super.applyEntityAttributes();
		updateAttributes();
	}

	protected void updateAttributes() {
		if (getEntityWorld() != null && !getEntityWorld().isRemote) {
			if (isChief()) {
				setSize(0.98F, 1.9F);
				experienceValue = 20;
				getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.28D);
				getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(100D);
				getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(4.25D);
				getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(20.0D);
				getEntityAttribute(SharedMonsterAttributes.KNOCKBACK_RESISTANCE).setBaseValue(0.75D);
			}
			if (!isChief() && isBiped()) {
				setSize(0.75F, 1.5F);
				experienceValue = 10;
				getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.29D);
				getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(30D);
				getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(2.5D);
				getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(20.0D);
				getEntityAttribute(SharedMonsterAttributes.KNOCKBACK_RESISTANCE).setBaseValue(0.5D);
			}
			if (!isChief() && !isBiped()) {
				setSize(0.95F, 1F);
				experienceValue = 5;
				getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.31D);
				getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(20D);
				getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(1.75D);
				getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(20.0D);
				getEntityAttribute(SharedMonsterAttributes.KNOCKBACK_RESISTANCE).setBaseValue(0.25D);
			}
		}
	}

	@Override
	protected SoundEvent getAmbientSound() {
		return SoundRegistry.CRYPT_CRAWLER_LIVING;
	}

	@Override
	protected SoundEvent getHurtSound(DamageSource damageSourceIn) {
		return SoundRegistry.CRYPT_CRAWLER_HURT;
	}

	@Override
	protected SoundEvent getDeathSound() {
		return SoundRegistry.CRYPT_CRAWLER_DEATH;
	}

	@Override
	protected ResourceLocation getLootTable() {
		return LootTableRegistry.CRYPT_CRAWLER;
	}

	@Override
	protected float getSoundPitch() {
		if(isChief())
			return super.getSoundPitch() * 0.5F;
		return super.getSoundPitch();
	}
	
	@Override
	public void onLivingUpdate() {
		if(this.isBlocking() && !this.getHeldItemOffhand().isEmpty() && this.getHeldItemOffhand().getItem().isShield(this.getHeldItemOffhand(), this)) {
			this.setActiveHand(EnumHand.OFF_HAND);
			
			//"Fix" for janky item pose
			if(this.world.isRemote) {
				this.activeItemStack = this.getHeldItemOffhand();
			}
		} else if(this.isHandActive() && this.getActiveHand() == EnumHand.OFF_HAND) {
			this.stopActiveHand();
		}
		
		if (getEntityWorld().isRemote) {
			if (isChief())
				setSize(1.2F, 1.9F);
			if (!isChief() && isBiped())
				setSize(0.75F, 1.5F);
			if (!isChief() && !isBiped())
				setSize(0.95F - standingAngle * 0.2F, 1F + standingAngle * 0.75F);
		}

		if (!getEntityWorld().isRemote && !isBiped()) {
			if (getAttackTarget() != null) {
				double distance = getDistance(getAttackTarget().posX, getAttackTarget().getEntityBoundingBox().minY, getAttackTarget().posZ);

				setIsStanding(distance <= 3.0D);
			}
	
			if (getAttackTarget() == null) {
				setIsStanding(false);
			}
		}

		if (getEntityWorld().isRemote && !isBiped()) {
			prevStandingAngle = standingAngle;

			if (standingAngle > 0 && !isStanding()) {
				standingAngle -= 0.1F;
			}

			if (isStanding() && standingAngle <= 1F) {
				standingAngle += 0.1F;
			}
			
			if (standingAngle < 0 && !isStanding()) {
				standingAngle = 0F;
			}

			if (isStanding() && standingAngle > 1F) {
				standingAngle = 1F;
			}
			
			standingAngle = MathHelper.clamp(standingAngle, 0, 1);
		}

		super.onLivingUpdate();
	}

    @SideOnly(Side.CLIENT)
    public float smoothedStandingAngle(float partialTicks) {
        return prevStandingAngle + (standingAngle - prevStandingAngle) * partialTicks;
    }

	@Override
	@SuppressWarnings("rawtypes")
	public boolean canAttackClass(Class entity) {
		return EntityCryptCrawler.class != entity;
	}

	@Override
	public boolean getCanSpawnHere() {
		return super.getCanSpawnHere();
	}

	@Override
    public boolean isNotColliding() {
        return !getEntityWorld().containsAnyLiquid(getEntityBoundingBox()) && getEntityWorld().getCollisionBoxes(this, getEntityBoundingBox()).isEmpty() && getEntityWorld().checkNoEntityCollision(getEntityBoundingBox(), this);
    }

	@Override
	public int getMaxSpawnedInChunk() {
		return 3;
	}

	@Override
	public boolean attackEntityAsMob(Entity entity) {
		if(!this.isBlocking() && this.canEntityBeSeen(entity)) {
			boolean hasHitTarget = entity.attackEntityFrom(DamageSource.causeMobDamage(this), (float) ((int) this.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).getAttributeValue()));

			if (hasHitTarget) {
				if(!getHeldItemMainhand().isEmpty())
					getHeldItemMainhand().getItem().hitEntity(getHeldItemMainhand(), (EntityLivingBase) entity, this);
				
				//entity.addVelocity(-MathHelper.sin(rotationYaw * 3.141593F / 180.0F) * 0.5F, 0.2D, MathHelper.cos(rotationYaw * 3.141593F / 180.0F) * 0.5F);
				
				if (!getEntityWorld().isRemote)
					getEntityWorld().playSound((EntityPlayer) null, posX, posY, posZ, SoundRegistry.CRYPT_CRAWLER_LIVING, SoundCategory.HOSTILE, 1F, 0.5F);
			}
			return hasHitTarget;
		}
		return false;
	}

	@Override
	public boolean attackEntityFrom(DamageSource source, float amount) {
		if (ticksExisted < 40 && source == DamageSource.IN_WALL) {
			return false;
		}
		
		boolean wasAttackBlocked = super.attackEntityFrom(source, amount);
		
		if(this.isBlocking() && !wasAttackBlocked) {
			recentlyBlockedAttack = true;
			
			//Play shield block sound to all listeners. For some reason shield block sound from item doesn't seem to work.
			this.world.setEntityState(this, EVENT_SHIELD_BLOCKED);
		}
		
		return wasAttackBlocked;
	}
	
	@Override
	public void handleStatusUpdate(byte id) {
		super.handleStatusUpdate(id);
		
		if(id == EVENT_SHIELD_BLOCKED) {
			this.world.playSound(this.posX, this.posY, this.posZ, SoundEvents.ITEM_SHIELD_BLOCK, SoundCategory.NEUTRAL, 1.0F, 0.8F + this.world.rand.nextFloat() * 0.4F, false);
		}
	}
	
	@Override
	@Nullable
	public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata) {
		if (rand.nextInt(3) == 0) { // TODO re-enable for both types
			setIsBiped(true);
			if (rand.nextInt(3) == 0)
				setIsChief(true);
		}

		if (isBiped()) {
			if (rand.nextFloat() < 0.05F)
				setLeftHanded(true);
			else
				setLeftHanded(false);
			setRandomEquipment();
		}
		updateAttributes();
		setHealth(getMaxHealth());
		return livingdata;
	}

	protected void setRandomEquipment() {
		if (!isChief()) {
			int randomWeapon = rand.nextInt(5);
			int randomShield = rand.nextInt(3);

			switch (randomWeapon) {
			case 0:
				setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(ItemRegistry.WIGHTS_BANE));
				break;
			case 1:
				setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(ItemRegistry.WEEDWOOD_SWORD));
				break;
			case 2:
				setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(ItemRegistry.BONE_SWORD));
				break;
			case 3:
				setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(ItemRegistry.BONE_AXE));
				break;
			case 4:
				setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(ItemRegistry.WEEDWOOD_AXE));
				break;
			}

			if (!getItemStackFromSlot(EntityEquipmentSlot.MAINHAND).isEmpty()) {
				switch (randomShield) {
				case 0:
					setItemStackToSlot(EntityEquipmentSlot.OFFHAND, ItemStack.EMPTY);
					break;
				case 1:
					setItemStackToSlot(EntityEquipmentSlot.OFFHAND, new ItemStack(ItemRegistry.WEEDWOOD_SHIELD));
					break;
				case 2:
					setItemStackToSlot(EntityEquipmentSlot.OFFHAND, new ItemStack(ItemRegistry.BONE_SHIELD));
					break;
				}
			}
		}
		else {
			setItemStackToSlot(EntityEquipmentSlot.OFFHAND, new ItemStack(ItemRegistry.SYRMORITE_SHIELD));
			setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(ItemRegistry.OCTINE_SWORD));
		}
	}

	@Override
	public void writeEntityToNBT(NBTTagCompound nbt) {
		super.writeEntityToNBT(nbt);
		nbt.setBoolean("is_biped", isBiped());
		nbt.setBoolean("is_chief", isChief());
	}

	@Override
	public void readEntityFromNBT(NBTTagCompound nbt) {
		super.readEntityFromNBT(nbt);
		setIsBiped(nbt.getBoolean("is_biped"));
		setIsChief(nbt.getBoolean("is_chief"));
	}

	@Override
	public float getAIMoveSpeed() {
		//Half move speed when blocking
		return (this.isBlocking() ? 0.5F : 1.0F) * super.getAIMoveSpeed();
	}
	
	static class AICryptCrawlerAttack extends EntityAIAttackMelee {
		public AICryptCrawlerAttack(EntityCryptCrawler crypt_crawler) {
			super(crypt_crawler, 1.2D, false);
			this.setMutexBits(this.getMutexBits() | MUTEX_ATTACKING);
		}

		@Override
		protected double getAttackReachSqr(EntityLivingBase attackTarget) {
			return (double) (3.0F + attackTarget.width);
		}
	}
	
	static class AIShieldCharge extends EntityAIBase {
		private EntityCryptCrawler crawler;
		
		private int chargeCooldown;
		private int chargeCooldownMax;
		
		private int chargeTimer = 0;
		
		public AIShieldCharge(EntityCryptCrawler crawler) {
			this.crawler = crawler;
			setMutexBits(3 | MUTEX_BLOCKING | MUTEX_ATTACKING);
		}
		
		protected boolean isHoldingShield() {
			if(crawler != null) {
				ItemStack heldItem = crawler.getItemStackFromSlot(EntityEquipmentSlot.OFFHAND);
				if (heldItem.isEmpty()) {
					return false;
				} else if(!heldItem.getItem().isShield(heldItem, this.crawler)) {
					return false;
				}
				
				return true;
			}
			
			return false;
		}
		
		@Override
		public boolean shouldExecute() {
			if(!crawler.isChief()) {
				return false;
			}
			
			ItemStack heldItem = crawler.getItemStackFromSlot(EntityEquipmentSlot.OFFHAND);
			if (heldItem.isEmpty()) {
				return false;
			} else if(!heldItem.getItem().isShield(heldItem, this.crawler)) {
				return false;
			}
			
			if(crawler.getAttackTarget() != null) {
				if(chargeCooldownMax < 0) {
					chargeCooldownMax = 80 + crawler.rand.nextInt(90);
				}
				
				chargeCooldown++;
				
				return chargeCooldown > chargeCooldownMax;
			}
			
			return false;
		}
		
		@Override
		public void startExecuting() {
			this.chargeCooldown = 0;
			this.chargeCooldownMax = -1;
		}
		
		@Override
		public boolean shouldContinueExecuting() {
			return this.isHoldingShield() && this.chargeTimer < 60;
		}
		
		@Override
		public void resetTask() {
			crawler.setIsBlocking(false);
			this.chargeTimer = 0;
		}
		
		@Override
		public void updateTask() {
			this.chargeTimer++;
			
			if(!crawler.isBlocking()) {
				crawler.setIsBlocking(true);
			}
			
			if(this.chargeTimer < 20) {
				crawler.setSneaking(true);
			} else {
				crawler.setSneaking(false);
			}
			
			EntityLivingBase target = this.crawler.getAttackTarget();
			if(target != null) {
				this.crawler.faceEntity(target, 15, 15);
			}
		}
	}

	static class AIShieldBlock extends EntityAIBase {
		private EntityCryptCrawler crawler;
		private EntityLivingBase target;
		private int blockingCount;
		private int blockingCountMax;
		private int meleeBlockingCounter;
		private int meleeBlockingCounterMax;
		
		private int blockingCooldownCounter;
		private int blockingCooldownCounterMax = -1;
		
		public AIShieldBlock(EntityCryptCrawler crawler) {
			this.crawler = crawler;
			setMutexBits(MUTEX_BLOCKING);
		}

		protected boolean isInMeleeRange() {
			return crawler != null && target != null && crawler.getDistanceSq(target) < 9.0D;
		}
		
		protected boolean isHoldingShield() {
			if(crawler != null) {
				ItemStack heldItem = crawler.getItemStackFromSlot(EntityEquipmentSlot.OFFHAND);
				if (heldItem.isEmpty()) {
					return false;
				} else if(!heldItem.getItem().isShield(heldItem, this.crawler)) {
					return false;
				}
				
				return true;
			}
			
			return false;
		}
		
		@Override
		public boolean shouldExecute() {
			if(!this.isHoldingShield()) {
				return false;
			}

			target = crawler.getAttackTarget();

			if(target != null && !crawler.isSwingInProgress && crawler.hurtResistantTime <= Math.max(0, crawler.maxHurtResistantTime - 5)) {
				blockingCooldownCounter++;
				
				if(blockingCooldownCounterMax < 0) {
					blockingCooldownCounterMax = 4 + crawler.rand.nextInt(5);
				}
				
				return blockingCooldownCounter > blockingCooldownCounterMax;
			}
			
			return false;
		}

		@Override
		public boolean shouldContinueExecuting() {
			return this.isHoldingShield() && !crawler.recentlyBlockedAttack && blockingCount != -1 && crawler.hurtResistantTime <= Math.max(0, crawler.maxHurtResistantTime - 5) && !crawler.isSwingInProgress &&
					!(blockingCount > blockingCountMax || (meleeBlockingCounterMax >= 0 && meleeBlockingCounter > meleeBlockingCounterMax));
		}

		@Override
		public void startExecuting() {
			blockingCount = 0;
			blockingCountMax = 40 + crawler.rand.nextInt(40);
			meleeBlockingCounterMax = -1;
			meleeBlockingCounter = 0;
			
			//Reset the recently blocked state
			crawler.recentlyBlockedAttack = false;
			
			blockingCooldownCounter = 0;
			blockingCooldownCounterMax = -1;
		}
		
		@Override
		public void resetTask() {
			crawler.setIsBlocking(false);
			blockingCount = -1;
			meleeBlockingCounterMax = -1;
			meleeBlockingCounter = 0;
		}

		@Override
	    public void updateTask() {
			if(!crawler.isBlocking()) {
				crawler.setIsBlocking(true);
			}
			
			this.blockingCount++;
			
			if(this.isInMeleeRange()) {
				if(meleeBlockingCounterMax < 0) {
					meleeBlockingCounterMax = 10 + crawler.rand.nextInt(20);
				}
				meleeBlockingCounter++;
			} else {
				meleeBlockingCounterMax = -1;
				meleeBlockingCounter = 0;
			}
	    }
	}
}