package common.entity;

import java.util.Collection;
import java.util.List;
import java.util.Set;

import common.attributes.AttributeInstance;
import common.attributes.AttributeMap;
import common.collect.Sets;
import common.entity.npc.EntityNPC;
import common.entity.projectile.EntityArrow;
import common.entity.types.EntityLiving;
import common.item.ItemStack;
import common.log.Log;
import common.nbt.NBTTagCompound;
import common.network.Packet;
import common.packet.S14PacketEntity;
import common.packet.S18PacketEntityTeleport;
import common.packet.S19PacketEntityHeadLook;
import common.packet.S1BPacketEntityAttach;
import common.packet.S1CPacketEntityMetadata;
import common.packet.S1DPacketEntityEffect;
import common.packet.S20PacketEntityProperties;
import common.packet.S43PacketUpdateEntityNBT;
import common.packet.SPacketEntityEquipment;
import common.packet.SPacketEntityVelocity;
import common.packet.SPacketSpawnMob;
import common.packet.SPacketSpawnObject;
import common.packet.SPacketSpawnPlayer;
import common.potion.PotionEffect;
import common.util.ExtMath;

public class EntityTrackerEntry {
	public final Entity trackedEntity;
	private final int trackingDistanceThreshold;
	private final int updateFrequency;
	private final boolean sendVelocityUpdates;
	private final Set<EntityNPC> trackingPlayers = Sets.<EntityNPC>newHashSet();

	private Entity lastRiding;
	private int encodedPosX;
	private int encodedPosY;
	private int encodedPosZ;
	private int encodedRotationYaw;
	private int encodedRotationPitch;
	private int lastHeadMotion;
	private double lastTrackedEntityMotionX;
	private double lastTrackedEntityMotionY;
	private double lastTrackedEntityMotionZ;
	private int updateCounter;
	private double lastTrackedEntityPosX;
	private double lastTrackedEntityPosY;
	private double lastTrackedEntityPosZ;
	private boolean firstUpdateDone;
	private int ticksSinceLastForcedTeleport;
	private boolean ridingEntity;
	private boolean onGround;
	private boolean playerEntitiesUpdated;

	public EntityTrackerEntry(Entity trackedEntityIn, int trackingDistanceThresholdIn, int updateFrequencyIn, boolean sendVelocityUpdatesIn) {
		this.trackedEntity = trackedEntityIn;
		this.trackingDistanceThreshold = trackingDistanceThresholdIn;
		this.updateFrequency = updateFrequencyIn;
		this.sendVelocityUpdates = sendVelocityUpdatesIn;
		this.encodedPosX = ExtMath.floord(trackedEntityIn.posX * 32.0D);
		this.encodedPosY = ExtMath.floord(trackedEntityIn.posY * 32.0D);
		this.encodedPosZ = ExtMath.floord(trackedEntityIn.posZ * 32.0D);
		this.encodedRotationYaw = ExtMath.floorf(trackedEntityIn.rotYaw * 256.0F / 360.0F);
		this.encodedRotationPitch = ExtMath.floorf(trackedEntityIn.rotPitch * 256.0F / 360.0F);
		this.lastHeadMotion = ExtMath.floorf(trackedEntityIn.getRotationYawHead() * 256.0F / 360.0F);
		this.onGround = trackedEntityIn.onGround;
	}

	public boolean equals(Object obj) {
		return obj instanceof EntityTrackerEntry
				? ((EntityTrackerEntry)obj).trackedEntity.getId() == this.trackedEntity.getId()
				: false;
	}

	public int hashCode() {
		return this.trackedEntity.getId();
	}

