/**
 * 
 */
package resonantinduction.electrical.tesla;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeDirection;
import resonantinduction.core.Reference;
import resonantinduction.core.ResonantInduction;
import resonantinduction.core.Settings;
import resonantinduction.core.handler.ILinkable;
import resonantinduction.electrical.Electrical;
import universalelectricity.api.energy.EnergyStorageHandler;
import universalelectricity.api.vector.Vector3;
import universalelectricity.api.vector.VectorWorld;
import calclavia.lib.CustomDamageSource;
import calclavia.lib.network.IPacketReceiver;
import calclavia.lib.network.IPacketSender;
import calclavia.lib.prefab.tile.TileElectrical;
import calclavia.lib.render.EnumColor;
import calclavia.lib.utility.LanguageUtility;
import codechicken.lib.colour.ColourRGBA;

import com.google.common.io.ByteArrayDataInput;

import cpw.mods.fml.common.network.PacketDispatcher;

/**
 * The Tesla TileEntity.
 * 
 * - Redstone (Prevent Output Toggle) - Right click (Prevent Input Toggle)
 * 
 * @author Calclavia
 * 
 */
public class TileTesla extends TileElectrical implements ITesla, IPacketSender, IPacketReceiver, ILinkable
{
	public final static int DEFAULT_COLOR = 12;
	public final long TRANSFER_CAP = 10000;
	private int dyeID = DEFAULT_COLOR;

	private boolean canReceive = true;
	private boolean attackEntities = true;

	/** Client side to do sparks */
	private boolean doTransfer = true;

	/** Prevents transfer loops */
	private final Set<TileTesla> outputBlacklist = new HashSet<TileTesla>();
	private final Set<TileTesla> connectedTeslas = new HashSet<TileTesla>();

	/**
	 * Caching
	 */
	private TileTesla topCache = null;
	private TileTesla controlCache = null;

	/**
	 * Quantum Tesla
	 */
	public Vector3 linked;
	public int linkDim;

	/**
	 * Client
	 */
	private int zapCounter = 0;
	private boolean isLinkedClient;
	private boolean isTransfering;

	public TileTesla()
	{
		this.energy = new EnergyStorageHandler(TRANSFER_CAP);
		this.saveIOMap = true;
	}

	@Override
	public void initiate()
	{
		super.initiate();
		TeslaGrid.instance().register(this);
	}

