package thebetweenlands.common.entity.mobs;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

import javax.annotation.Nullable;

import com.google.common.base.Optional;

import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.MoverType;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.monster.EntityMob;
import net.minecraft.entity.player.EntityPlayer;
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.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 net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import thebetweenlands.api.capability.IPuppetCapability;
import thebetweenlands.api.capability.IPuppeteerCapability;
import thebetweenlands.api.capability.ProtectionShield;
import thebetweenlands.api.entity.IEntityBL;
import thebetweenlands.common.entity.EntityShockwaveBlock;
import thebetweenlands.common.registries.CapabilityRegistry;

public class EntityFortressBossBlockade extends EntityMob implements IEntityBL, IEntityAdditionalSpawnData {
	protected static final DataParameter<Optional<UUID>> OWNER = EntityDataManager.<Optional<UUID>>createKey(EntityFortressBossBlockade.class, DataSerializers.OPTIONAL_UNIQUE_ID);
	protected static final DataParameter<Float> SIZE = EntityDataManager.<Float>createKey(EntityFortressBossBlockade.class, DataSerializers.FLOAT);
	protected static final DataParameter<Float> ROTATION = EntityDataManager.<Float>createKey(EntityFortressBossBlockade.class, DataSerializers.FLOAT);

	private Entity cachedOwner;

	private float prevRotation = 0.0F;
	private float rotation = 0.0F;
	private int despawnTicks = 0;
	private int maxDespawnTicks = 160;

	public EntityFortressBossBlockade(World world) {
		super(world);
		this.setSize(1.0F, 0.2F);
		this.prevRotation = this.rotation = world.rand.nextFloat() * 360.0f;
	}

	public EntityFortressBossBlockade(World world, Entity source) {
		super(world);
		this.setSize(1.0F, 0.2F);
		this.setOwner(source);
		this.prevRotation = this.rotation = world.rand.nextFloat() * 360.0f;
	}

