package com.hbm.tileentity.machine.rbmk;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.LinkedHashMap;

import com.hbm.handler.CompatHandler;
import com.hbm.interfaces.IControlReceiver;
import com.hbm.inventory.fluid.Fluids;
import com.hbm.inventory.gui.GUIRBMKConsole;
import com.hbm.tileentity.IGUIProvider;
import com.hbm.tileentity.TileEntityMachineBase;
import com.hbm.tileentity.machine.rbmk.TileEntityRBMKControlManual.RBMKColor;
import com.hbm.util.BufferUtil;
import com.hbm.util.Compat;
import com.hbm.util.EnumUtil;
import com.hbm.util.i18n.I18nUtil;

import cpw.mods.fml.common.Optional;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.*;
import net.minecraft.world.World;

import li.cil.oc.api.machine.Arguments;
import li.cil.oc.api.machine.Callback;
import li.cil.oc.api.machine.Context;
import li.cil.oc.api.network.SimpleComponent;

@Optional.InterfaceList({@Optional.Interface(iface = "li.cil.oc.api.network.SimpleComponent", modid = "OpenComputers")})
public class TileEntityRBMKConsole extends TileEntityMachineBase implements IControlReceiver, IGUIProvider, SimpleComponent, CompatHandler.OCComponent {

	private int targetX;
	private int targetY;
	private int targetZ;

	private byte rotation;

	public static final int fluxDisplayBuffer = 60;
	public int[] fluxBuffer = new int[fluxDisplayBuffer];

	//made this one-dimensional because it's a lot easier to serialize
	public RBMKColumn[] columns = new RBMKColumn[15 * 15];

	public RBMKScreen[] screens = new RBMKScreen[6];

	public TileEntityRBMKConsole() {
		super(0);

		for(int i = 0; i < screens.length; i++) {
			screens[i] = new RBMKScreen();
		}
	}

	@Override
	public String getName() {
		return null;
	}

	@Override
	public void updateEntity() {

		if(!worldObj.isRemote) {

			if(this.worldObj.getTotalWorldTime() % 10 == 0) {

				this.worldObj.theProfiler.startSection("rbmkConsole_rescan");
				rescan();
				this.worldObj.theProfiler.endSection();
				prepareScreenInfo();
			}

			this.networkPackNT(50);
		}
	}

	private void rescan() {

		double flux = 0;

		for(int index = 0; index < columns.length; index++) {
			int rx = getXFromIndex(index);
			int rz = getZFromIndex(index);

			TileEntity te = Compat.getTileStandard(worldObj, targetX + rx, targetY, targetZ + rz);

			if(te instanceof TileEntityRBMKBase) {

				TileEntityRBMKBase rbmk = (TileEntityRBMKBase)te;

				columns[index] = new RBMKColumn(rbmk.getConsoleType(), rbmk.getNBTForConsole());
				columns[index].data.setDouble("heat", rbmk.heat);
				columns[index].data.setDouble("maxHeat", rbmk.maxHeat());
				if(rbmk.isModerated()) columns[index].data.setBoolean("moderated", true); //false is the default anyway and not setting it when we don't need to reduces cruft

				if(te instanceof TileEntityRBMKRod) {
					TileEntityRBMKRod fuel = (TileEntityRBMKRod) te;
					flux += fuel.lastFluxQuantity;
				}

			} else {
				columns[index] = null;
			}
		}

		for(int i = 0; i < this.fluxBuffer.length - 1; i++) {
			this.fluxBuffer[i] = this.fluxBuffer[i + 1];
		}

		this.fluxBuffer[this.fluxBuffer.length - 1] = (int) flux;
	}

