package advancedsystemsmanager.tileentities;
import advancedsystemsmanager.api.network.IPacketBlock;
import advancedsystemsmanager.api.tileentities.IRedstoneEmitter;
import advancedsystemsmanager.flow.menus.MenuPulse;
import advancedsystemsmanager.flow.menus.MenuRedstoneOutput;
import advancedsystemsmanager.flow.menus.MenuRedstoneSidesEmitter;
import advancedsystemsmanager.network.ASMPacket;
import advancedsystemsmanager.network.PacketHandler;
import advancedsystemsmanager.registry.BlockRegistry;
import advancedsystemsmanager.registry.ClusterRegistry;
import advancedsystemsmanager.util.ClusterMethodRegistration;
import advancedsystemsmanager.util.SystemCoord;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraftforge.common.util.ForgeDirection;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
public class TileEntityEmitter extends TileEntityElementBase implements IPacketBlock, IRedstoneEmitter
{
private static final String NBT_SIDES = "Sides";
private static final String NBT_STRENGTH = "Strength";
private static final String NBT_STRONG = "Strong";
private static final String NBT_TICK = "Tick";
private static final String NBT_PULSES = "Pulses";
private static final int UPDATE_BUFFER_DISTANCE = 5;
private int[] strengths;
private boolean[] strong;
private int[] updatedStrength;
private boolean[] updatedStrong;
private List<PulseTimer>[] pulseTimers;
private boolean hasUpdatedThisTick;
private List<SystemCoord> scheduledToUpdate = new ArrayList<SystemCoord>();
private boolean hasUpdatedData;
public TileEntityEmitter()
{
strengths = new int[ForgeDirection.VALID_DIRECTIONS.length];
strong = new boolean[ForgeDirection.VALID_DIRECTIONS.length];
updatedStrength = new int[ForgeDirection.VALID_DIRECTIONS.length];
updatedStrong = new boolean[ForgeDirection.VALID_DIRECTIONS.length];
pulseTimers = new List[ForgeDirection.VALID_DIRECTIONS.length];
for (int i = 0; i < pulseTimers.length; i++)
{
pulseTimers[i] = new ArrayList<PulseTimer>();
}
}
public boolean hasStrongSignalAtSide(int side)
{
return strong[side];
}
public boolean hasStrongSignalAtOppositeSide(int side)
{
return strong[getOpposite(side)];
}
private int getOpposite(int side)
{
return ForgeDirection.getOrientation(side).getOpposite().ordinal();
}
public int getStrengthFromOppositeSide(int side)
{
return getStrengthFromSide(getOpposite(side));
}
public int getStrengthFromSide(int side)
{
return strengths[side];
}
public void updateState(MenuRedstoneSidesEmitter sides, MenuRedstoneOutput output, MenuPulse pulse)
{
boolean updateClient = false;
for (int i = 0; i < ForgeDirection.VALID_DIRECTIONS.length; i++)
{
if (sides.isSideRequired(i))
{
int oldStrength = updatedStrength[i];
boolean oldStrong = updatedStrong[i];
updateSideState(i, output);
updatedStrong[i] = sides.useStrongSignal();
/*if (((updatedStrength[i] > 0) != (oldStrength > 0)) || (oldStrong != updatedStrong[i])) {
updateClient = true;
}*/
boolean updateBlocks = oldStrength != updatedStrength[i] || oldStrong != updatedStrong[i];
if (updateBlocks)
{
updateClient = true;
}
if (updateBlocks)
{
addBlockScheduledForUpdate(i);
}
if (pulse.shouldEmitPulse())
{
PulseTimer timer = new PulseTimer(oldStrength, oldStrong, pulse.getPulseTime() + 1); //add one to counter the first tick (which is the same tick as we add it)
List<PulseTimer> timers = pulseTimers[i];
if (timers.size() < 200)
{ //to block a huge amount of pulses at the same time
switch (pulse.getSelectedPulseOverride())
{
case EXTEND_OLD:
if (timers.size() > 0)
{
if (timers.size() > 1)
{
PulseTimer temp = timers.get(0);
timers.clear();
timers.add(temp);
}
PulseTimer oldTimer = timers.get(0);
oldTimer.ticks = Math.max(oldTimer.ticks, timer.ticks);
} else
{
timers.add(timer);
}
break;
case KEEP_ALL:
timers.add(timer);
break;
case KEEP_NEW:
timers.clear();
timers.add(timer);
break;
case KEEP_OLD:
if (timers.isEmpty())
{
timers.add(timer);
}
}
}
}
}
}
if (updateClient)
{
sendPacketToClient(CLIENT_SYNC);
}
}
private void updateSideState(int side, MenuRedstoneOutput output)
{
int strength = updatedStrength[side];
int selectedStrength = output.getSelectedStrength();
switch (output.getSelectedSetting())
{
case FIXED:
strength = selectedStrength;
break;
case TOGGLE:
strength = strength > 0 ? 0 : 15;
break;
case MAX:
strength = Math.max(strength, selectedStrength);
break;
case MIN:
strength = Math.min(strength, selectedStrength);
break;
case INCREASE:
strength = Math.min(15, strength + selectedStrength);
break;
case DECREASE:
strength = Math.max(0, strength - selectedStrength);
break;
case FORWARD:
strength = (strength + selectedStrength) % 16;
break;
case BACKWARD:
strength -= selectedStrength;
if (strength < 0) strength += 16;
break;
}
updatedStrength[side] = strength;
}
private void addBlockScheduledForUpdate(int side)
{
hasUpdatedThisTick = true;
ForgeDirection direction = ForgeDirection.getOrientation(side);
int x = xCoord + direction.offsetX;
int y = yCoord + direction.offsetY;
int z = zCoord + direction.offsetZ;
SystemCoord coordinate = new SystemCoord(x, y, z, this.worldObj);
if (!scheduledToUpdate.contains(coordinate))
{
scheduledToUpdate.add(coordinate);
}
}
private void notifyUpdate(int x, int y, int z, boolean spread)
{
if (ClusterRegistry.CABLE.isBlock(worldObj.getBlock(x, y, z), worldObj.getBlockMetadata(x, y, z)) && (x != xCoord || y != yCoord || z != zCoord))
{
worldObj.notifyBlockOfNeighborChange(x, y, z, getBlockType());
if (spread)
{
notifyUpdate(x - 1, y, z, false);
notifyUpdate(x + 1, y, z, false);
notifyUpdate(x, y - 1, z, false);
notifyUpdate(x, y + 1, z, false);
notifyUpdate(x, y, z - 1, false);
notifyUpdate(x, y, z + 1, false);
}
}
}
@Override
public void writeToTileNBT(NBTTagCompound nbtTagCompound)
{
NBTTagList sidesTag = new NBTTagList();
for (int i = 0; i < strengths.length; i++)
{
NBTTagCompound sideTag = new NBTTagCompound();
sideTag.setByte(NBT_STRENGTH, (byte)updatedStrength[i]);
sideTag.setBoolean(NBT_STRONG, updatedStrong[i]);
NBTTagList pulsesTag = new NBTTagList();
List<PulseTimer> timers = pulseTimers[i];
for (PulseTimer timer : timers)
{
NBTTagCompound pulseTag = new NBTTagCompound();
pulseTag.setByte(NBT_STRENGTH, (byte)timer.strength);
pulseTag.setBoolean(NBT_STRONG, timer.strong);
pulseTag.setShort(NBT_TICK, (short)timer.ticks);
pulsesTag.appendTag(pulseTag);
}
sideTag.setTag(NBT_PULSES, pulsesTag);
sidesTag.appendTag(sideTag);
}
nbtTagCompound.setTag(NBT_SIDES, sidesTag);
}
@Override
public void readFromTileNBT(NBTTagCompound nbtTagCompound)
{
NBTTagList sidesTag = nbtTagCompound.getTagList(NBT_SIDES, 10);
for (int i = 0; i < sidesTag.tagCount(); i++)
{
NBTTagCompound sideTag = sidesTag.getCompoundTagAt(i);
strengths[i] = updatedStrength[i] = sideTag.getByte(NBT_STRENGTH);
strong[i] = updatedStrong[i] = sideTag.getBoolean(NBT_STRONG);
List<PulseTimer> timers = pulseTimers[i];
timers.clear();
NBTTagList pulsesTag = sideTag.getTagList(NBT_PULSES, 10);
for (int j = 0; j < pulsesTag.tagCount(); j++)
{
NBTTagCompound pulseTag = pulsesTag.getCompoundTagAt(j);
timers.add(new PulseTimer(pulseTag.getByte(NBT_STRENGTH), pulseTag.getBoolean(NBT_STRONG), pulseTag.getShort(NBT_TICK)));
}
}
}
@Override
public void writeClientSyncData(ASMPacket packet)
{
super.writeClientSyncData(packet);
for (int i = 0; i < ForgeDirection.VALID_DIRECTIONS.length; i++)
{
packet.writeByte(updatedStrength[i] | (updatedStrong[i] ? 1 << 5 : 0));
}
}
@Override
public void readClientSyncData(ASMPacket packet)
{
super.readClientSyncData(packet);
for (int i = 0; i < ForgeDirection.VALID_DIRECTIONS.length; i++)
{
byte val = packet.readByte();
strengths[i] = val & 0xF;
strong[i] = (val >> 5) > 0;
}
}
@Override
public void updateEntity()
{
if (!worldObj.isRemote)
{
updatePulses();
if (hasUpdatedThisTick)
{
hasUpdatedThisTick = false;
List<SystemCoord> coordinates = new ArrayList<SystemCoord>(scheduledToUpdate);
scheduledToUpdate.clear();
for (int i = 0; i < strengths.length; i++)
{
strengths[i] = updatedStrength[i];
strong[i] = updatedStrong[i];
}
for (SystemCoord coordinate : coordinates)
{
notifyUpdate(coordinate.getX(), coordinate.getY(), coordinate.getZ(), true);
}
}
}
}
private void updatePulses()
{
boolean updateClient = false;
for (int i = 0; i < ForgeDirection.VALID_DIRECTIONS.length; i++)
{
Iterator<PulseTimer> iterator = pulseTimers[i].iterator();
while (iterator.hasNext())
{
PulseTimer timer = iterator.next();
timer.ticks--;
if (timer.ticks == 0)
{
if (updatedStrength[i] != timer.strength || updatedStrong[i] == timer.strong)
{
updatedStrength[i] = timer.strength;
updatedStrong[i] = timer.strong;
addBlockScheduledForUpdate(i);
updateClient = true;
}
iterator.remove();
}
}
}
if (updateClient)
{
sendPacketToClient(CLIENT_SYNC);
}
}
@Override
public int[] getPower()
{
return updatedStrength;
}
private class PulseTimer
{
private int strength;
private boolean strong;
private int ticks;
private PulseTimer(int strength, boolean strong, int ticks)
{
this.strength = strength;
this.strong = strong;
this.ticks = ticks;
}
}
}