	public void updatePlayerList(List<EntityNPC> players) {
		this.playerEntitiesUpdated = false;

		if(!this.firstUpdateDone
				|| this.trackedEntity.getDistanceSq(this.lastTrackedEntityPosX, this.lastTrackedEntityPosY, this.lastTrackedEntityPosZ) > 16.0D) {
			this.lastTrackedEntityPosX = this.trackedEntity.posX;
			this.lastTrackedEntityPosY = this.trackedEntity.posY;
			this.lastTrackedEntityPosZ = this.trackedEntity.posZ;
			this.firstUpdateDone = true;
			this.playerEntitiesUpdated = true;
			this.updatePlayerEntities(players);
		}

		if(this.lastRiding != this.trackedEntity.vehicle || this.trackedEntity.vehicle != null && this.updateCounter % 60 == 0) {
			this.lastRiding = this.trackedEntity.vehicle;
			this.sendPacketToTrackedPlayers(new S1BPacketEntityAttach(0, this.trackedEntity, this.trackedEntity.vehicle));
		}

//		if(this.trackedEntity instanceof EntityFrame && this.updateCounter % 10 == 0) {
////			EntityItemFrame entityitemframe = (EntityItemFrame)this.trackedEntity;
////			ItemStack itemstack = entityitemframe.getDisplayedItem();
////
////			if(itemstack != null && itemstack.getItem() instanceof ItemMap) {
////				MapData mapdata = Items.filled_map.getMapData(itemstack, this.trackedEntity.worldObj);
////
////				for(EntityNPC entityplayer : players) {
////					EntityNPCMP entityplayermp = (EntityNPCMP)entityplayer;
////					mapdata.updateVisiblePlayers(entityplayermp, itemstack);
////					Packet packet = Items.filled_map.createMapDataPacket(itemstack, this.trackedEntity.worldObj, entityplayermp);
////
////					if(packet != null) {
////						entityplayermp.netHandler.sendPacket(packet);
////					}
////				}
////			}
//
//			this.sendMetadataToAllAssociatedPlayers();
//		}

		if(this.updateCounter % this.updateFrequency == 0 || this.trackedEntity.isAirBorne
				|| this.trackedEntity.getDataWatcher().hasObjectChanged()) {
			if(this.trackedEntity.vehicle == null) {
				++this.ticksSinceLastForcedTeleport;
				int k = ExtMath.floord(this.trackedEntity.posX * 32.0D);
				int j1 = ExtMath.floord(this.trackedEntity.posY * 32.0D);
				int k1 = ExtMath.floord(this.trackedEntity.posZ * 32.0D);
				int l1 = ExtMath.floorf(this.trackedEntity.rotYaw * 256.0F / 360.0F);
				int i2 = ExtMath.floorf(this.trackedEntity.rotPitch * 256.0F / 360.0F);
				int j2 = k - this.encodedPosX;
				int k2 = j1 - this.encodedPosY;
				int i = k1 - this.encodedPosZ;
				Packet packet1 = null;
				boolean flag = Math.abs(j2) >= 4 || Math.abs(k2) >= 4 || Math.abs(i) >= 4 || this.updateCounter % 60 == 0;
				boolean flag1 = Math.abs(l1 - this.encodedRotationYaw) >= 4 || Math.abs(i2 - this.encodedRotationPitch) >= 4;

				if(this.updateCounter > 0 || this.trackedEntity instanceof EntityArrow) {
					if(j2 >= -128 && j2 < 128 && k2 >= -128 && k2 < 128 && i >= -128 && i < 128 && this.ticksSinceLastForcedTeleport <= 400
							&& !this.ridingEntity && this.onGround == this.trackedEntity.onGround) {
						if((!flag || !flag1) && !(this.trackedEntity instanceof EntityArrow)) {
							if(flag) {
								packet1 = new S14PacketEntity.S15PacketEntityRelMove(this.trackedEntity.getId(), (byte)j2, (byte)k2, (byte)i,
										this.trackedEntity.onGround);
							}
							else if(flag1) {
								packet1 = new S14PacketEntity.S16PacketEntityLook(this.trackedEntity.getId(), (byte)l1, (byte)i2,
										this.trackedEntity.onGround);
							}
						}
						else {
							packet1 = new S14PacketEntity.S17PacketEntityLookMove(this.trackedEntity.getId(), (byte)j2, (byte)k2, (byte)i,
									(byte)l1, (byte)i2, this.trackedEntity.onGround);
						}
					}
					else {
						this.onGround = this.trackedEntity.onGround;
						this.ticksSinceLastForcedTeleport = 0;
						packet1 = new S18PacketEntityTeleport(this.trackedEntity.getId(), k, j1, k1, (byte)l1, (byte)i2,
								this.trackedEntity.onGround);
					}
				}

				if(this.sendVelocityUpdates) {
					double d0 = this.trackedEntity.motionX - this.lastTrackedEntityMotionX;
					double d1 = this.trackedEntity.motionY - this.lastTrackedEntityMotionY;
					double d2 = this.trackedEntity.motionZ - this.lastTrackedEntityMotionZ;
					double d3 = 0.02D;
					double d4 = d0 * d0 + d1 * d1 + d2 * d2;

					if(d4 > d3 * d3 || d4 > 0.0D && this.trackedEntity.motionX == 0.0D && this.trackedEntity.motionY == 0.0D
							&& this.trackedEntity.motionZ == 0.0D) {
						this.lastTrackedEntityMotionX = this.trackedEntity.motionX;
						this.lastTrackedEntityMotionY = this.trackedEntity.motionY;
						this.lastTrackedEntityMotionZ = this.trackedEntity.motionZ;
						this.sendPacketToTrackedPlayers(new SPacketEntityVelocity(this.trackedEntity.getId(), this.lastTrackedEntityMotionX,
								this.lastTrackedEntityMotionY, this.lastTrackedEntityMotionZ));
					}
				}

				if(packet1 != null) {
					this.sendPacketToTrackedPlayers(packet1);
				}

				this.sendMetadataToAllAssociatedPlayers();

				if(flag) {
					this.encodedPosX = k;
					this.encodedPosY = j1;
					this.encodedPosZ = k1;
				}

				if(flag1) {
					this.encodedRotationYaw = l1;
					this.encodedRotationPitch = i2;
				}

				this.ridingEntity = false;
			}
			else {
				int j = ExtMath.floorf(this.trackedEntity.rotYaw * 256.0F / 360.0F);
				int i1 = ExtMath.floorf(this.trackedEntity.rotPitch * 256.0F / 360.0F);
				boolean flag2 = Math.abs(j - this.encodedRotationYaw) >= 4 || Math.abs(i1 - this.encodedRotationPitch) >= 4;

				if(flag2) {
					this.sendPacketToTrackedPlayers(new S14PacketEntity.S16PacketEntityLook(this.trackedEntity.getId(), (byte)j, (byte)i1,
							this.trackedEntity.onGround));
					this.encodedRotationYaw = j;
					this.encodedRotationPitch = i1;
				}

				this.encodedPosX = ExtMath.floord(this.trackedEntity.posX * 32.0D);
				this.encodedPosY = ExtMath.floord(this.trackedEntity.posY * 32.0D);
				this.encodedPosZ = ExtMath.floord(this.trackedEntity.posZ * 32.0D);
				this.sendMetadataToAllAssociatedPlayers();
				this.ridingEntity = true;
			}

			int l = ExtMath.floorf(this.trackedEntity.getRotationYawHead() * 256.0F / 360.0F);

			if(Math.abs(l - this.lastHeadMotion) >= 4) {
				this.sendPacketToTrackedPlayers(new S19PacketEntityHeadLook(this.trackedEntity, (byte)l));
				this.lastHeadMotion = l;
			}

			this.trackedEntity.isAirBorne = false;
		}

		++this.updateCounter;

		if(this.trackedEntity.veloChanged) {
			this.sendPacketToTrackedAndSelf(new SPacketEntityVelocity(this.trackedEntity));
			this.trackedEntity.veloChanged = false;
		}
	}