	@SuppressWarnings("incomplete-switch") //shut up
	private void prepareScreenInfo() {

		for(RBMKScreen screen : this.screens) {

			if(screen.type == ScreenType.NONE) {
				screen.display = null;
				continue;
			}

			double value = 0;
			int count = 0;

			for(Integer i : screen.columns) {

				RBMKColumn col = this.columns[i];

				if(col == null)
					continue;

				switch(screen.type) {
				case COL_TEMP:
					count++;
					value += col.data.getDouble("heat");
					break;
				case FUEL_DEPLETION:
					if(col.data.hasKey("enrichment")) {
						count++;
						value += (100D - (col.data.getDouble("enrichment") * 100D));
					}
					break;
				case FUEL_POISON:
					if(col.data.hasKey("xenon")) {
						count++;
						value += col.data.getDouble("xenon");
					}
					break;
				case FUEL_TEMP:
					if(col.data.hasKey("c_heat")) {
						count++;
						value += col.data.getDouble("c_heat");
					}
					break;
				case ROD_EXTRACTION:
					if(col.data.hasKey("level")) {
						count++;
						value += col.data.getDouble("level") * 100;
					}
					break;
				}
			}

			double result = value / (double) count;
			String text = ((int)(result * 10)) / 10D + "";

			switch(screen.type) {
			case COL_TEMP: text = "rbmk.screen.temp=" + text + "°C"; break;
			case FUEL_DEPLETION: text = "rbmk.screen.depletion=" + text + "%"; break;
			case FUEL_POISON: text = "rbmk.screen.xenon=" + text + "%"; break;
			case FUEL_TEMP: text = "rbmk.screen.core=" + text + "°C"; break;
			case ROD_EXTRACTION: text = "rbmk.screen.rod=" + text + "%"; break;
			}

			screen.display = text;
		}
	}

	@Override
	public void serialize(ByteBuf buf) {
		super.serialize(buf);

		if (this.worldObj.getTotalWorldTime() % 10 == 0) {
			buf.writeBoolean(true);

			for (RBMKColumn column : this.columns) {
				if (column == null || column.type == null)
					buf.writeByte(-1);
				else {
					buf.writeByte((byte) column.type.ordinal());
					BufferUtil.writeNBT(buf, column.data);
				}
			}

			BufferUtil.writeIntArray(buf, fluxBuffer);

			for (RBMKScreen screen : this.screens) {
				BufferUtil.writeString(buf, screen.display);
			}

		} else {

			buf.writeBoolean(false);

			for (RBMKScreen screen : screens) {
				buf.writeByte((byte) screen.type.ordinal());
			}
		}
	}

	@Override
	public void deserialize(ByteBuf buf) {
		super.deserialize(buf);

		if (buf.readBoolean()) { // check if it should be a full packet

			for(int i = 0; i < this.columns.length; i++) {
				byte ordinal = buf.readByte();
				if (ordinal == -1)
					this.columns[i] = null;
				else
					this.columns[i] = new RBMKColumn(ColumnType.values()[ordinal], BufferUtil.readNBT(buf));
			}

			this.fluxBuffer = BufferUtil.readIntArray(buf);

			for (RBMKScreen screen : this.screens) {
				screen.display = BufferUtil.readString(buf);
			}

		} else {

			for (RBMKScreen screen : this.screens) {
				screen.type = ScreenType.values()[buf.readByte()];
			}

		}
	}

	@Override
	public boolean hasPermission(EntityPlayer player) {
		return Vec3.createVectorHelper(xCoord - player.posX, yCoord - player.posY, zCoord - player.posZ).lengthVector() < 20;
	}