	@Override
	public void updateEntity()
	{
		super.updateEntity();

		boolean doPacketUpdate = this.energy.getEnergy() > 0;

		/**
		 * Only transfer if it is the bottom controlling Tesla tower.
		 */
		if (this.isController())
		{
			// this.produce();

			if (this.ticks % (4 + this.worldObj.rand.nextInt(2)) == 0 && ((this.worldObj.isRemote && isTransfering) || (this.energy.getEnergy() > 0 && !this.worldObj.isBlockIndirectlyGettingPowered(this.xCoord, this.yCoord, this.zCoord))))
			{
				final TileTesla topTesla = this.getTopTelsa();
				final Vector3 topTeslaVector = new Vector3(topTesla);

				/**
				 * Quantum transportation.
				 */
				if (this.linked != null || this.isLinkedClient)
				{
					if (!this.worldObj.isRemote)
					{
						World dimWorld = MinecraftServer.getServer().worldServerForDimension(this.linkDim);

						if (dimWorld != null)
						{
							TileEntity transferTile = this.linked.getTileEntity(dimWorld);

							if (transferTile instanceof TileTesla && !transferTile.isInvalid())
							{
								this.transfer(((TileTesla) transferTile), Math.min(this.energy.getEmptySpace(), TRANSFER_CAP));

								if (this.zapCounter % 5 == 0 && Settings.SOUND_FXS)
								{
									this.worldObj.playSoundEffect(this.xCoord + 0.5, this.yCoord + 0.5, this.zCoord + 0.5, Reference.PREFIX + "electricshock", (float) this.energy.getEnergy() / (float) TRANSFER_CAP, 1.3f - 0.5f * (this.dyeID / 16f));
								}
							}
						}
					}
					else
					{
						Electrical.proxy.renderElectricShock(this.worldObj, topTeslaVector.clone().translate(0.5), topTeslaVector.clone().translate(new Vector3(0.5, Double.POSITIVE_INFINITY, 0.5)), false);
					}
				}
				else
				{
					List<ITesla> transferTeslaCoils = new ArrayList<ITesla>();

					for (ITesla tesla : TeslaGrid.instance().get())
					{
						if (new Vector3((TileEntity) tesla).distance(new Vector3(this)) < this.getRange())
						{
							/**
							 * Make sure Tesla is not part of this tower.
							 */
							if (!this.connectedTeslas.contains(tesla) && tesla.canTeslaTransfer(this))
							{
								if (tesla instanceof TileTesla)
								{
									if (((TileTesla) tesla).getHeight() <= 1)
									{
										continue;
									}

									tesla = ((TileTesla) tesla).getControllingTelsa();
								}

								transferTeslaCoils.add(tesla);
							}
						}
					}

					/**
					 * Sort by distance.
					 */
					Collections.sort(transferTeslaCoils, new Comparator()
					{
						public int compare(ITesla o1, ITesla o2)
						{
							double distance1 = new Vector3(topTesla).distance(new Vector3((TileEntity) o1));
							double distance2 = new Vector3(topTesla).distance(new Vector3((TileEntity) o2));

							if (distance1 < distance2)
							{
								return 1;
							}
							else if (distance1 > distance2)
							{
								return -1;
							}

							return 0;
						}

						@Override
						public int compare(Object obj, Object obj1)
						{
							return compare((ITesla) obj, (ITesla) obj1);
						}
					});

					if (transferTeslaCoils.size() > 0)
					{
						long transferEnergy = this.energy.getEnergy() / transferTeslaCoils.size();
						int count = 0;
						boolean sentPacket = false;
						for (ITesla tesla : transferTeslaCoils)
						{
							if (this.zapCounter % 5 == 0 && Settings.SOUND_FXS)
							{
								this.worldObj.playSoundEffect(this.xCoord + 0.5, this.yCoord + 0.5, this.zCoord + 0.5, Reference.PREFIX + "electricshock", (float) this.energy.getEnergy() / (float) TRANSFER_CAP, 1.3f - 0.5f * (this.dyeID / 16f));
							}

							Vector3 targetVector = new Vector3((TileEntity) tesla);

							if (tesla instanceof TileTesla)
							{
								((TileTesla) tesla).getControllingTelsa().outputBlacklist.add(this);
								targetVector = new Vector3(((TileTesla) tesla).getTopTelsa());
							}

							double distance = topTeslaVector.distance(targetVector);
							// TODO: Fix color.
							Electrical.proxy.renderElectricShock(this.worldObj, new Vector3(topTesla).translate(new Vector3(0.5)), targetVector.translate(new Vector3(0.5)), EnumColor.DYES[this.dyeID].toColor());

							this.transfer(tesla, Math.min(transferEnergy, TRANSFER_CAP));

							if (!sentPacket && transferEnergy > 0)
							{
								this.sendPacket(3);
							}

							if (this.attackEntities && this.zapCounter % 5 == 0)
							{
								MovingObjectPosition mop = topTeslaVector.clone().translate(0.5).rayTraceEntities(this.worldObj, targetVector.clone().translate(0.5));

								if (mop != null && mop.entityHit != null)
								{
									if (mop.entityHit instanceof EntityLivingBase)
									{
										mop.entityHit.attackEntityFrom(CustomDamageSource.electrocution, 4);
										Electrical.proxy.renderElectricShock(this.worldObj, new Vector3(topTesla).clone().translate(0.5), new Vector3(mop.entityHit));
									}
								}
							}

							if (count++ > 1)
							{
								break;
							}
						}
					}
				}

				this.zapCounter++;
				this.outputBlacklist.clear();

				this.doTransfer = false;
			}

			if (!this.worldObj.isRemote && this.energy.didEnergyStateChange())
			{
				this.sendPacket(2);
			}
		}

		this.clearCache();
	}

	private void transfer(ITesla tesla, long transferEnergy)
	{
		if (transferEnergy > 0)
		{
			tesla.teslaTransfer((long) (transferEnergy * (1 - (this.worldObj.rand.nextFloat() * 0.1f))), true);
			this.teslaTransfer(-transferEnergy, true);
		}
	}

	@Override
	public boolean canTeslaTransfer(TileEntity tileEntity)
	{
		return this.canReceive && !this.outputBlacklist.contains(tileEntity);
	}

	@Override
	public boolean canConnect(ForgeDirection direction)
	{
		return super.canConnect(direction) && this.isController();
	}

	public void sendPacket(int type)
	{
		PacketDispatcher.sendPacketToAllInDimension(ResonantInduction.PACKET_TILE.getPacket(this, this.getPacketData(type).toArray()), this.worldObj.provider.dimensionId);
	}

