package crazypants.enderio.machine.light;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.block.Block;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.EnumSkyBlock;
import net.minecraftforge.common.util.ForgeDirection;
import com.enderio.core.common.util.BlockCoord;
import com.enderio.core.common.util.ForgeDirectionOffsets;
import com.enderio.core.common.vecmath.Vector3d;
import crazypants.enderio.EnderIO;
import crazypants.enderio.TileEntityEio;
import crazypants.enderio.machine.wireless.WirelessChargedLocation;
import crazypants.enderio.power.Capacitors;
import crazypants.enderio.power.IInternalPowerReceiver;
import crazypants.enderio.power.PowerHandlerUtil;
public class TileElectricLight extends TileEntityEio implements IInternalPowerReceiver {
private ForgeDirection face = ForgeDirection.DOWN;
public static final int RF_USE_PER_TICK = 1;
private boolean init = true;
private List<TileLightNode> lightNodes;
private int[] lightNodeCoords;
private boolean updatingLightNodes = false;
private boolean lastActive = false;
private boolean isInvereted;
private boolean requiresPower = true;
private WirelessChargedLocation chargedLocation;
private int energyStoredRF;
public TileElectricLight() {
}
public void onNeighborBlockChange(Block blockID) {
init = true;
}
public void nodeNeighbourChanged(TileLightNode tileLightNode) {
init = true;
}
public void nodeRemoved(TileLightNode tileLightNode) {
if(!updatingLightNodes) {
init = true;
}
}
public ForgeDirection getFace() {
return face;
}
public void setFace(ForgeDirection face) {
this.face = face;
}
public void setInverted(boolean inverted) {
isInvereted = inverted;
}
public void setRequiresPower(boolean isPowered) {
requiresPower = isPowered;
}
public boolean isRequiresPower() {
return requiresPower;
}
public void setInvereted(boolean isInvereted) {
this.isInvereted = isInvereted;
}
public boolean isInvereted() {
return isInvereted;
}
public void setWireless(boolean wireless) {
if(!wireless) {
chargedLocation = null;
} else if(chargedLocation == null) {
chargedLocation = new WirelessChargedLocation(this);
}
}
public boolean isWireless() {
return chargedLocation != null;
}
@Override
public void doUpdate() {
if(worldObj.isRemote) {
return;
}
boolean isActivated = init ? isPoweredRedstone() ^ isInvereted : lastActive;
if(requiresPower) {
if(isActivated) {
if(!hasPower()) {
isActivated = false;
} else {
setEnergyStored(getEnergyStored() - RF_USE_PER_TICK);
}
}
if(init) {
updateLightNodes();
}
}
if(isActivated != lastActive || init) {
worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, isActivated ? 1 : 0, 2);
if(requiresPower) {
for (TileLightNode ln : lightNodes) {
if(ln != null) {
worldObj.setBlockMetadataWithNotify(ln.xCoord, ln.yCoord, ln.zCoord, isActivated ? 1 : 0, 2);
worldObj.markBlockForUpdate(ln.xCoord, ln.yCoord, ln.zCoord);
worldObj.updateLightByType(EnumSkyBlock.Block, ln.xCoord, ln.yCoord, ln.zCoord);
}
}
}
worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
worldObj.updateLightByType(EnumSkyBlock.Block, xCoord, yCoord, zCoord);
init = false;
lastActive = isActivated;
}
if (chargedLocation != null) {
if (energyStoredRF < getMaxEnergyStored()) {
boolean needInit = energyStoredRF == 0;
energyStoredRF += chargedLocation.takeEnergy(Math.min(getMaxEnergyStored() - energyStoredRF, 10));
if (needInit && energyStoredRF > 0) {
init = true;
}
}
}
}
public void onBlockRemoved() {
if(!requiresPower) {
return;
}
updatingLightNodes = true;
try {
clearLightNodes();
} finally {
updatingLightNodes = false;
}
}
private void updateLightNodes() {
Set<BlockCoord> before;
if(lightNodes != null && !lightNodes.isEmpty()) {
before = new HashSet<BlockCoord>(lightNodes.size());
for (TileLightNode node : lightNodes) {
before.add(node.getLocation());
}
} else {
before = Collections.emptySet();
}
Set<BlockCoord> after = new HashSet<BlockCoord>(17);
updatingLightNodes = true;
try {
if(lightNodeCoords != null) {
// just loaded
lightNodes = new ArrayList<TileLightNode>();
for (int i = 0; i < lightNodeCoords.length; i += 3) {
TileEntity te = worldObj.getTileEntity(lightNodeCoords[i], lightNodeCoords[i + 1], lightNodeCoords[i + 2]);
if(te instanceof TileLightNode) {
lightNodes.add((TileLightNode) te);
}
}
lightNodeCoords = null;
} else if(lightNodes == null) { // just created
lightNodes = new ArrayList<TileLightNode>();
}
for (ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) {
if(dir != face && dir != face.getOpposite()) { // don't project behind
// us
Vector3d offset = ForgeDirectionOffsets.forDirCopy(dir);
addNodeInDirection(new Vector3d(offset), after);
addNodeInDirection(offset.add(ForgeDirectionOffsets.forDirCopy(face.getOpposite())), after);
}
}
addNodeInDirection(ForgeDirectionOffsets.forDirCopy(face.getOpposite()), after);
Vector3d[] diags = new Vector3d[2];
if(face.offsetX != 0) {
diags[0] = ForgeDirectionOffsets.forDirCopy(ForgeDirection.UP);
diags[1] = ForgeDirectionOffsets.forDirCopy(ForgeDirection.SOUTH);
} else if(face.offsetY != 0) {
diags[0] = ForgeDirectionOffsets.forDirCopy(ForgeDirection.EAST);
diags[1] = ForgeDirectionOffsets.forDirCopy(ForgeDirection.SOUTH);
} else {
diags[0] = ForgeDirectionOffsets.forDirCopy(ForgeDirection.UP);
diags[1] = ForgeDirectionOffsets.forDirCopy(ForgeDirection.EAST);
}
addDiaganals(diags, new Vector3d(), after);
addDiaganals(diags, ForgeDirectionOffsets.forDirCopy(face.getOpposite()), after);
if(!before.equals(after)) {
clearLightNodes();
for (BlockCoord entry : after) {
worldObj.setBlock(entry.x, entry.y, entry.z, EnderIO.blockLightNode);
TileEntity te = worldObj.getTileEntity(entry.x, entry.y, entry.z);
if(te instanceof TileLightNode) {
TileLightNode ln = (TileLightNode) te;
ln.parentX = xCoord;
ln.parentY = yCoord;
ln.parentZ = zCoord;
lightNodes.add(ln);
}
}
} else {
init = false;
}
} finally {
updatingLightNodes = false;
}
}
private void addDiaganals(Vector3d[] diags, Vector3d trans, Set<BlockCoord> result) {
Vector3d offset = new Vector3d();
offset.set(diags[0]);
offset.add(diags[1]);
addNodeInDirection(offset.add(trans), result);
offset.set(diags[0]);
offset.sub(diags[1]);
addNodeInDirection(offset.add(trans), result);
offset.set(diags[0]);
offset.negate();
offset.add(diags[1]);
addNodeInDirection(offset.add(trans), result);
offset.set(diags[0]);
offset.negate();
offset.sub(diags[1]);
addNodeInDirection(offset.add(trans), result);
}
private void addNodeInDirection(Vector3d offset, Set<BlockCoord> result) {
boolean isAir = isAir(offset);
boolean isTransp = isTranparent(offset);
if(isAir || isTransp) {
offset.scale(2);
if(isAir(offset)) {
addLightNode(offset, result);
} else if(isAir) {
offset.scale(0.5);
addLightNode(offset, result);
}
}
}
private boolean isLightNode(Vector3d offset) {
return worldObj.getBlock(xCoord + (int) offset.x, yCoord + (int) offset.y, zCoord + (int) offset.z) == EnderIO.blockLightNode;
}
private void clearLightNodes() {
if(lightNodes != null) {
for (TileLightNode ln : lightNodes) {
if(worldObj.getBlock(ln.xCoord, ln.yCoord, ln.zCoord) == EnderIO.blockLightNode) {
worldObj.setBlockToAir(ln.xCoord, ln.yCoord, ln.zCoord);
}
}
lightNodes.clear();
}
}
private void addLightNode(Vector3d offset, Set<BlockCoord> result) {
int x = xCoord + (int) offset.x;
int y = yCoord + (int) offset.y;
int z = zCoord + (int) offset.z;
if(isLightNode(offset)) {
TileLightNode te = (TileLightNode) worldObj.getTileEntity(x, y, z);
if(te.parentX != xCoord || te.parentY != yCoord || te.parentZ != zCoord) {
// its somebody else's so leave it alone
return;
}
}
result.add(new BlockCoord(x, y, z));
}
private boolean isRailcraftException(Block id) {
String className = id.getClass().getName();
return className.equals("mods.railcraft.common.blocks.machine.BlockMachine");
}
private boolean isTranparent(Vector3d offset) {
Block id = worldObj.getBlock(xCoord + (int) offset.x, yCoord + (int) offset.y, zCoord + (int) offset.z);
if(isRailcraftException(id)) {
return false;
}
return worldObj.getBlockLightOpacity(xCoord + (int) offset.x, yCoord + (int) offset.y, zCoord + (int) offset.z) == 0;
}
private boolean isAir(Vector3d offset) {
return worldObj.isAirBlock(xCoord + (int) offset.x, yCoord + (int) offset.y, zCoord + (int) offset.z) || isLightNode(offset);
}
@Override
public void readCustomNBT(NBTTagCompound root) {
face = ForgeDirection.values()[root.getShort("face")];
isInvereted = root.getBoolean("isInverted");
requiresPower = root.getBoolean("requiresPower");
setWireless(root.getBoolean("isWireless"));
if(root.hasKey("storedEnergy")) {
float se = root.getFloat("storedEnergy");
energyStoredRF = (int) (se * 10);
} else {
energyStoredRF = root.getInteger("storedEnergyRF");
}
lightNodeCoords = root.getIntArray("lightNodes");
}
@Override
public void writeCustomNBT(NBTTagCompound root) {
root.setShort("face", (short) face.ordinal());
root.setInteger("storedEnergyRF", energyStoredRF);
root.setBoolean("isInverted", isInvereted);
root.setBoolean("requiresPower", requiresPower);
root.setBoolean("isWireless", isWireless());
if(lightNodes != null) {
int[] lnLoc = new int[lightNodes.size() * 3];
int index = 0;
for (TileLightNode ln : lightNodes) {
lnLoc[index++] = ln.xCoord;
lnLoc[index++] = ln.yCoord;
lnLoc[index++] = ln.zCoord;
}
root.setIntArray("lightNodes", lnLoc);
}
}
public boolean hasPower() {
return energyStoredRF >= RF_USE_PER_TICK;
}
// RF Power
@Override
public int receiveEnergy(ForgeDirection from, int maxReceive, boolean simulate) {
if(!requiresPower) {
return 0;
}
if (energyStoredRF == 0) {
init = true;
}
return PowerHandlerUtil.recieveInternal(this, maxReceive, from, simulate);
}
@Override
public boolean canConnectEnergy(ForgeDirection from) {
return requiresPower;
}
@Override
public int getEnergyStored(ForgeDirection from) {
return getEnergyStored();
}
@Override
public int getMaxEnergyStored(ForgeDirection from) {
return getMaxEnergyStored();
}
@Override
public int getMaxEnergyRecieved(ForgeDirection dir) {
if(!requiresPower) {
return 0;
}
return Capacitors.BASIC_CAPACITOR.capacitor.getMaxEnergyReceived();
}
@Override
public int getEnergyStored() {
if(!requiresPower) {
return 0;
}
return energyStoredRF;
}
@Override
public int getMaxEnergyStored() {
if(!requiresPower) {
return 0;
}
return 100;
}
@Override
public void setEnergyStored(int stored) {
energyStoredRF = stored;
}
@Override
public boolean displayPower() {
return isRequiresPower();
}
}