	@Override
	public void receiveControl(NBTTagCompound data) {

		if(data.hasKey("level")) {

			Set<String> keys = data.func_150296_c();

			for(String key : keys) {

				if(key.startsWith("sel_")) {

					int index = data.getInteger(key);
					int x = getXFromIndex(index);
					int z = getZFromIndex(index);

					TileEntity te = Compat.getTileStandard(worldObj, targetX + x, targetY, targetZ + z);

					if(te instanceof TileEntityRBMKControlManual) {
						TileEntityRBMKControlManual rod = (TileEntityRBMKControlManual) te;
						rod.startingLevel = rod.level;
						rod.setTarget(MathHelper.clamp_double(data.getDouble("level"), 0, 1));
						te.markDirty();
					}
				}
			}
		}

		if(data.hasKey("toggle")) {
			int slot = data.getByte("toggle");
			int next = this.screens[slot].type.ordinal() + 1;
			ScreenType type = ScreenType.values()[next % ScreenType.values().length];
			this.screens[slot].type = type;
		}

		if(data.hasKey("id")) {
			int slot = data.getByte("id");
			List<Integer> list = new ArrayList();

			for(int i = 0; i < 15 * 15; i++) {
				if(data.getBoolean("s" + i)) {
					list.add(i);
				}
			}

			Integer[] cols = list.toArray(new Integer[0]);
			this.screens[slot].columns = cols;
		}

		if(data.hasKey("assignColor")) {
			int color = data.getByte("assignColor");
			int[] cols = data.getIntArray("cols");

			for(int i : cols) {
				int x = getXFromIndex(i);
				int z = getZFromIndex(i);

				TileEntity te = Compat.getTileStandard(worldObj, targetX + x, targetY, targetZ + z);

				if(te instanceof TileEntityRBMKControlManual) {
					TileEntityRBMKControlManual rod = (TileEntityRBMKControlManual) te;
					rod.color = EnumUtil.grabEnumSafely(RBMKColor.class, color);
					te.markDirty();
				}
			}
		}

		if(data.hasKey("compressor")) {
			int[] cols = data.getIntArray("cols");

			for(int i : cols) {
				int x = getXFromIndex(i);
				int z = getZFromIndex(i);

				TileEntity te = Compat.getTileStandard(worldObj, targetX + x, targetY, targetZ + z);

				if(te instanceof TileEntityRBMKBoiler) {
					TileEntityRBMKBoiler rod = (TileEntityRBMKBoiler) te;
					rod.cyceCompressor();
				}
			}
		}
	}

	@Override
	public AxisAlignedBB getRenderBoundingBox() {
		return AxisAlignedBB.getBoundingBox(xCoord - 2, yCoord, zCoord - 2, xCoord + 3, yCoord + 4, zCoord + 3);
	}

	@Override
	@SideOnly(Side.CLIENT)
	public double getMaxRenderDistanceSquared() {
		return 65536.0D;
	}

	public void setTarget(int x, int y, int z) {
		this.targetX = x;
		this.targetY = y;
		this.targetZ = z;
		this.markDirty();
	}

	@Override
	public void readFromNBT(NBTTagCompound nbt) {
		super.readFromNBT(nbt);

		this.targetX = nbt.getInteger("tX");
		this.targetY = nbt.getInteger("tY");
		this.targetZ = nbt.getInteger("tZ");

		for(int i = 0; i < this.screens.length; i++) {
			this.screens[i].type = ScreenType.values()[nbt.getByte("t" + i)];
			this.screens[i].columns = Arrays.stream(nbt.getIntArray("s" + i)).boxed().toArray(Integer[]::new);
		}
		rotation = nbt.getByte("rotation");
	}

	@Override
	public void writeToNBT(NBTTagCompound nbt) {
		super.writeToNBT(nbt);

		nbt.setInteger("tX", this.targetX);
		nbt.setInteger("tY", this.targetY);
		nbt.setInteger("tZ", this.targetZ);

		for(int i = 0; i < this.screens.length; i++) {
			nbt.setByte("t" + i, (byte) this.screens[i].type.ordinal());
			nbt.setIntArray("s" + i, Arrays.stream(this.screens[i].columns).mapToInt(Integer::intValue).toArray());
		}
		nbt.setByte("rotation", rotation);
	}

	public void rotate() {
		rotation = (byte)((rotation + 1) % 4);
	}

	public int getXFromIndex(int col) {
		final int i = col % 15 - 7;
		final int j = col / 15 - 7;
		switch (rotation) {
			case 0: // 0°
				return i;
			case 1: // 90°
				return -j;
			case 2: // 180°
				return -i;
			case 3: // 270°
				return j;
		}

		return i;
	}