	private void sendMetadataToAllAssociatedPlayers() {
		DataWatcher datawatcher = this.trackedEntity.getDataWatcher();

		if(datawatcher.hasObjectChanged()) {
			this.sendPacketToTrackedAndSelf(new S1CPacketEntityMetadata(this.trackedEntity.getId(), datawatcher, false));
		}

		if(this.trackedEntity instanceof EntityLiving) {
			AttributeMap serversideattributemap = ((EntityLiving)this.trackedEntity).getAttributeMap();
			Set<AttributeInstance> set = serversideattributemap.getDirty();

			if(!set.isEmpty()) {
				this.sendPacketToTrackedAndSelf(new S20PacketEntityProperties(this.trackedEntity.getId(), set));
			}

			set.clear();
		}
	}

	public void sendPacketToTrackedPlayers(Packet packetIn) {
		for(EntityNPC entityplayermp : this.trackingPlayers) {
			entityplayermp.connection.sendPacket(packetIn);
		}
	}

	public void sendPacketToTrackedAndSelf(Packet packetIn) {
		this.sendPacketToTrackedPlayers(packetIn);

		if(this.trackedEntity.isPlayer()) {
			((EntityNPC)this.trackedEntity).connection.sendPacket(packetIn);
		}
	}

	public void sendDestroyEntityPacketToTrackedPlayers() {
		for(EntityNPC entityplayermp : this.trackingPlayers) {
			entityplayermp.connection.removeEntity(this.trackedEntity);
		}
	}

