package crazypants.enderio.rail;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.UUID;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.item.EntityMinecart;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagString;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.util.ForgeDirection;
import crazypants.enderio.config.Config;
import crazypants.enderio.machine.transceiver.TileTransceiver;
import crazypants.util.BlockCoord;
import crazypants.util.EntityUtil;
import crazypants.util.ForgeDirectionOffsets;
import crazypants.vecmath.Vector3d;
public class EnderRailController {
//keep track of which carts we have received have left the track as they can't be teleported again until they have
private final Set<UUID> newlySpawnedCarts = new HashSet<UUID>();
private final LinkedList<List<Entity>> cartsToSpawn = new LinkedList<List<Entity>>();
private final List<PlayerTpInfo> playersToRemount = new ArrayList<EnderRailController.PlayerTpInfo>();
private int ticksFailedToSpawn = 0;
private NBTTagList cartList;
private final TileTransceiver transciever;
public EnderRailController(TileTransceiver tileTransceiver) {
transciever = tileTransceiver;
}
public void doTick() {
if(cartList != null) {
loadCartsToSpawn();
cartList = null;
}
spawnRecievedCart();
List<EntityMinecart> carts = getMinecartsOnTrack();
List<UUID> toRemove = new ArrayList<UUID>();
//any cart in the newly spawned list no longer on the track needs to be removed
for (UUID recievedCartUID : newlySpawnedCarts) {
EntityMinecart minecart = getMinecartWthUUID(recievedCartUID, carts);
if(minecart == null) {
toRemove.add(recievedCartUID);
}
}
for (UUID recievedCart : toRemove) {
newlySpawnedCarts.remove(recievedCart);
}
}
private EntityMinecart getMinecartWthUUID(UUID recievedCartUID, Collection<EntityMinecart> carts) {
for (EntityMinecart cart : carts) {
if(recievedCartUID.equals(cart.getPersistentID())) {
return cart;
}
}
return null;
}
private void spawnRecievedCart() {
if(Config.enderRailTeleportPlayers) {
ListIterator<PlayerTpInfo> li = playersToRemount.listIterator();
while (li.hasNext()) {
PlayerTpInfo tpi = li.next();
if(tpi.attemptMount() || tpi.attemptsRemaining <= 0) {
li.remove();
}
}
}
if(cartsToSpawn.isEmpty()) {
ticksFailedToSpawn = 0;
return;
}
boolean failedSpawn = false;
if(!isClear()) {
ticksFailedToSpawn++;
if(ticksFailedToSpawn < Config.enderRailTicksBeforeForceSpawningLinkedCarts) {
return;
}
failedSpawn = true;
}
List<Entity> spawnThisTick = cartsToSpawn.removeFirst();
for (Entity ent : spawnThisTick) {
if(failedSpawn) {
doRandomSpawn(ent);
} else {
EntityMinecart cart = null;
if(ent instanceof EntityMinecart) {
cart = (EntityMinecart) ent;
setCartDirection(cart);
}
TeleportUtil.spawn(transciever.getWorldObj(), ent);
if(cart != null) {
newlySpawnedCarts.add(cart.getPersistentID());
CartLinkUtil.recreateLinks(cart);
}
}
}
ticksFailedToSpawn = 0;
}
private void setCartDirection(EntityMinecart cart) {
int meta = transciever.getWorldObj().getBlockMetadata(transciever.xCoord, transciever.yCoord + 1, transciever.zCoord);
ForgeDirection dir = BlockEnderRail.getDirection(meta);
double velocity = Math.max(Math.abs(cart.motionX), Math.abs(cart.motionZ));
cart.motionX = dir.offsetX * velocity;
cart.motionZ = dir.offsetZ * velocity;
}
public void onTrainRecieved(List<List<Entity>> toTeleport) {
cartsToSpawn.addAll(toTeleport);
}
public boolean isRecievedCart(EntityMinecart mc) {
return newlySpawnedCarts.contains(mc.getPersistentID());
}
public boolean isClear() {
World worldObj = transciever.getWorldObj();
BlockCoord railCoord = new BlockCoord(transciever).getLocation(ForgeDirection.UP);
int meta = worldObj.getBlockMetadata(railCoord.x, railCoord.y, railCoord.z);
double buf = 1;
ForgeDirection dir = BlockEnderRail.getDirection(meta);
Vector3d offset = ForgeDirectionOffsets.forDirCopy(dir);
offset.scale(buf);
offset.x = Math.abs(offset.x);
offset.z = Math.abs(offset.z);
List res = worldObj.getEntitiesWithinAABB(Entity.class, AxisAlignedBB.getBoundingBox(railCoord.x - offset.x, railCoord.y,
railCoord.z - offset.z, railCoord.x + 1 + offset.x, railCoord.y + 1, railCoord.z + 1 + offset.z));
return res == null || res.isEmpty();
}
public List<EntityMinecart> getMinecartsOnTrack() {
return getMinecartsAt(transciever.getWorldObj(), transciever.xCoord, transciever.yCoord + 1, transciever.zCoord);
}
public static List<EntityMinecart> getMinecartsAt(World world, int x, int y, int z) {
List entities = world.getEntitiesWithinAABB(EntityMinecart.class, AxisAlignedBB.getBoundingBox(x, y, z, x + 1, y + 1, z + 1));
List<EntityMinecart> carts = new ArrayList<EntityMinecart>();
for (Object o : entities) {
EntityMinecart cart = (EntityMinecart) o;
if(!cart.isDead) {
carts.add((EntityMinecart) o);
}
}
return carts;
}
private void loadCartsToSpawn() {
World worldObj = transciever.getWorldObj();
while (cartList.tagCount() > 0) {
NBTTagList entityList = (NBTTagList) cartList.removeTag(0);
List<Entity> ents = new ArrayList<Entity>(entityList.tagCount());
for (int i = 0; i < entityList.tagCount(); i++) {
NBTTagCompound entityRoot = entityList.getCompoundTagAt(i);
Entity entity = EntityList.createEntityFromNBT(entityRoot, worldObj);
if(entity != null) {
ents.add(entity);
}
}
cartsToSpawn.add(ents);
}
}
public void readFromNBT(NBTTagCompound root) {
cartsToSpawn.clear();
if(!root.hasKey("cartList")) {
return;
}
cartList = (NBTTagList) root.getTag("cartList");
newlySpawnedCarts.clear();
if(root.hasKey("newlySpawnedCarts")) {
NBTTagList spawnedCartList = (NBTTagList) root.getTag("newlySpawnedCarts");
for (int i = 0; i < spawnedCartList.tagCount(); i++) {
String uuisStr = spawnedCartList.getStringTagAt(i);
newlySpawnedCarts.add(UUID.fromString(uuisStr));
}
}
}
public void writeToNBT(NBTTagCompound root) {
if(cartsToSpawn.isEmpty()) {
return;
}
NBTTagList cartList = new NBTTagList();
for (List<Entity> entsInCart : cartsToSpawn) {
if(entsInCart != null && !entsInCart.isEmpty()) {
NBTTagList entityList = new NBTTagList();
cartList.appendTag(entityList);
for (Entity entity : entsInCart) {
NBTTagCompound entRoot = new NBTTagCompound();
entRoot.setString("id", EntityList.getEntityString(entity));
entity.writeToNBT(entRoot);
entityList.appendTag(entRoot);
}
}
}
root.setTag("cartList", cartList);
if(!newlySpawnedCarts.isEmpty()) {
NBTTagList spawnedCartList = new NBTTagList();
for (UUID uuid : newlySpawnedCarts) {
spawnedCartList.appendTag(new NBTTagString(uuid.toString()));
}
root.setTag("newlySpawnedCarts", spawnedCartList);
}
}
public void dropNonSpawnedCarts() {
if(cartsToSpawn.isEmpty()) {
return;
}
for (List<Entity> entList : cartsToSpawn) {
for (Entity entity : entList) {
doRandomSpawn(entity);
}
}
cartsToSpawn.clear();
}
private void doRandomSpawn(Entity entity) {
if(entity == null) {
return;
}
double oX = entity.posX;
double oZ = entity.posZ;
World world = transciever.getWorldObj();
MinecraftServer minecraftserver = MinecraftServer.getServer();
WorldServer worldserver = minecraftserver.worldServerForDimension(world.provider.dimensionId);
for (int i = 0; i < 4; i++) {
int x = transciever.xCoord + randOffset(2);
int y = transciever.yCoord + 1;
int z = transciever.zCoord + randOffset(2);
Block b = world.getBlock(x, y, z);
entity.setPosition(x + 0.5, entity.posY, z + 0.5);
if(world.canPlaceEntityOnSide(b, x, y, z, false, ForgeDirection.UP.ordinal(), entity, null)) {
resetForRandomRandomSpawn(entity);
if(worldserver.spawnEntityInWorld(entity)) {
//entity.onUpdate();
worldserver.updateEntity(entity);
return;
}
}
}
entity.setPosition(oX, entity.posY, oZ);
resetForRandomRandomSpawn(entity);
worldserver.spawnEntityInWorld(entity);
}
private void resetForRandomRandomSpawn(Entity entity) {
CartLinkUtil.breakLinks(transciever.getWorldObj(), entity);
entity.riddenByEntity = null;
entity.ridingEntity = null;
entity.motionX = 0;
entity.motionY = 0;
entity.motionZ = 0;
// if(entity instanceof EntityMinecart) {
// entity.posY -= 0.3;
// }
entity.prevPosX = entity.posX;
entity.prevPosY = entity.posY;
entity.prevPosZ = entity.posZ;
entity.rotationYaw = (float) (transciever.getWorldObj().rand.nextDouble() * 360);
}
private int randOffset(int spread) {
return (int) Math.round((transciever.getWorldObj().rand.nextDouble() - 0.5) * spread * 2);
}
public void onPlayerTeleported(EntityPlayerMP playerToTP, EntityMinecart toMount) {
if(playerToTP != null) {
playersToRemount.add(new PlayerTpInfo(playerToTP.getCommandSenderName(), toMount.getPersistentID(), 20));
}
}
private class PlayerTpInfo {
String playerName;
UUID cartId;
int attemptsRemaining;
PlayerTpInfo(String playerName, UUID cartId, int attemptsRemaining) {
this.playerName = playerName;
this.cartId = cartId;
this.attemptsRemaining = attemptsRemaining;
}
boolean attemptMount() {
attemptsRemaining--;
List<EntityMinecart> carts = getMinecartsOnTrack();
for (EntityMinecart cart : carts) {
if(cart != null && cart.getPersistentID().equals(cartId)) {
EntityPlayer player = transciever.getWorldObj().getPlayerEntityByName(playerName);
if(player != null) {
Vector3d playerPos = EntityUtil.getEntityPosition(player);
Vector3d cartPos = EntityUtil.getEntityPosition(cart);
if(playerPos.distanceSquared(cartPos) <= 9) {
player.posX = cart.posX;
player.posY = cart.posY;
player.posZ = cart.posZ;
player.mountEntity(cart);
return true;
}
}
}
}
return false;
}
}
}