	public int getZFromIndex(int col) {
		final int i = col % 15 - 7;
		final int j = col / 15 - 7;
		switch (rotation) {
			case 0: // 0°
				return j;
			case 1: // 90°
				return i;
			case 2: // 180°
				return -j;
			case 3: // 270°
				return -i;
		}

		return j;
	}

	public static class RBMKColumn {

		public ColumnType type;
		public NBTTagCompound data;

		public RBMKColumn(ColumnType type) {
			this.type = type;
		}

		public RBMKColumn(ColumnType type, NBTTagCompound data) {
			this.type = type;

			if(data != null) {
				this.data = data;
			} else {
				this.data = new NBTTagCompound();
			}
		}

		@SuppressWarnings("incomplete-switch")
		@SideOnly(Side.CLIENT)
		public List<String> getFancyStats() {

			if(this.data == null)
				return null;

			/*
			 * Making a big switch with the values converted based on type by hand might seem "UnPrOfEsSiOnAl" and a major pain in the ass
			 * but my only other solution that would not have me do things in multiple places where they shouldn't be involved passing
			 * classes in the enum and then calling a special method from that class and quite honestly it turned out to be such a crime
			 * against humanity that I threw the towel. It's not fancy, I get that, please fuck off.
			 */

			List<String> stats = new ArrayList();
			stats.add(EnumChatFormatting.YELLOW + I18nUtil.resolveKey("rbmk.heat", ((int)((this.data.getDouble("heat") * 10D)) / 10D) + "°C"));
			switch(this.type) {

			case FUEL:
			case FUEL_SIM:
				stats.add(EnumChatFormatting.GREEN + I18nUtil.resolveKey("rbmk.rod.depletion", ((int)(((1D - this.data.getDouble("enrichment")) * 100000)) / 1000D) + "%"));
				stats.add(EnumChatFormatting.DARK_PURPLE + I18nUtil.resolveKey("rbmk.rod.xenon", ((int)(((this.data.getDouble("xenon")) * 1000D)) / 1000D) + "%"));
				stats.add(EnumChatFormatting.DARK_RED + I18nUtil.resolveKey("rbmk.rod.coreTemp", ((int)((this.data.getDouble("c_coreHeat") * 10D)) / 10D) + "°C"));
				stats.add(EnumChatFormatting.RED + I18nUtil.resolveKey("rbmk.rod.skinTemp", ((int)((this.data.getDouble("c_heat") * 10D)) / 10D) + "°C", ((int)((this.data.getDouble("c_maxHeat") * 10D)) / 10D) + "°C"));
				break;
			case BOILER:
				stats.add(EnumChatFormatting.BLUE + I18nUtil.resolveKey("rbmk.boiler.water", this.data.getInteger("water"), this.data.getInteger("maxWater")));
				stats.add(EnumChatFormatting.WHITE + I18nUtil.resolveKey("rbmk.boiler.steam", this.data.getInteger("steam"), this.data.getInteger("maxSteam")));
				stats.add(EnumChatFormatting.YELLOW + I18nUtil.resolveKey("rbmk.boiler.type", Fluids.fromID(this.data.getShort("type")).getLocalizedName()));
				break;
			case CONTROL:

				if(this.data.hasKey("color")) {
					short col = this.data.getShort("color");

					if(col >= 0 && col < RBMKColor.values().length)
						stats.add(EnumChatFormatting.YELLOW + I18nUtil.resolveKey("rbmk.control." + RBMKColor.values()[col].name().toLowerCase(Locale.US)));
				}

			case CONTROL_AUTO:
				stats.add(EnumChatFormatting.YELLOW + I18nUtil.resolveKey("rbmk.control.level", ((int)((this.data.getDouble("level") * 100D))) + "%"));
				break;

			case HEATEX:
				stats.add(EnumChatFormatting.BLUE + Fluids.fromID(this.data.getShort("type")).getLocalizedName() + " " +
			this.data.getInteger("water") + "/" + this.data.getInteger("maxWater") + "mB");
				stats.add(EnumChatFormatting.RED + Fluids.fromID(this.data.getShort("hottype")).getLocalizedName() + " " +
			this.data.getInteger("steam") + "/" + this.data.getInteger("maxSteam") + "mB");
				break;
			}

			if(data.getBoolean("moderated"))
				stats.add(EnumChatFormatting.YELLOW + I18nUtil.resolveKey("rbmk.moderated"));

			return stats;
		}
	}