	@Override
	public Packet getDescriptionPacket()
	{
		return ResonantInduction.PACKET_TILE.getPacket(this, this.getPacketData(1).toArray());
	}

	/**
	 * 1 - Description Packet
	 * 2 - Energy Update
	 * 3 - Tesla Beam
	 */
	@Override
	public ArrayList getPacketData(int type)
	{
		ArrayList data = new ArrayList();
		data.add((byte) type);

		switch (type)
		{
			case 1:
			{
				data.add(this.dyeID);
				data.add(this.canReceive);
				data.add(this.attackEntities);
				data.add(this.linked != null);
				break;
			}
			case 2:
			{
				data.add(this.energy.getEnergy() > 0);
				break;
			}
		}

		return data;
	}

	@Override
	public void onReceivePacket(ByteArrayDataInput data, EntityPlayer player, Object... extra)
	{
		try
		{
			switch (data.readByte())
			{
				case 1:
					this.dyeID = data.readInt();
					this.canReceive = data.readBoolean();
					this.attackEntities = data.readBoolean();
					this.isLinkedClient = data.readBoolean();
					break;
				case 2:
					this.isTransfering = data.readBoolean();
					break;
				case 3:
					this.doTransfer = true;
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	private boolean isController()
	{
		return this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord) == 0;
	}

	private void clearCache()
	{
		this.topCache = null;
		this.controlCache = null;
	}

	@Override
	public long teslaTransfer(long transferEnergy, boolean doTransfer)
	{
		if (isController() || this.getControllingTelsa() == this)
		{
			if (doTransfer)
			{
				this.energy.receiveEnergy(transferEnergy, true);

				if (this.energy.didEnergyStateChange())
				{
					this.sendPacket(2);
				}
			}

			return transferEnergy;
		}
		else
		{
			if (this.energy.getEnergy() > 0)
			{
				transferEnergy += this.energy.getEnergy();
				this.energy.setEnergy(0);
			}

			return this.getControllingTelsa().teslaTransfer(transferEnergy, doTransfer);
		}
	}

	public int getRange()
	{
		return Math.min(4 * (this.getHeight() - 1), 50);
	}

	public void updatePositionStatus()
	{
		boolean isTop = new Vector3(this).translate(new Vector3(0, 1, 0)).getTileEntity(this.worldObj) instanceof TileTesla;
		boolean isBottom = new Vector3(this).translate(new Vector3(0, -1, 0)).getTileEntity(this.worldObj) instanceof TileTesla;

		if (isTop && isBottom)
		{
			this.worldObj.setBlockMetadataWithNotify(this.xCoord, this.yCoord, this.zCoord, 1, 3);
		}
		else if (isBottom)
		{
			this.worldObj.setBlockMetadataWithNotify(this.xCoord, this.yCoord, this.zCoord, 2, 3);
		}
		else
		{
			this.worldObj.setBlockMetadataWithNotify(this.xCoord, this.yCoord, this.zCoord, 0, 3);
		}
	}

	/**
	 * Called only on bottom.
	 * 
	 * @return The highest Tesla coil in this tower.
	 */
	public TileTesla getTopTelsa()
	{
		if (this.topCache != null)
		{
			return this.topCache;
		}

		this.connectedTeslas.clear();
		Vector3 checkPosition = new Vector3(this);
		TileTesla returnTile = this;

		while (true)
		{
			TileEntity t = checkPosition.getTileEntity(this.worldObj);

			if (t instanceof TileTesla)
			{
				this.connectedTeslas.add((TileTesla) t);
				returnTile = (TileTesla) t;
			}
			else
			{
				break;
			}

			checkPosition.y++;
		}

		this.topCache = returnTile;
		return returnTile;
	}

	/**
	 * For non-controlling Tesla to use.
	 * 
	 * @return
	 */
	public TileTesla getControllingTelsa()
	{
		if (this.controlCache != null)
		{
			return this.controlCache;
		}

		Vector3 checkPosition = new Vector3(this);
		TileTesla returnTile = this;

		while (true)
		{
			TileEntity t = checkPosition.getTileEntity(this.worldObj);

			if (t instanceof TileTesla)
			{
				returnTile = (TileTesla) t;
			}
			else
			{
				break;
			}

			checkPosition.y--;
		}

		this.controlCache = returnTile;
		return returnTile;
	}

	/**
	 * Called only on bottom.
	 * 
	 * @return The highest Tesla coil in this tower.
	 */
	public int getHeight()
	{
		this.connectedTeslas.clear();
		int y = 0;

		while (true)
		{
			TileEntity t = new Vector3(this).translate(new Vector3(0, y, 0)).getTileEntity(this.worldObj);

			if (t instanceof TileTesla)
			{
				this.connectedTeslas.add((TileTesla) t);
				y++;
			}
			else
			{
				break;
			}

		}

		return y;
	}

	@Override
	public void invalidate()
	{
		TeslaGrid.instance().unregister(this);
		super.invalidate();
	}

	public void setDye(int id)
	{
		this.dyeID = id;
		this.worldObj.markBlockForUpdate(this.xCoord, this.yCoord, this.zCoord);
	}

	public boolean toggleReceive()
	{
		return this.canReceive = !this.canReceive;
	}

	public boolean toggleEntityAttack()
	{
		boolean returnBool = this.attackEntities = !this.attackEntities;
		this.worldObj.markBlockForUpdate(this.xCoord, this.yCoord, this.zCoord);
		return returnBool;
	}

	/**
	 * Reads a tile entity from NBT.
	 */
	@Override
	public void readFromNBT(NBTTagCompound nbt)
	{
		super.readFromNBT(nbt);
		this.dyeID = nbt.getInteger("dyeID");
		this.canReceive = nbt.getBoolean("canReceive");
		this.attackEntities = nbt.getBoolean("attackEntities");

		if (nbt.hasKey("link_x") && nbt.hasKey("link_y") && nbt.hasKey("link_z"))
		{
			this.linked = new Vector3(nbt.getInteger("link_x"), nbt.getInteger("link_y"), nbt.getInteger("link_z"));
			this.linkDim = nbt.getInteger("linkDim");
		}
	}

	/**
	 * Writes a tile entity to NBT.
	 */
	@Override
	public void writeToNBT(NBTTagCompound nbt)
	{
		super.writeToNBT(nbt);
		nbt.setInteger("dyeID", this.dyeID);
		nbt.setBoolean("canReceive", this.canReceive);
		nbt.setBoolean("attackEntities", this.attackEntities);

		if (this.linked != null)
		{
			nbt.setInteger("link_x", (int) this.linked.x);
			nbt.setInteger("link_y", (int) this.linked.y);
			nbt.setInteger("link_z", (int) this.linked.z);
			nbt.setInteger("linkDim", this.linkDim);
		}
	}

	public void setLink(Vector3 vector3, int dimID, boolean setOpponent)
	{
		if (!this.worldObj.isRemote)
		{
			World otherWorld = MinecraftServer.getServer().worldServerForDimension(this.linkDim);

			if (setOpponent && this.linked != null && otherWorld != null)
			{
				TileEntity tileEntity = this.linked.getTileEntity(otherWorld);

				if (tileEntity instanceof TileTesla)
				{
					((TileTesla) tileEntity).setLink(null, this.linkDim, false);
				}
			}

			this.linked = vector3;
			this.linkDim = dimID;
			this.worldObj.markBlockForUpdate(this.xCoord, this.yCoord, this.zCoord);

			World newOtherWorld = MinecraftServer.getServer().worldServerForDimension(this.linkDim);

			if (setOpponent && newOtherWorld != null && this.linked != null)
			{
				TileEntity tileEntity = this.linked.getTileEntity(newOtherWorld);

				if (tileEntity instanceof TileTesla)
				{
					((TileTesla) tileEntity).setLink(new Vector3(this), this.worldObj.provider.dimensionId, false);
				}
			}
		}
	}

	@Override
	public boolean onLink(EntityPlayer player, VectorWorld vector)
	{
		if (linked == null)
		{
			if (vector != null)
			{
				if (!worldObj.isRemote)
				{
					World otherWorld = vector.world;

					if (vector.getTileEntity(otherWorld) instanceof TileTesla)
					{
						this.setLink(new Vector3(((TileTesla) vector.getTileEntity(otherWorld)).getTopTelsa()), vector.world.provider.dimensionId, true);
						player.addChatMessage(LanguageUtility.getLocal("message.tesla.pair").replace("%v0", this.getBlockType().getLocalizedName()).replace("%v1", vector.x + "").replace("%v2", vector.y + "").replace("%v3", vector.z + ""));
						worldObj.playSoundEffect(this.xCoord + 0.5, this.yCoord + 0.5, this.zCoord + 0.5, "ambient.weather.thunder", 5, 1);
						return true;
					}
				}
			}
		}
		else
		{
			this.setLink(null, worldObj.provider.dimensionId, true);

			if (!worldObj.isRemote)
			{
				player.addChatMessage("Unlinked Tesla.");
			}
		}

		return false;
	}
}
