package com.hbm.entity.projectile;

import java.util.List;

import com.hbm.handler.threading.PacketThreading;
import com.hbm.items.weapon.sedna.BulletConfig;
import com.hbm.packet.toclient.AuxParticlePacketNT;

import cpw.mods.fml.common.network.NetworkRegistry.TargetPoint;
import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntityMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;

public class EntityBulletBeamBase extends Entity implements IEntityAdditionalSpawnData {
	
	public EntityLivingBase thrower;
	public BulletConfig config;
	public float damage;
	public double headingX;
	public double headingY;
	public double headingZ;
	public double beamLength;

	public EntityBulletBeamBase(World world) {
		super(world);
		this.ignoreFrustumCheck = true;
		this.renderDistanceWeight = 10.0D;
		this.setSize(0.5F, 0.5F);
		this.isImmuneToFire = true;
	}
	
	public EntityLivingBase getThrower() { return this.thrower; }
	
	public EntityBulletBeamBase(EntityLivingBase entity, BulletConfig config, float baseDamage) {
		this(entity.worldObj);
		
		this.thrower = entity;
		this.setBulletConfig(config);
		
		this.damage = baseDamage * this.config.damageMult;
	}
	
	public EntityBulletBeamBase(EntityLivingBase entity, BulletConfig config, float baseDamage, float angularInaccuracy, double sideOffset, double heightOffset, double frontOffset) {
		this(entity.worldObj);
		
		this.thrower = entity;
		this.setBulletConfig(config);
		
		this.damage = baseDamage * this.config.damageMult;
		
		this.setLocationAndAngles(thrower.posX, thrower.posY + thrower.getEyeHeight(), thrower.posZ, thrower.rotationYaw + (float) rand.nextGaussian() * angularInaccuracy, thrower.rotationPitch + (float) rand.nextGaussian() * angularInaccuracy);
		
		Vec3 offset = Vec3.createVectorHelper(sideOffset, heightOffset, frontOffset);
		offset.rotateAroundX(-this.rotationPitch / 180F * (float) Math.PI);
		offset.rotateAroundY(-this.rotationYaw / 180F * (float) Math.PI);

		this.posX += offset.xCoord;
		this.posY += offset.yCoord;
		this.posZ += offset.zCoord;
		
		this.setPosition(this.posX, this.posY, this.posZ);
		
		this.headingX = (double) (-MathHelper.sin(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float) Math.PI));
		this.headingZ = (double) (MathHelper.cos(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float) Math.PI));
		this.headingY = (double) (-MathHelper.sin((this.rotationPitch) / 180.0F * (float) Math.PI));
		
		double range = 250D;
		this.headingX *= range;
		this.headingY *= range;
		this.headingZ *= range;
		
		performHitscan();
	}
	
	public void setRotationsFromVector(Vec3 delta) {
		this.rotationPitch = (float) (-Math.asin(delta.yCoord / delta.lengthVector()) * 180D / Math.PI);
		this.rotationYaw = (float) (-Math.atan2(delta.xCoord, delta.zCoord) * 180D / Math.PI);
		
		this.headingX = (double) (-MathHelper.sin(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float) Math.PI));
		this.headingZ = (double) (MathHelper.cos(this.rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float) Math.PI));
		this.headingY = (double) (-MathHelper.sin((this.rotationPitch) / 180.0F * (float) Math.PI));
	}
	
	public void performHitscanExternal(double range) {
		this.headingX *= range;
		this.headingY *= range;
		this.headingZ *= range;
		performHitscan();
	}

	@Override
	protected void entityInit() {
		this.dataWatcher.addObject(3, Integer.valueOf(0));
	}
	
	public void setBulletConfig(BulletConfig config) {
		this.config = config;
		this.dataWatcher.updateObject(3, config.id);
	}
	
	public BulletConfig getBulletConfig() {
		int id = this.dataWatcher.getWatchableObjectInt(3);
		if(id < 0 || id > BulletConfig.configs.size()) return null;
		return BulletConfig.configs.get(id);
	}
	
	@Override
	public void onUpdate() {
		
		if(config == null) config = this.getBulletConfig();

		if(config == null){
			this.setDead();
			return;
		}
		
		if(config.onUpdate != null) config.onUpdate.accept(this);
		
		super.onUpdate();
		
		if(!worldObj.isRemote && this.ticksExisted > config.expires) this.setDead();
	}
	
	protected void performHitscan() {
		
		Vec3 pos = Vec3.createVectorHelper(this.posX, this.posY, this.posZ);
		Vec3 nextPos = Vec3.createVectorHelper(this.posX + this.headingX, this.posY + this.headingY, this.posZ + this.headingZ);
		MovingObjectPosition mop = null;
		if(!this.isSpectral()) mop = this.worldObj.func_147447_a(pos, nextPos, false, true, false);
		pos = Vec3.createVectorHelper(this.posX, this.posY, this.posZ);
		nextPos = Vec3.createVectorHelper(this.posX + this.headingX, this.posY + this.headingY, this.posZ + this.headingZ);

		if(mop != null) {
			nextPos = Vec3.createVectorHelper(mop.hitVec.xCoord, mop.hitVec.yCoord, mop.hitVec.zCoord);
		}
		
		if(!this.worldObj.isRemote && this.doesImpactEntities()) {
			
			Entity hitEntity = null;
			List<Entity> list = this.worldObj.getEntitiesWithinAABBExcludingEntity(this, this.boundingBox.addCoord(this.headingX, this.headingY, this.headingZ).expand(1.0D, 1.0D, 1.0D));
			double nearest = 0.0D;
			MovingObjectPosition nonPenImpact = null;
			MovingObjectPosition coinHit = null;
			
			double closestCoin = 0;
			EntityCoin hitCoin = null;
			
			for(Entity entity : list) {
				if(entity.isDead) continue;
				if(entity instanceof EntityCoin) {
					double hitbox = 0.3F;
					AxisAlignedBB aabb = entity.boundingBox.expand(hitbox, hitbox, hitbox);
					MovingObjectPosition hitMop = aabb.calculateIntercept(pos, nextPos);
					if(hitMop != null) {
						double dist = pos.distanceTo(hitMop.hitVec);
						if(closestCoin == 0 || dist < closestCoin) {
							closestCoin = dist;
							hitCoin = (EntityCoin) entity;
							coinHit = hitMop;
						}
					}
				}
			}

			for(int j = 0; j < list.size(); ++j) {
				Entity entity = (Entity) list.get(j);
				
				if(entity.canBeCollidedWith() && entity != thrower && entity.isEntityAlive()) {
					double hitbox = 0.3F;
					AxisAlignedBB aabb = entity.boundingBox.expand(hitbox, hitbox, hitbox);
					MovingObjectPosition hitMop = aabb.calculateIntercept(pos, nextPos);

					if(hitMop != null) {
						
						double dist = pos.distanceTo(hitMop.hitVec);
						
						// if penetration is enabled, run impact for all intersecting entities
						if(this.doesPenetrate()) {
							if(hitCoin == null || dist < closestCoin) {
								this.onImpact(new MovingObjectPosition(entity, hitMop.hitVec));
							}
						} else {
							if(dist < nearest || nearest == 0.0D) {
								hitEntity = entity;
								nearest = dist;
								nonPenImpact = hitMop;
							}
						}
					}
				}
			}

			// if not, only run it for the closest MOP
			if(!this.doesPenetrate() && hitEntity != null) {
				mop = new MovingObjectPosition(hitEntity, nonPenImpact.hitVec);
			}
			
			if(hitCoin != null) {
				Vec3 vec = Vec3.createVectorHelper(coinHit.hitVec.xCoord - posX, coinHit.hitVec.yCoord - posY, coinHit.hitVec.zCoord - posZ);
				this.beamLength = vec.lengthVector();
				
				double range = 50;
				List<Entity> targets = worldObj.getEntitiesWithinAABB(Entity.class, AxisAlignedBB.getBoundingBox(coinHit.hitVec.xCoord, coinHit.hitVec.yCoord, coinHit.hitVec.zCoord, coinHit.hitVec.xCoord, coinHit.hitVec.yCoord, coinHit.hitVec.zCoord).expand(range, range, range));
				Entity nearestCoin = null;
				Entity nearestPlayer = null;
				Entity nearestMob = null;
				Entity nearestOther = null;
				double coinDist = 0;
				double playerDist = 0;
				double mobDist = 0;
				double otherDist = 0;
				
				hitCoin.setDead();
				
				// well i mean we could just uuse a single var for all variants and then overwrite stuff
				// when we run into things with higher priority. however i can't be assed fuck off
				for(Entity entity : targets) {
					if(entity == this.getThrower()) continue;
					if(entity.isDead) continue;
					double dist = entity.getDistanceToEntity(hitCoin);
					if(dist > range) continue;
					
					if(entity instanceof EntityCoin) {
						if(coinDist == 0 || dist < coinDist) {
							coinDist = dist;
							nearestCoin = entity;
						}
					} else if(entity instanceof EntityPlayer) {
						if(playerDist == 0 || dist < playerDist) {
							playerDist = dist;
							nearestPlayer = entity;
						}
					} else if(entity instanceof EntityMob) {
						if(mobDist == 0 || dist < mobDist) {
							mobDist = dist;
							nearestMob = entity;
						}
					} else if(entity instanceof EntityLivingBase) {
						if(otherDist == 0 || dist < otherDist) {
							otherDist = dist;
							nearestOther = entity;
						}
					}
				}
				
				// ternary of shame
				Entity target = nearestCoin != null ? nearestCoin :
					nearestPlayer != null ? nearestPlayer :
					nearestMob != null ? nearestMob :
					nearestOther != null ? nearestOther : null;
				
				if(target != null) {
					EntityBulletBeamBase newBeam = new EntityBulletBeamBase(hitCoin.getThrower() != null ? hitCoin.getThrower() : this.thrower, this.config, this.damage * 1.25F);
					newBeam.setPosition(coinHit.hitVec.xCoord, coinHit.hitVec.yCoord, coinHit.hitVec.zCoord);
					Vec3 delta = Vec3.createVectorHelper(target.posX - newBeam.posX, (target.posY + target.height / 2D) - newBeam.posY, target.posZ - newBeam.posZ);
					newBeam.setRotationsFromVector(delta);
					newBeam.performHitscanExternal(250D);
					worldObj.spawnEntityInWorld(newBeam);
				} else {
					EntityBulletBeamBase newBeam = new EntityBulletBeamBase(hitCoin.getThrower() != null ? hitCoin.getThrower() : this.thrower, this.config, this.damage * 1.25F);
					newBeam.setPosition(coinHit.hitVec.xCoord, coinHit.hitVec.yCoord, coinHit.hitVec.zCoord);
					newBeam.setRotationsFromVector(Vec3.createVectorHelper(rand.nextGaussian() * 0.5, -1, rand.nextGaussian() * 0.5));
					newBeam.performHitscanExternal(250D);
					worldObj.spawnEntityInWorld(newBeam);
				}
				
				NBTTagCompound data = new NBTTagCompound();
				data.setString("type", "vanillaExt");
				data.setString("mode", "largeexplode");
				data.setFloat("size", 1.5F);
				data.setByte("count", (byte)1);
				PacketThreading.createAllAroundThreadedPacket(new AuxParticlePacketNT(data, coinHit.hitVec.xCoord, coinHit.hitVec.yCoord, coinHit.hitVec.zCoord), new TargetPoint(worldObj.provider.dimensionId, coinHit.hitVec.xCoord, coinHit.hitVec.yCoord, coinHit.hitVec.zCoord, 100));
			
				
				return;
			}
		}

		if(mop != null) {
			if(mop.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK && this.worldObj.getBlock(mop.blockX, mop.blockY, mop.blockZ) == Blocks.portal) {
				this.setInPortal();
			} else {
				this.onImpact(mop);
			}

			Vec3 vec = Vec3.createVectorHelper(mop.hitVec.xCoord - posX, mop.hitVec.yCoord - posY, mop.hitVec.zCoord - posZ);
			this.beamLength = vec.lengthVector();
		} else {
			Vec3 vec = Vec3.createVectorHelper(nextPos.xCoord - posX, nextPos.yCoord - posY, nextPos.zCoord - posZ);
			this.beamLength = vec.lengthVector();
		}
		
	}

	
	protected void onImpact(MovingObjectPosition mop) {
		if(!worldObj.isRemote) {
			if(this.config.onImpactBeam != null) this.config.onImpactBeam.accept(this, mop);
		}
	}
	
	public boolean doesImpactEntities() { return this.config.impactsEntities; }
	public boolean doesPenetrate() { return this.config.doesPenetrate; }
	public boolean isSpectral() { return this.config.isSpectral; }

	@Override @SideOnly(Side.CLIENT) public float getShadowSize() { return 0.0F; }
	
	@Override protected void writeEntityToNBT(NBTTagCompound nbt) { }
	@Override public boolean writeToNBTOptional(NBTTagCompound nbt) { return false; }
	@Override public void readEntityFromNBT(NBTTagCompound nbt) { this.setDead(); }

	@Override public void writeSpawnData(ByteBuf buf) {
		buf.writeDouble(beamLength);
		buf.writeFloat(rotationYaw);
		buf.writeFloat(rotationPitch);
	}
	@Override public void readSpawnData(ByteBuf buf) {
		this.beamLength = buf.readDouble();
		this.rotationYaw = buf.readFloat();
		this.rotationPitch = buf.readFloat();
	}
	
	@Override @SideOnly(Side.CLIENT) public boolean canRenderOnFire() { return false; }
}