	public static enum ColumnType {
		BLANK(0),
		FUEL(10),
		FUEL_SIM(90),
		CONTROL(20),
		CONTROL_AUTO(30),
		BOILER(40),
		MODERATOR(50),
		ABSORBER(60),
		REFLECTOR(70),
		OUTGASSER(80),
		BREEDER(100),
		STORAGE(110),
		COOLER(120),
		HEATEX(130);

		public int offset;

		private ColumnType(int offset) {
			this.offset = offset;
		}
	}

	public class RBMKScreen {
		public ScreenType type = ScreenType.NONE;
		public Integer[] columns = new Integer[0];
		public String display = null;

		public RBMKScreen() { }
		public RBMKScreen(ScreenType type, Integer[] columns, String display) {
			this.type = type;
			this.columns = columns;
			this.display = display;
		}
	}

	public static enum ScreenType {
		NONE(0 * 18),
		COL_TEMP(1 * 18),
		ROD_EXTRACTION(2 * 18),
		FUEL_DEPLETION(3 * 18),
		FUEL_POISON(4 * 18),
		FUEL_TEMP(5 * 18);

		public int offset;

		private ScreenType(int offset) {
			this.offset = offset;
		}
	}

	@Override
	public Container provideContainer(int ID, EntityPlayer player, World world, int x, int y, int z) {
		return null;
	}

	@Override
	@SideOnly(Side.CLIENT)
	public Object provideGUI(int ID, EntityPlayer player, World world, int x, int y, int z) {
		return new GUIRBMKConsole(player.inventory, this);
	}

	// do some opencomputer stuff
	@Override
	@Optional.Method(modid = "OpenComputers")
	public String getComponentName() {
		return "rbmk_console";
	}