	@Override
	protected void applyEntityAttributes() {
		super.applyEntityAttributes();
		this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.0D);
	}

	@Override
	protected void entityInit() {
		super.entityInit();
		this.getDataManager().register(OWNER, Optional.absent());
		this.getDataManager().register(SIZE, 1.0F);
		this.getDataManager().register(ROTATION, this.rotation);
	}

	public void setOwner(@Nullable Entity entity) {
		this.getDataManager().set(OWNER, entity == null ? Optional.absent() : Optional.of(entity.getUniqueID()));
	}

	@Nullable
	public UUID getOwnerUUID() {
		Optional<UUID> uuid = this.getDataManager().get(OWNER);
		return uuid.isPresent() ? uuid.get() : null;
	}

	@Nullable
	public Entity getOwner() {
		UUID uuid = this.getOwnerUUID();
		if(uuid == null) {
			this.cachedOwner = null;
		} else if(this.cachedOwner == null || !this.cachedOwner.isEntityAlive() || !this.cachedOwner.getUniqueID().equals(uuid)) {
			this.cachedOwner = null;
			for(Entity entity : this.getEntityWorld().getEntitiesWithinAABB(Entity.class, this.getEntityBoundingBox().grow(64.0D, 64.0D, 64.0D))) {
				if(entity.getUniqueID().equals(uuid)) {
					this.cachedOwner = entity;
					break;
				}
			}
		}
		return this.cachedOwner;
	}

	public void setTriangleSize(float size) {
		this.getDataManager().set(SIZE, size);
		if(this.world.isRemote) {
			double prevX = this.posX;
			double prevZ = this.posZ;
			this.setSize(size*2, this.height);
			this.setPosition(prevX, this.posY, prevZ);
		}
	}

	public float getTriangleSize() {
		return this.getDataManager().get(SIZE);
	}

	public void setMaxDespawnTicks(int ticks) {
		this.maxDespawnTicks = ticks;
	}

	public int getMaxDespawnTicks() {
		return this.maxDespawnTicks;
	}

	public int getDespawnTicks() {
		return this.despawnTicks;
	}

	@Override
	public boolean canBeCollidedWith() {
		return false;
	}

	@Override
	public boolean canBePushed() {
		return false;
	}

	@Override
	public void writeEntityToNBT(NBTTagCompound nbt) {
		super.writeEntityToNBT(nbt);
		nbt.setFloat("triangleSize", this.getTriangleSize());
		nbt.setFloat("triangleRotation", this.getDataManager().get(ROTATION));
		nbt.setInteger("despawnTicks", this.despawnTicks);
		nbt.setInteger("maxDespawnTicks", this.maxDespawnTicks);
		if(this.getOwnerUUID() != null) {
			nbt.setUniqueId("owner", this.getOwnerUUID());
		}
	}

	@Override
	public void readEntityFromNBT(NBTTagCompound nbt) {
		super.readEntityFromNBT(nbt);
		this.setTriangleSize(nbt.getFloat("triangleSize"));
		this.getDataManager().set(ROTATION, nbt.getFloat("triangleRotation"));
		this.despawnTicks = nbt.getInteger("despawnTicks");
		this.maxDespawnTicks = nbt.getInteger("maxDespawnTicks");
		if(nbt.hasUniqueId("owner")) {
			this.getDataManager().set(OWNER, Optional.of(nbt.getUniqueId("owner")));
		} else {
			this.getDataManager().set(OWNER, Optional.absent());
		}
	}
	
	protected boolean isPlayerControlled() {
		return this.getOwner() instanceof EntityPlayer;
	}

	@Override
	public void onUpdate() {
		if(!this.world.isRemote && (this.world.getDifficulty() == EnumDifficulty.PEACEFUL || (this.getOwner() != null && !this.getOwner().isEntityAlive()))) {
			this.setDead();
			return;
		}

		this.setTriangleSize(this.getTriangleSize());

		this.motionX = 0;
		this.motionY = 0;
		this.motionZ = 0;

		super.onUpdate();

		if(!this.world.isRemote) {
			this.despawnTicks++;
			if(this.despawnTicks >= this.getMaxDespawnTicks()) {
				this.setDead();
			}

			this.rotation += 1.0F;
			this.getDataManager().set(ROTATION, this.rotation);

			List<EntityLivingBase> targets = this.world.getEntitiesWithinAABB(EntityLivingBase.class, this.getEntityBoundingBox().grow(this.getTriangleSize()*2, 0, this.getTriangleSize()*2));
			for(EntityLivingBase target : targets) {
				if(target != this.getOwner()) {
					Vec3d[] vertices = this.getTriangleVertices(1);
					
					if(EntityFortressBoss.rayTraceTriangle(new Vec3d(target.posX - this.posX, 1, target.posZ - this.posZ), new Vec3d(0, -2, 0), vertices[0], vertices[1], vertices[2])) {
						
						if(this.isPlayerControlled()) {
							IPuppetCapability cap = target.getCapability(CapabilityRegistry.CAPABILITY_PUPPET, null);
							
							Entity owner = this.getOwner();
							
							if(cap != null && cap.getPuppeteer() == owner && owner instanceof EntityPlayer) {
								EntityPlayer player = (EntityPlayer) owner;
								
								IPuppeteerCapability playerCap = player.getCapability(CapabilityRegistry.CAPABILITY_PUPPETEER, null);
								if(playerCap != null) {
									ProtectionShield shield = playerCap.getShield();
									
									if(shield != null) {
										float healthPercent = 0.3f;
										
										if(target.getHealth() * (1 - healthPercent) > 5.0f) {
											float healthCost = target.getHealth() * healthPercent;
											float prevHealth = target.getHealth();
											
											if(target.attackEntityFrom(DamageSource.MAGIC, healthCost) && (prevHealth - target.getHealth()) >= healthCost * 0.5f) {
												List<Integer> indices = new ArrayList<>();
												for(int i = 0; i < 20; i++) {
													indices.add(i);
												}
												Collections.shuffle(indices, this.rand);
												
												for(int index : indices) {
													if(!shield.isActive(index)) {
														shield.setActive(index, true);
														break;
													}
												}
												
												this.maxDespawnTicks = this.despawnTicks + 8;
											}
										}
									}
								}
							}
						} else if(target instanceof EntityPlayer)  {
							float damage = (float) this.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).getAttributeValue();
							if(target.attackEntityFrom(DamageSource.MAGIC, damage) && this.getOwner() != null && this.getOwner() instanceof EntityLivingBase) {
								EntityLivingBase owner = (EntityLivingBase) this.getOwner();
								if(owner.getHealth() < owner.getMaxHealth() - damage) {
									owner.heal(damage * 3.0F);
								}
							}
						}
						
					}
				}
			}
		} else {
			this.prevRotation = this.rotation;
			this.rotation = this.getDataManager().get(ROTATION);

			for(int c = 0; c < 4; c++) {
				float r1 = this.world.rand.nextFloat();
				float r2 = this.world.rand.nextFloat();
				this.rotation += 15;
				Vec3d[] vertices = this.getTriangleVertices(1);
				this.rotation -= 15;
				double xc = 0, zc = 0;
				for(int i = 0; i < 3; i++) {
					Vec3d vertex = vertices[i];
					switch(i) {
					default:
					case 0:
						xc += vertex.x * (1 - Math.sqrt(r1));
						zc += vertex.z * (1 - Math.sqrt(r1));
						break;
					case 1:
						xc += (Math.sqrt(r1) * (1 - r2)) * vertex.x;
						zc += (Math.sqrt(r1) * (1 - r2)) * vertex.z;
						break;
					case 2:
						xc += (Math.sqrt(r1) * r2) * vertex.x;
						zc += (Math.sqrt(r1) * r2) * vertex.z;
						break;
					}
				}
				Vec3d rp = new Vec3d(xc, vertices[0].y, zc);

				double sx = this.posX + rp.x;
				double sy = this.posY + rp.y + 4;
				double sz = this.posZ + rp.z;
				double ex = this.posX + rp.x;
				double ey = this.posY + rp.y;
				double ez = this.posZ + rp.z;

				if(this.getOwner() != null) {
					sx = this.getOwner().posX;
					sy = this.getOwner().getEntityBoundingBox().minY + (this.getOwner().getEntityBoundingBox().maxY - this.getOwner().getEntityBoundingBox().minY) / 2.0D;
					sz = this.getOwner().posZ;
				}

				if(!this.isPlayerControlled()) {
					this.world.spawnParticle(EnumParticleTypes.PORTAL, sx, sy, sz, ex - sx, ey - sy, ez - sz);
				}
			}
		}
	}

	@Override
	public void travel(float strafe, float up, float forward) {
		if (this.isInWater()) {
			this.moveRelative(strafe, up, forward, 0.02F);
			this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ);
			this.motionX *= 0.800000011920929D;
			this.motionY *= 0.800000011920929D;
			this.motionZ *= 0.800000011920929D;
		} else {
			float friction = 0.91F;

			if (this.onGround) {
				friction = this.world.getBlockState(new BlockPos(MathHelper.floor(this.posX), MathHelper.floor(this.getEntityBoundingBox().minY) - 1, MathHelper.floor(this.posZ))).getBlock().slipperiness * 0.91F;
			}

			float groundFriction = 0.16277136F / (friction * friction * friction);
			this.moveRelative(strafe, up, forward, this.onGround ? 0.1F * groundFriction : 0.02F);
			friction = 0.91F;

			if (this.onGround) {
				friction = this.world.getBlockState(new BlockPos(MathHelper.floor(this.posX), MathHelper.floor(this.getEntityBoundingBox().minY) - 1, MathHelper.floor(this.posZ))).getBlock().slipperiness * 0.91F;
			}

			this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ);
			this.motionX *= (double)friction;
			this.motionY *= (double)friction;
			this.motionZ *= (double)friction;
		}

		this.prevLimbSwingAmount = this.limbSwingAmount;
		double dx = this.posX - this.prevPosX;
		double dz = this.posZ - this.prevPosZ;
		float distanceMoved = MathHelper.sqrt(dx * dx + dz * dz) * 4.0F;

		if (distanceMoved > 1.0F) {
			distanceMoved = 1.0F;
		}

		this.limbSwingAmount += (distanceMoved - this.limbSwingAmount) * 0.4F;
		this.limbSwing += this.limbSwingAmount;
	}

	public Vec3d[] getTriangleVertices(float partialTicks) {
		Vec3d[] vertices = new Vec3d[3];
		double rot = Math.toRadians(this.prevRotation + (this.rotation - this.prevRotation) * partialTicks);
		double angle = Math.PI * 2.0D / 3.0D;
		for(int i = 0; i < 3; i++) {
			double sin = Math.sin(angle * i + rot);
			double cos = Math.cos(angle * i + rot);
			vertices[i] = new Vec3d(sin * this.getTriangleSize(), 0, cos * this.getTriangleSize());
		}
		return vertices;
	}

	@Override
	public boolean attackEntityAsMob(Entity target) {
		return false;
	}

	@Override
	protected void collideWithEntity(Entity entityIn) { }
	
	@Override
	public boolean isEntityInvulnerable(DamageSource source) {
        return !DamageSource.OUT_OF_WORLD.getDamageType().equals(source.getDamageType()) && source.getImmediateSource() instanceof EntityShockwaveBlock == false;
    }

	@Override
    public boolean getIsInvulnerable() {
		return true;
	}

	@Override
	public void writeSpawnData(ByteBuf buffer) {
		buffer.writeFloat(this.rotation);
	}

	@Override
	public void readSpawnData(ByteBuf buffer) {
		this.prevRotation = this.rotation = buffer.readFloat();
		this.dataManager.set(ROTATION, this.rotation);
	}
}