	public void removeFromTrackedPlayers(EntityNPC playerMP) {
		if(this.trackingPlayers.contains(playerMP)) {
			playerMP.connection.removeEntity(this.trackedEntity);
			this.trackingPlayers.remove(playerMP);
		}
	}

	public void updatePlayerEntity(EntityNPC playerMP) {
		if(playerMP != this.trackedEntity) {
			if(this.canBeSeen(playerMP)) {
				if(!this.trackingPlayers.contains(playerMP) && (this.isPlayerWatchingThisChunk(playerMP) || this.trackedEntity.forceSpawn)) {
					this.trackingPlayers.add(playerMP);
					Packet packet = this.createSpawnPacket();
					playerMP.connection.sendPacket(packet);

					if(!this.trackedEntity.getDataWatcher().isBlank()) {
						playerMP.connection
								.sendPacket(new S1CPacketEntityMetadata(this.trackedEntity.getId(), this.trackedEntity.getDataWatcher(), true));
					}

					NBTTagCompound nbttagcompound = this.trackedEntity.getNBTTagCompound();

					if(nbttagcompound != null) {
						playerMP.connection.sendPacket(new S43PacketUpdateEntityNBT(this.trackedEntity.getId(), nbttagcompound));
					}

					if(this.trackedEntity instanceof EntityLiving) {
						AttributeMap serversideattributemap = ((EntityLiving)this.trackedEntity).getAttributeMap();
						Collection<AttributeInstance> collection = serversideattributemap.getWatchedAttributes();

						if(!collection.isEmpty()) {
							playerMP.connection.sendPacket(new S20PacketEntityProperties(this.trackedEntity.getId(), collection));
						}
					}

					this.lastTrackedEntityMotionX = this.trackedEntity.motionX;
					this.lastTrackedEntityMotionY = this.trackedEntity.motionY;
					this.lastTrackedEntityMotionZ = this.trackedEntity.motionZ;
					
//					if(this.trackedEntity.isPlayer() && !((EntityNPCMP)this.trackedEntity).isVisibleTo(playerMP))
//						return;

					if(this.sendVelocityUpdates && !(packet instanceof SPacketSpawnMob)) {
						playerMP.connection.sendPacket(new SPacketEntityVelocity(this.trackedEntity.getId(),
								this.trackedEntity.motionX, this.trackedEntity.motionY, this.trackedEntity.motionZ));
					}

					if(this.trackedEntity.vehicle != null) {
						playerMP.connection.sendPacket(new S1BPacketEntityAttach(0, this.trackedEntity, this.trackedEntity.vehicle));
					}

					if(this.trackedEntity instanceof EntityLiving && ((EntityLiving)this.trackedEntity).getLeashedTo() != null) {
						playerMP.connection.sendPacket(
								new S1BPacketEntityAttach(1, this.trackedEntity, ((EntityLiving)this.trackedEntity).getLeashedTo()));
					}

					if(this.trackedEntity instanceof EntityLiving) {
						for(int i = 0; i < 5; ++i) {
							ItemStack itemstack = ((EntityLiving)this.trackedEntity).getItem(i);

							if(itemstack != null) {
								playerMP.connection
										.sendPacket(new SPacketEntityEquipment(this.trackedEntity.getId(), i, itemstack));
							}
						}
					}

//					if(this.trackedEntity.isPlayer()) {
//						EntityNPC entityplayer = (EntityNPC)this.trackedEntity;
//
//						if(entityplayer.isPlayerSleeping()) {
//							playerMP.netHandler.sendPacket(new SPacketUseBed(entityplayer, new BlockPos(this.trackedEntity)));
//						}
//					}

					if(this.trackedEntity instanceof EntityLiving) {
						EntityLiving entitylivingbase = (EntityLiving)this.trackedEntity;

						for(PotionEffect potioneffect : entitylivingbase.getEffects()) {
							playerMP.connection.sendPacket(new S1DPacketEntityEffect(this.trackedEntity.getId(), potioneffect));
						}
					}
				}
			}
			else if(this.trackingPlayers.contains(playerMP)) {
				this.trackingPlayers.remove(playerMP);
				playerMP.connection.removeEntity(this.trackedEntity);
			}
		}
	}