	@Callback(direct = true)
	@Optional.Method(modid = "OpenComputers")
	public Object[] getColumnData(Context context, Arguments args) {
		int x = args.checkInteger(0) - 7;
		int y = -args.checkInteger(1) + 7;

		int i = x;
		int j = y;
		switch (rotation) {
			case 0:
				break;
			case 1:
				i = y;
				j = -x;
				break;
			case 2:
				i = -x;
				j = -y;
				break;
			case 3:
				i = -y;
				j = x;
				break;
		}

		int index = (j + 7) * 15 + (i + 7);

		TileEntity te = Compat.getTileStandard(worldObj, targetX + x, targetY, targetZ + y);
		if (te instanceof TileEntityRBMKBase) {
			TileEntityRBMKBase column = (TileEntityRBMKBase) te;

			NBTTagCompound column_data = columns[index].data;
			LinkedHashMap<String, Object> data_table = new LinkedHashMap<>();
			data_table.put("type", column.getConsoleType().name());
			data_table.put("hullTemp", column_data.getDouble("heat"));
			data_table.put("realSimWater", column.reasimWater);
			data_table.put("realSimSteam", column.reasimSteam);
			data_table.put("moderated", column_data.getBoolean("moderated"));
			data_table.put("level", column_data.getDouble("level"));
			data_table.put("targetLevel", column_data.getDouble("targetLevel"));
			data_table.put("color", column_data.getShort("color"));
			data_table.put("enrichment", column_data.getDouble("enrichment"));
			data_table.put("xenon", column_data.getDouble("xenon"));
			data_table.put("coreSkinTemp", column_data.getDouble("c_heat"));
			data_table.put("coreTemp", column_data.getDouble("c_coreHeat"));
			data_table.put("coreMaxTemp", column_data.getDouble("c_maxHeat"));

			if (te instanceof TileEntityRBMKControlAuto){
				TileEntityRBMKControlAuto controlAuto = (TileEntityRBMKControlAuto) te;
				data_table.put("function", controlAuto.function.toString());
				data_table.put("heatUpper", controlAuto.heatUpper);
				data_table.put("heatLower", controlAuto.heatLower);
				data_table.put("levelUpper", controlAuto.levelUpper);
				data_table.put("levelLower",controlAuto.levelLower);
			}

			if(te instanceof TileEntityRBMKRod){
				TileEntityRBMKRod fuelChannel = (TileEntityRBMKRod)te;
				data_table.put("fluxQuantity", fuelChannel.lastFluxQuantity);
				data_table.put("fluxRatio", fuelChannel.fluxFastRatio);
				ItemStack rod = fuelChannel.getStackInSlot(0);
				if (rod != null)
					data_table.put("rodName", rod.getUnlocalizedName());
				else
					data_table.put("rodName", "");
			}

			if(te instanceof TileEntityRBMKBoiler){
				TileEntityRBMKBoiler boiler = (TileEntityRBMKBoiler)te;
				data_table.put("water", boiler.feed.getFill());
				data_table.put("steam", boiler.steam.getFill());
				data_table.put("steamType", Fluids.fromID(column_data.getShort("type")).getUnlocalizedName());
			}

			if(te instanceof TileEntityRBMKOutgasser){
				TileEntityRBMKOutgasser irradiationChannel = (TileEntityRBMKOutgasser)te;
				data_table.put("fluxProgress", irradiationChannel.progress);
				data_table.put("requiredFlux", irradiationChannel.duration);
				ItemStack input = irradiationChannel.getStackInSlot(0);
				if (input != null){
					data_table.put("craftingName", input.getUnlocalizedName());
					data_table.put("craftingNumber", input.stackSize);
				}
				else {
					data_table.put("craftingName", "");
					data_table.put("craftingNumber", 0);
				}
			}

			if(te instanceof TileEntityRBMKHeater){
				TileEntityRBMKHeater heaterChannel = (TileEntityRBMKHeater)te;
				data_table.put("coolant", heaterChannel.feed.getFill());
				data_table.put("hotcoolant", heaterChannel.steam.getFill());
				data_table.put("coldtype", Fluids.fromID(column_data.getShort("type")).getUnlocalizedName());
				data_table.put("hottype", Fluids.fromID(column_data.getShort("hottype")).getUnlocalizedName());
			}

			if (te instanceof TileEntityRBMKCooler){
				TileEntityRBMKCooler coolerChannel = (TileEntityRBMKCooler) te;
				data_table.put("cryogel", coolerChannel.getAllTanks()[0].getFill());
			}

			return new Object[] {data_table};
		}
		return new Object[] {null};
	}

	@Callback(direct = true)
	@Optional.Method(modid = "OpenComputers")
	public Object[] getRBMKPos(Context context, Arguments args) {
		if(!(targetX == 0 && targetY== 0 && targetZ==0)){
			LinkedHashMap<String, Integer> data_table = new LinkedHashMap<>();
			data_table.put("rbmkCenterX", targetX);
			data_table.put("rbmkCenterY", targetY);
			data_table.put("rbmkCenterZ", targetZ);

			return new Object[] {data_table};
		}
		return new Object[] {null};
	}

	@Callback(direct = true)
	@Optional.Method(modid = "OpenComputers")
	public Object[] setLevel(Context context, Arguments args) {
		double new_level = args.checkDouble(0);
		boolean foundRods = false;
		for(int i = -7; i <= 7; i++) {
			for(int j = -7; j <= 7; j++) {
				TileEntity te = Compat.getTileStandard(worldObj, targetX + i, targetY, targetZ + j);

				if (te instanceof TileEntityRBMKControlManual) {
					TileEntityRBMKControlManual rod = (TileEntityRBMKControlManual) te;
					rod.startingLevel = rod.level;
					new_level = Math.min(1, Math.max(0, new_level));

					rod.setTarget(new_level);
					te.markDirty();
					foundRods = true;
				}
			}
		}
		if(foundRods)
			return new Object[] {};
		else
			return new Object[] {"No control rods found"};
	}

	@Callback(direct = true)
	@Optional.Method(modid = "OpenComputers")
	public Object[] setColumnLevel(Context context, Arguments args) {
		int x = args.checkInteger(0) - 7;
		int y = -args.checkInteger(1) + 7;
		double new_level = args.checkDouble(2);

		TileEntity te = Compat.getTileStandard(worldObj, targetX + x, targetY, targetZ + y);

		if (te instanceof TileEntityRBMKControlManual) {
			TileEntityRBMKControlManual rod = (TileEntityRBMKControlManual) te;
			rod.startingLevel = rod.level;
			new_level = Math.min(1, Math.max(0, new_level));

			rod.setTarget(new_level);
			te.markDirty();
			return new Object[] {};
		}
		return new Object[] {"No control rod found at "+(x+7)+","+(7-y)};
	}

	@Callback(direct = true)
	@Optional.Method(modid = "OpenComputers")
	public Object[] setColorLevel(Context context, Arguments args) {
		int color = args.checkInteger(0);
		double new_level = args.checkDouble(1);
		boolean foundRods = false;
		if(color >= 0 && color <=4){
			for(int i = -7; i <= 7; i++) {
				for(int j = -7; j <= 7; j++) {
					TileEntity te = Compat.getTileStandard(worldObj, targetX + i, targetY, targetZ + j);

					if (te instanceof TileEntityRBMKControlManual) {
						TileEntityRBMKControlManual rod = (TileEntityRBMKControlManual) te;
						if(rod.color == RBMKColor.values()[color]){
							rod.startingLevel = rod.level;
							new_level = Math.min(1, Math.max(0, new_level));

							rod.setTarget(new_level);
							te.markDirty();
							foundRods = true;
						}
					}
				}
			}
			if(foundRods)
				return new Object[] {};
			else
				return new Object[] { "No rods for color "+color+" found" };
		}
		return new Object[] {"Color "+color+" does not exist"};
	}

	@Callback(direct = true)
	@Optional.Method(modid = "OpenComputers")
	public Object[] setColor(Context context, Arguments args) {
		int x = args.checkInteger(0) - 7;
		int y = -args.checkInteger(1) + 7;
		int new_color = args.checkInteger(2);
		if(new_color >= 0 && new_color <=4){
			TileEntity te = Compat.getTileStandard(worldObj, targetX + x, targetY, targetZ + y);

			if (te instanceof TileEntityRBMKControlManual) {
				TileEntityRBMKControlManual rod = (TileEntityRBMKControlManual) te;
				rod.color = RBMKColor.values()[new_color];
				te.markDirty();
				return new Object[] {};
			}
			return new Object[] {"No control rod found at "+(x+7)+","+(7-y)};
		}
		return new Object[] {"Color "+new_color+" does not exist"};
	}

	@Callback(direct = true)
	@Optional.Method(modid = "OpenComputers")
	public Object[] pressAZ5(Context context, Arguments args) {
		worldObj.playSoundEffect(xCoord + 0.5, yCoord + 0.5, zCoord + 0.5,"hbm:block.shutdown",1.0F, 1.0F);
		boolean hasRods = false;
		for(int i = -7; i <= 7; i++) {
			for(int j = -7; j <= 7; j++) {
				TileEntity te = Compat.getTileStandard(worldObj, targetX + i, targetY, targetZ + j);

				if (te instanceof TileEntityRBMKControlManual) {
					TileEntityRBMKControlManual rod = (TileEntityRBMKControlManual) te;
					rod.startingLevel = rod.level;
					rod.setTarget(0);
					te.markDirty();
					hasRods = true;
				}
			}
		}
		if(hasRods){
			return new Object[] {};
		} else {
			return new Object[] {"No control rods found"};
		}
	}
}