	public boolean canBeSeen(EntityNPC playerMP) {
		double d0 = playerMP.posX - (double)(this.encodedPosX / 32);
		double d1 = playerMP.posZ - (double)(this.encodedPosZ / 32);
		return d0 >= (double)(-this.trackingDistanceThreshold) && d0 <= (double)this.trackingDistanceThreshold
				&& d1 >= (double)(-this.trackingDistanceThreshold) && d1 <= (double)this.trackingDistanceThreshold
				&& this.trackedEntity.isVisibleTo(playerMP);
	}

	private boolean isPlayerWatchingThisChunk(EntityNPC playerMP) {
		return playerMP.getServerWorld().isPlayerWatchingChunk(playerMP, this.trackedEntity.chunkCoordX,
				this.trackedEntity.chunkCoordZ);
	}

	public void updatePlayerEntities(List<EntityNPC> players) {
		for(int i = 0; i < players.size(); ++i) {
			this.updatePlayerEntity(players.get(i));
		}
	}

	private Packet createSpawnPacket() {
		if(this.trackedEntity.dead) {
			Log.JNI.warn("Erstelle Spawn-Paket für entferntes Objekt");
		}

		SPacketSpawnObject packet = null;
//		int oid = EntityRegistry.getObjectID(this.trackedEntity);
//		if(oid != 0) {
//		}
		if(this.trackedEntity.isPlayer()) {
			return new SPacketSpawnPlayer((EntityNPC)this.trackedEntity);
		}
		else if(this.trackedEntity instanceof EntityLiving) {
			this.lastHeadMotion = ExtMath.floorf(this.trackedEntity.getRotationYawHead() * 256.0F / 360.0F);
			return new SPacketSpawnMob((EntityLiving)this.trackedEntity);
		}
//		else if(this.trackedEntity instanceof EntityPainting) {
//			return new SPacketSpawnPainting((EntityPainting)this.trackedEntity);
//		}
//		else if(this.trackedEntity instanceof EntityXPOrb) {
//			return new SPacketSpawnExperienceOrb((EntityXPOrb)this.trackedEntity);
//		}
//		else if(this.trackedEntity instanceof IObjectData) {
//			packet = new SPacketSpawnObject(this.trackedEntity, ((IObjectData)this.trackedEntity).getPacketData());
//		}
		else {
			return new SPacketSpawnObject(this.trackedEntity);
		}
//		else {
//			throw new IllegalArgumentException("Kann Spawn-Paket für " + this.trackedEntity.getClass() + " nicht erstellen!");
//		}

//		return packet;
	}

	public void removeTrackedPlayerSymmetric(EntityNPC playerMP) {
		if(this.trackingPlayers.contains(playerMP)) {
			this.trackingPlayers.remove(playerMP);
			playerMP.connection.removeEntity(this.trackedEntity);
		}
	}
	
	public boolean isUpdated() {
		return this.playerEntitiesUpdated;
	}
}
