@Mod(modid = "PLC", name = "modPLC", version = "0.0.1")
@NetworkMod(clientSideRequired = true, serverSideRequired = true, packetHandler = PacketHandler.class, channels = { "modPLCChannel12" })
public class PLC {

	public static Block controller = null;
	public static Block extenderBasic = null;

	public static Item multiItem = null;
	public static Item itemWaferRaw = null;
	public static ItemStack itemWafer = null;
	public static ItemStack itemWaferRed = null;
	public static ItemStack itemWaferGreen = null;
	public static ItemStack itemWaferBlue = null;
	public static ItemStack itemChipRed = null;
	public static ItemStack itemChipGreen = null;
	public static ItemStack itemChipBlue = null;
	public static ItemStack itemInterface = null;
	public static ItemStack itemInterfaceRedstone = null;

	private NetworkBroker networkBroker = new NetworkBroker();

	// The instance of your mod that Forge uses.
	@Instance("PLC")
	public static PLC instance;

	// Says where the client and server 'proxy' code is loaded.
	@SidedProxy(clientSide = "de.squig.plc.client.ClientProxy", serverSide = "de.squig.plc.CommonProxy")
	public static CommonProxy proxy;

	@PreInit
	public void preInit(FMLPreInitializationEvent event) {
		LogHelper.init();
		Configuration config = new Configuration(
				event.getSuggestedConfigurationFile());
		config.load();

		StaticData.BlockController = config.getBlock("controller", 500)
				.getInt();
		StaticData.BlockExtender = config.getBlock("extender", 501).getInt();
		StaticData.range = config.get("extenders", "range", 32,
				"Range of Extenders to connect controllers (-1 no limit)")
				.getInt();

		StaticData.ItemsBase = config.getItem("itemsBase", 1900).getInt();
		StaticData.ItemWaferRaw = config.getItem("waferRaw", 1901).getInt();

		config.save();

		CircuitElement.addCircuitElementType(0, Deleted.class);
		CircuitElement.addCircuitElementType(1, Line.class);
		CircuitElement.addCircuitElementType(2, Input.class);
		CircuitElement.addCircuitElementType(3, Output.class);
		CircuitElement.addCircuitElementType(50, Not.class);
		CircuitElement.addCircuitElementType(51, High.class);
		CircuitElement.addCircuitElementType(100, Counter.class);
		CircuitElement.addCircuitElementType(101, Pulse.class);
		CircuitElement.addCircuitElementType(102, Timer.class);
		CircuitElement.addCircuitElementType(103, Delay.class);

		CircuitObject.addCircuitObjectType(1, LogicInput.class, null, null);
		CircuitObject.addCircuitObjectType(2, LogicOutput.class, null, null);
		CircuitObject.addCircuitObjectType(3, LogicMemory.class, null, null);
		CircuitObject.addCircuitObjectType(10, LogicDelay.class,
				LogicDelay.dataTypes, LogicDelay.dataStatics);
		CircuitObject.addCircuitObjectType(11, LogicTimer.class,
				LogicTimer.dataTypes, LogicTimer.dataStatics);
		CircuitObject.addCircuitObjectType(12, LogicCounter.class,
				LogicCounter.dataTypes, LogicCounter.dataStatics);

	}

	@Init
	public void load(FMLInitializationEvent event) {
		proxy.registerRenderers();
		proxy.initTileEntities();

		// TickRegistry.registerTickHandler(new TickHandler(), Side.SERVER);

		NetworkRegistry.instance().registerGuiHandler(instance, proxy);

		controller = new Controller(StaticData.BlockController);
		extenderBasic = new ExtenderBasic(StaticData.BlockExtender);

		multiItem = new ItemMulti(StaticData.ItemsBase);
		itemWaferRaw = new ItemRawWafer(StaticData.ItemWaferRaw);

		LogHelper.info("multiItem @" + StaticData.ItemsBase);
		LogHelper.info("WafterRaw @" + StaticData.ItemWaferRaw);

		itemWafer = new ItemStack(multiItem, 1, StaticData.ItemsShiftWafer);
		itemWaferRed = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftRedWafer);
		itemWaferGreen = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftGreenWafer);
		itemWaferBlue = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftBlueWafer);
		itemChipRed = new ItemStack(multiItem, 1, StaticData.ItemsShiftRedChip);
		itemChipGreen = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftGreenChip);
		itemChipBlue = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftBlueChip);
		itemInterface = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftInterface);
		itemInterfaceRedstone = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftInterfaceRedstone);

		LanguageRegistry.addName(controller, "Controller");
		MinecraftForge.setBlockHarvestLevel(controller, "pickaxe", 3);
		GameRegistry.registerBlock(controller, "plc.controller");

		LanguageRegistry.addName(extenderBasic, "Basic Extender");
		MinecraftForge.setBlockHarvestLevel(extenderBasic, "pickaxe", 3);
		GameRegistry.registerBlock(extenderBasic, "plc.extender");

		GameRegistry.registerItem(itemWaferRaw, "plc.rawwafer");
		GameRegistry.registerItem(multiItem, "plc.multiitem");

		LanguageRegistry.addName(itemWaferRaw, "Raw Wafer");
		LanguageRegistry.addName(itemWafer, "Sliced Wafer");
		LanguageRegistry.addName(itemWaferRed, "Red Wafer");
		LanguageRegistry.addName(itemWaferGreen, "Green Wafer");
		LanguageRegistry.addName(itemWaferBlue, "Blue Wafer");
		LanguageRegistry.addName(itemChipRed, "Power Chip");
		LanguageRegistry.addName(itemChipGreen, "Controller Chip");
		LanguageRegistry.addName(itemChipBlue, "Memory Chip");
		LanguageRegistry.addName(itemInterface, "Interface Card");
		LanguageRegistry.addName(itemInterfaceRedstone, "Redstone Interface");

		GameRegistry.addRecipe(new ItemStack(itemWaferRaw, 1), new Object[] {
				"SCS", "CSC", "SCS", 'C', Item.coal, 'S', Block.sand });

		GameRegistry.addSmelting(itemWaferRaw.shiftedIndex, new ItemStack(
				multiItem, 8, StaticData.ItemsShiftWafer), 0.1f);

		GameRegistry.addRecipe(new ItemStack(multiItem, 3,
				StaticData.ItemsShiftRedWafer), new Object[] { "   ", " R ",
				"WWW", 'W', itemWafer, 'R', Item.redstone });
		GameRegistry.addRecipe(new ItemStack(multiItem, 3,
				StaticData.ItemsShiftGreenWafer), new Object[] { "   ", "GRG",
				"WWW", 'W', itemWafer, 'R', Item.redstone, 'G',
				Item.lightStoneDust });
		GameRegistry.addRecipe(new ItemStack(multiItem, 3,
				StaticData.ItemsShiftBlueWafer), new Object[] { "   ", "LRL",
				"WWW", 'W', itemWafer, 'R', Item.redstone, 'L',
				new ItemStack(Item.dyePowder, 1, 4) });

		GameRegistry.addRecipe(new ItemStack(multiItem, 4,
				StaticData.ItemsShiftRedChip), new Object[] { "WIW", "ICI",
				"WIW", 'C', itemWaferRed, 'I', Item.ingotIron, 'W',
				Block.cloth});
		GameRegistry.addRecipe(new ItemStack(multiItem, 4,
				StaticData.ItemsShiftBlueChip), new Object[] { "WWW", "WCG",
				"WWW", 'C', itemWaferBlue, 'G', Item.goldNugget, 'W',
				Block.cloth});
		GameRegistry.addRecipe(new ItemStack(multiItem, 4,
				StaticData.ItemsShiftGreenChip), new Object[] { "WGW", "ICG",
				"WGW", 'C', itemWaferGreen, 'G', Item.goldNugget, 'I', Item.ingotIron,'W',
				Block.cloth});
		
		GameRegistry.addRecipe(new ItemStack(controller,1), new Object[] { "MMW", "PCG",
				"MMW", 'M', itemChipBlue, 'C', itemChipGreen, 
				'P',itemChipRed,'G', Block.glass,'W',
				Block.planks});
		
		GameRegistry.addRecipe(new ItemStack(extenderBasic,1), new Object[] { "WWI", "PCI",
			"WWI",  'C', itemChipGreen, 
			'P',itemChipRed,
			'I', itemInterface,
			'W', Block.planks});
	
		GameRegistry.addRecipe(new ItemStack(multiItem,8,StaticData.ItemsShiftInterface), new Object[] {
			"   ", "III","PWW", 
			'I', Item.ingotIron,
			'W', Block.woodSingleSlab,
			'P', Item.paper});
	
		GameRegistry.addRecipe(new ItemStack(multiItem,6,StaticData.ItemsShiftInterfaceRedstone), new Object[] {
			" R ", " C "," I ", 
			'C',itemChipRed,
			'I', itemInterface,
			'R', Item.redstone});
		
		// trying to load bc3 int

		try {
			Class plcmod = PLC.class.getClassLoader().loadClass(
					"de.squig.plc.bc3.BC3Integration");
			plcmod.getMethod("init", new Class[0]).invoke(null, new Object[0]);
		} catch (Throwable t) {
			LogHelper.warn("could not load BC3 Integration! " + t.getMessage());
		}

	}

	@PostInit
	public void postInit(FMLPostInitializationEvent event) {

	}

	public NetworkBroker getNetworkBroker() {
		return networkBroker;
	}

} public class ControllerDataEvent extends PLCEvent {
	private boolean request = true;
	
	private List<ControllerDataPayload> payload;
		
	public ControllerDataEvent(TilePLC source, UUID dest,  List<ControllerDataPayload> payload ) {
		super(source, dest);
		this.payload = payload;
	}

	public List<ControllerDataPayload> getPayload() {
		return payload;
	}

	
	

} public class PLCEvent {
	public enum TARGETTYPE {EXTENDER,CONTROLLER}
	private TilePLC source;
	private UUID dest;
	private Integer range = null;
	private TARGETTYPE target = null;
	private boolean server = true;
	private boolean client = false;
	
	
	// directed (uni or multi) uuid
	public PLCEvent(TilePLC source, UUID dest) {
		super();
		this.source = source;
		this.dest = dest;
	}
	// broadcast
	public PLCEvent(TilePLC source, TARGETTYPE target, Integer range) {
		super();
		this.source = source;
		this.target = target;
		this.range = range;
	}

	public TilePLC getSource() {
		return source;
	}

	public TARGETTYPE getTarget() {
		return target;
	}
	public boolean isServer() {
		return server;
	}
	public void setServer(boolean server) {
		this.server = server;
	}
	public boolean isClient() {
		return client;
	}
	public void setClient(boolean client) {
		this.client = client;
	}
	public Integer getRange() {
		return range;
	}
	public UUID getDest() {
		return dest;
	}
	
	
	
} public class SearchEvent extends PLCEvent {
	public SearchEvent(TilePLC source, TARGETTYPE target, Integer range) {
		super(source,target,range);
		setServer(false);
		setClient(true);
	}
} public class SearchResponseEvent extends PLCEvent {
	private int distance;
	private UUID uuid;
	private String name;
	private int inChannels;
	private int outChannels;
	
	public SearchResponseEvent(TilePLC source, UUID dest, int distance, UUID uuid, String name,
			int inChannels, int outChannels) {
		super(source, dest);
		setServer(false);
		setClient(true);
		this.distance = distance;
		this.uuid = uuid;
		this.name = name;
		this.inChannels = inChannels;
		this.outChannels = outChannels;
	}
	public int getDistance() {
		return distance;
	}
	public UUID getUuid() {
		return uuid;
	}
	public String getName() {
		return name;
	}
	public int getInChannels() {
		return inChannels;
	}
	public int getOutChannels() {
		return outChannels;
	}
	
	
	
} public class SignalEvent extends PLCEvent {
	private Signal signal;
	private int channel;
	private UUID dest;
	public SignalEvent(TilePLC source, UUID dest, Signal signal, int channel) {
		super(source, dest);
		this.signal = signal;
		this.channel = channel;
	}
	
	
	public Signal getSignal() {
		return signal;
	}
	public int getChannel() {
		return channel;
	}

	
	public static void fireDirected (TilePLC src, UUID dest, Signal signal, int chn) {
		SignalEvent event = new SignalEvent(src,dest, signal,chn);
		PLC.instance.getNetworkBroker().fireEvent(event);
	}
	
} public class ControllerDataPayload {
	public int channel;
	public Signal signal;
	
	public ControllerDataPayload(int channel, Signal signal) {
		this.channel = channel;
		this.signal = signal;
	}
} public class StaticData {
	public static int BlockController = 500;
	public static int BlockExtender = 501;
	
	public static int ItemsBase = 1900;
	public static int ItemsShiftRawWafer = 0;
	public static int ItemsShiftWafer = 1;
	public static int ItemsShiftRedWafer = 2;
	public static int ItemsShiftGreenWafer = 3;
	public static int ItemsShiftBlueWafer = 4;
	public static int ItemsShiftRedChip = 16;
	public static int ItemsShiftGreenChip = 17;
	public static int ItemsShiftBlueChip = 18;
	public static int ItemsShiftInterface = 32;
	public static int ItemsShiftInterfaceRedstone = 33;
	public static int ItemWaferRaw = 1901;
	
	public static int ExtenderBaseRange = 32;
	public static int SimulatorDelay = 5;
	
	public static int range = 32;
	
	
} public enum Signal {
	ON, OFF, PULSE, NEGATIVEPULSE;

	public Signal getHigherSignal(Signal signal) {
		if (signal == null)
			return OFF;
		if (this.equals(signal))
			return this;
		if (signal.equals(ON) || this.equals(ON))
			return ON;
		if (this.equals(OFF))
			return signal;
		if (signal.equals(OFF))
			return this;
		if (this.equals(PULSE) && signal.equals(NEGATIVEPULSE))
			return ON;
		if (this.equals(NEGATIVEPULSE) && signal.equals(PULSE))
			return ON;
		return OFF;
	}

	public Signal getLowerSignal(Signal signal) {
		if (signal == null)
			return OFF;
		if (this.equals(OFF) || signal.equals(OFF))
			return OFF;
		if (this.equals(ON) && signal.equals(ON))
			return Signal.ON;
		if (this.equals(ON))
			return signal;
		if (signal.equals(ON))
			return this;
		if (this.equals(PULSE) && signal.equals(NEGATIVEPULSE))
			return OFF;
		if (this.equals(NEGATIVEPULSE) && signal.equals(PULSE))
			return OFF;
		return OFF;
	}

	public Signal invert() {
		if (this.equals(ON))
			return OFF;
		if (this.equals(OFF))
			return ON;
		if (this.equals(PULSE))
			return NEGATIVEPULSE;
		if (this.equals(NEGATIVEPULSE))
			return PULSE;
		return OFF;
	}

	public static Signal fromOrdinal(int ordinal) {
		switch (ordinal) {
		case 0:
			return ON;
		case 1:
			return OFF;
		case 2:
			return PULSE;
		case 3:
			return NEGATIVEPULSE;
		}
		return null;
	}

} public class ExtenderChannel {
	public static enum TYPES {
		INPUT, OUTPUT
	}

	private TYPES type;
	private int number;
	private TileExtender extender;

	
	private String functionData = "";
	private String triggerData = "";
	private Object functionLocalData = "";
	private Object triggerLocalData = "";
	
	private long sheduledOn = -1;
	

	private ExtenderFunction function = ExtenderFunction.disabledFunction;
	private int side = 6;

	private Signal signal = Signal.OFF;
	private ExtenderTrigger trigger;

	
	
	
	public ExtenderChannel(TileExtender extender, TYPES type, int number) {
		this.number = number;
		this.type = type;
		this.extender = extender;
		this.function = ExtenderFunction.disabledFunction;
	}
	
	public void setSignalWithReset(Signal signal, int timer) {
		
	}



	public TYPES getType() {
		return type;
	}

	public void setType(TYPES type) {
		this.type = type;
	}

	public int getNumber() {
		return number;
	}

	public void setNumber(int number) {
		this.number = number;
	}

	public String getName() {
		if (type == TYPES.INPUT)
			return ("INPUT " + number);
		else
			return ("OUTPUT " + number);
	}

	public ExtenderFunction getFunction() {
		return function;
	}

	public void setFunction(int functionid) {
		if (function != null && function.getId() == functionid)
			return;
		if (function != null) {
			function.onDestroy(this);
		}
		function = ExtenderFunction.getFunction(this,functionid);
		if (function != null)
			function.onCreate(this);
		
	}

	public TileExtender getExtender() {
		return extender;
	}

	public Signal getSignal() {
		return signal;
	}

	public void setSignal(Signal signal) {
		this.signal = signal;
	}

	public void saveTo(DataOutputStream data) throws IOException {
		data.writeChar(number);
	
		data.writeChar(type.ordinal());
		data.writeChar(side);
		
		data.writeChar(function.getId());
		if (trigger != null)
			data.writeChar(trigger.getTriggerId());
		else data.writeChar(255);
		data.writeUTF(functionData);
		data.writeUTF(triggerData);
		
	}

	public int getSide() {
		return side;
	}

	public void setSide(int side) {
		boolean update = this.side != side;
		this.side = side;
		if (update)
			extender.sheduleNeighbourUpdate();
	}

	public static ExtenderChannelNetworkData readFrom(DataInputStream data)
			throws IOException {

		char number = data.readChar();
		char type = data.readChar();
		char side = data.readChar();
		char functiontype = data.readChar();
		char triggerid = data.readChar();
		String functionData = data.readUTF();
		String triggervalue = data.readUTF();
		return new ExtenderChannelNetworkData(number, type, side, functiontype, triggerid, functionData,triggervalue);

	}

	public void inject(ExtenderChannelNetworkData dt) {
		if (this.type.ordinal() != dt.getType()) {
			LogHelper.error("Trying to Update Channel of wrong type! "
					+ this.getType().ordinal() + " != " + dt.getType());
			return;
		}
		
		
		setSide(dt.getSide());
		
		setFunction(dt.getFunctionType());
		
		if (dt.getTriggerType() < 255 && function != null && function.getTriggers() != null) {
			for (ExtenderTrigger trg : function.getTriggers()) 
				if (trg.getTriggerId() == dt.getTriggerType()) 
					setTrigger(trg);
				
		} else trigger = null;
			
		functionData = dt.getFunctionData();
		triggerData = dt.getTriggerData();

	}

	public void onSignal(Signal signal) {
		
		if (type.equals(TYPES.INPUT)) {
			if (function != null)
				function.onSignal(this,signal);
			this.signal = signal;

		} else if (type.equals(TYPES.OUTPUT)) {
			if (function != null && !(function instanceof DisabledFunction))
				function.onSignal(this,signal);
		}
	}
	
	public void onRedstoneChanged(boolean isRedstonePowered, boolean b) {
		if (function instanceof RedstoneFunction)
			((RedstoneFunction) function).onRedstoneChanged(this,isRedstonePowered,b);
	}

	public void enableShedule(long inticks) {
		setSheduledOn(extender.getWorldObj().getTotalWorldTime()+inticks);
		extender.addChannelToShedule(this);
	}
	
	
	public ExtenderTrigger getTrigger() {
		return trigger;
	}
	public void setTrigger(ExtenderTrigger trigger) {
		this.trigger = trigger;
	}


	public String getFunctionData() {
		return functionData;
	}


	public void setFunctionData(String functionData) {
		this.functionData = functionData;
	}


	public String getTriggerData() {
		return triggerData;
	}


	public void setTriggerData(String triggerData) {
		this.triggerData = triggerData;
	}


	public Object getFunctionLocalData() {
		return functionLocalData;
	}


	public void setFunctionLocalData(Object functionLocalData) {
		this.functionLocalData = functionLocalData;
	}


	public Object getTriggerLocalData() {
		return triggerLocalData;
	}


	public void setTriggerLocalData(Object triggerLocalData) {
		this.triggerLocalData = triggerLocalData;
	}

	public long getSheduledOn() {
		return sheduledOn;
	}

	public void setSheduledOn(long sheduledOn) {
		this.sheduledOn = sheduledOn;
	}
	

} public static enum TYPES {
	INPUT, OUTPUT
} public class ExtenderChannelNetworkData {
	private char number;

	private char type;
	private char side;

	private char functionType;
	private char triggerType;
	private String functionData;
	private String triggerData;
	public ExtenderChannelNetworkData(char number, char type,
			 char side,
			char functionType, char triggerType, String functionData, String triggerData) {
		super();
		this.number = number;
	
		this.type = type;
	
		this.side = side;
	
		this.functionType = functionType;
		this.triggerType = triggerType;
		this.functionData = functionData;
		this.triggerData = triggerData;
	}
	
	public char getNumber() {
		return number;
	}
	public char getType() {
		return type;
	}

	public char getFunctionType() {
		return functionType;
	}
	

	public char getTriggerType() {
		return triggerType;
	}

	public char getSide() {
		return side;
	}

	public String getFunctionData() {
		return functionData;
	}

	public String getTriggerData() {
		return triggerData;
	}
	
	
} public class RedstoneFunction extends ExtenderFunction {
	private static ExtenderTrigger defaultTrigger = new ExtenderTrigger("direct signal", false, 0, null);
	private static List<ExtenderTrigger> myTriggers = new ArrayList<ExtenderTrigger>() {
		{
			add(defaultTrigger);
			add(new ExtenderTrigger("inverted input", false, 1, ExtenderChannel.TYPES.INPUT));
			add(new ExtenderTrigger("pulse on raising", false, 2, ExtenderChannel.TYPES.INPUT));
			add(new ExtenderTrigger("pulse on falling", false, 3, ExtenderChannel.TYPES.INPUT));
			add(new ExtenderTrigger("pulse on change", false, 4, ExtenderChannel.TYPES.INPUT));
			//add(new ExtenderTrigger("stable input", false, 5, ExtenderChannel.TYPES.INPUT));

		}
	};
	public static List<ExtenderChannel.TYPES> myIoTypes = new ArrayList<ExtenderChannel.TYPES>() {
		{
			add(ExtenderChannel.TYPES.INPUT);
			add(ExtenderChannel.TYPES.OUTPUT);
		}
	};

	public RedstoneFunction() {
		super(1, "RS",myIoTypes, myTriggers);
	}


	
	@Override
	public void onDestroy(ExtenderChannel channel) {
		channel.getExtender().sheduleNeighbourUpdate();
	}
	@Override
	public void onCreate(ExtenderChannel channel) {
		channel.setTrigger(defaultTrigger);
		channel.setFunctionLocalData(Signal.OFF);
		if (channel.getType().equals(ExtenderChannel.TYPES.OUTPUT))
			channel.getExtender().requestUpdateFromController();
		else {
			channel.getExtender().checkRedstonePower();
		}
		
	}



	
	public void onRedstoneChanged(ExtenderChannel channel, boolean isRedstonePowered, boolean statePulse) {
		//LogHelper.info("on change");
		if (channel.getType() == ExtenderChannel.TYPES.INPUT) {
			Signal tosend = null;
			boolean lastpowered = false;
			if (channel.getFunctionLocalData() instanceof Signal)
				lastpowered = ((Signal)channel.getFunctionLocalData()).equals(Signal.ON);
			
			
			switch (channel.getTrigger().getTriggerId()) {
				case 0: // direct
					if (isRedstonePowered)
						tosend = Signal.ON;
					else tosend = Signal.OFF;
					break;
				case 1: // invert
					if (isRedstonePowered)
						tosend = Signal.OFF;
					else tosend = Signal.ON;
					break;
						
				case 2: // flank positive
					if (!lastpowered && isRedstonePowered)
						tosend = Signal.PULSE;
					else tosend = Signal.OFF;
					break;
				case 3: // flank negative
					if (lastpowered && !isRedstonePowered)
						tosend = Signal.PULSE;
					else tosend = Signal.OFF;
					break;
				case 4:	// change
					//LogHelper.info(lastpowered+" "+isRedstonePowered);
					if (lastpowered != isRedstonePowered)
						tosend = Signal.PULSE;
					
					break;
				case 5:
					// todo
					break;

				default:
					LogHelper
							.warn("Redstone Function has undefined Trigger type "
									+ channel.getTrigger().getTriggerId());
					break;
				}
			if (isRedstonePowered)
				channel.setFunctionLocalData(Signal.ON);
			else channel.setFunctionLocalData(Signal.OFF);
			
			if (tosend != null) {
				channel.getExtender().sheduleRemoteUpdate();
				channel.setSignal(tosend);
				SignalEvent.fireDirected(channel.getExtender(), channel.getExtender().getConnectedController(), tosend, channel.getNumber());
			}
		}
	}
	
	@Override
	public void onSignal(ExtenderChannel channel, Signal signal) {
		if (channel.getType() == ExtenderChannel.TYPES.OUTPUT) {
			Signal lastSignal = Signal.OFF;
			if (channel.getFunctionLocalData() instanceof Signal)
				lastSignal = (Signal)channel.getFunctionLocalData();
			if (!lastSignal.equals(signal)) {
				channel.getExtender().sheduleNeighbourUpdate();
				channel.getExtender().sheduleRemoteUpdate();
				lastSignal = signal;
				
				if (signal.equals(Signal.PULSE)) {
					signal = Signal.ON;
					channel.enableShedule(2);
				}
				if (signal.equals(Signal.NEGATIVEPULSE)) {
					signal = Signal.OFF;
					channel.enableShedule(2);
				}
				
			}
			channel.setFunctionLocalData(lastSignal);
			channel.setSignal(signal);
			
		}
	}
	
	@Override
	public void onUpdate(ExtenderChannel channel,long worldTotalTime) {
		if (channel.getType() == ExtenderChannel.TYPES.OUTPUT)
			if (channel.getFunctionLocalData() instanceof Signal) {
				Signal lastSignal = (Signal)channel.getFunctionLocalData();
				if (lastSignal.equals(Signal.PULSE)) {
					channel.setFunctionLocalData(Signal.OFF);
					channel.setSignal(Signal.OFF);
					channel.getExtender().sheduleNeighbourUpdate();
					channel.getExtender().sheduleRemoteUpdate();
				} else if (lastSignal.equals(Signal.NEGATIVEPULSE)) {
					channel.setFunctionLocalData(Signal.ON);
					channel.setSignal(Signal.ON);
					channel.getExtender().sheduleNeighbourUpdate();
					channel.getExtender().sheduleRemoteUpdate();
				}
			}
		
	}
} public class LogHelper {

	private static Logger plcLoggerServer = null;
	private static Logger plcLoggerClient = null;

	public static void init() {
		plcLoggerClient = Logger.getLogger("plcClient");
		plcLoggerClient.setParent(FMLLog.getLogger());
		plcLoggerServer = Logger.getLogger("plcServer");
		plcLoggerServer.setParent(FMLLog.getLogger());

	}

	public static void log(Level logLevel, String message) {
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side == Side.CLIENT)
			plcLoggerClient.log(logLevel, message);
		else
			plcLoggerServer.log(logLevel, message);

	}

	public static void info(String message) {
		log(Level.INFO, message);
	}

	public static void warn(String message) {
		log(Level.WARNING, message);
	}

	public static void error(String message) {
		log(Level.SEVERE, message);
	}

} public class PacketExtenderLiteData extends PLCPacket {

	public int x, y, z;

	private List<Character> resC = null;
	private List<Boolean> res = null;
	private List<Boolean> resT = null;
	private int ins = 0;
	private int outs = 0;

	private TileExtender extender = null;

	public PacketExtenderLiteData() {
		super(PacketTypeHandler.EXTENDERLITEDATA, true);
	}

	public void setCoords(int x, int y, int z) {
		this.x = x;
		this.y = y;
		this.z = z;
	}

	public void setExtender(TileExtender extender) {
		this.extender = extender;
	}

	// int x,y,z
	//
	// int in, int out
	// boolean en, boolean on

	@Override
	public void writeData(DataOutputStream data) throws IOException {

		data.writeInt(x);
		data.writeInt(y);
		data.writeInt(z);

		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side.equals(Side.SERVER)) {
			data.writeChar(extender.getChannelsIn().size());
			data.writeChar(extender.getChannelsOut().size());

			List<Character> resC = new LinkedList<Character>();
			List<Boolean> res = new ArrayList<Boolean>();
			List<Boolean> resT = new ArrayList<Boolean>();

			for (ExtenderChannel chn : extender.getChannelsIn()) {
				if (chn.getFunction() != null
						&& !(chn.getFunction() instanceof DisabledFunction)) {
					resC.add((char) chn.getNumber());
					res.add(!chn.getSignal().equals(Signal.OFF));
					resT.add(chn.getType().equals(ExtenderChannel.TYPES.INPUT));
				}
			}
			for (ExtenderChannel chn : extender.getChannelsOut()) {
				if (chn.getFunction() != null
						&& !(chn.getFunction() instanceof DisabledFunction)) {
					resC.add((char) chn.getNumber());
					res.add(!chn.getSignal().equals(Signal.OFF));
					resT.add(chn.getType().equals(ExtenderChannel.TYPES.INPUT));
				}
			}

			data.writeChar((char) resC.size());
			int i = 0;
			for (Character c : resC) {
				data.writeChar(c);
				data.writeBoolean(res.get(i));
				data.writeBoolean(resT.get(i++));
			}

		} else {
			data.writeChar(0);
			data.writeChar(0);
		}
		data.writeBoolean(false);
	}

	public void readData(DataInputStream data) throws IOException {
		this.x = data.readInt();
		this.y = data.readInt();
		this.z = data.readInt();
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side.equals(Side.CLIENT)) {
			ins = data.readChar();
			outs = data.readChar();

			int i = data.readChar();
			resC = new ArrayList<Character>();
			res = new ArrayList<Boolean>();
			resT = new ArrayList<Boolean>();

			for (int j = 0; j < i; j++) {
				resC.add(data.readChar());
				res.add(data.readBoolean());
				boolean t = data.readBoolean();
				resT.add(t);

			}
		}
	}

	public void execute(INetworkManager manager, Player player) {
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		World worldObj = null;
		if (side == Side.CLIENT) {
			if (player instanceof EntityClientPlayerMP)
				worldObj = ((EntityClientPlayerMP) player).worldObj;
		} else if (side == Side.SERVER) {

			if (player instanceof EntityPlayerMP)
				worldObj = ((EntityPlayerMP) player).worldObj;
		}

		if (worldObj != null) {
			if (side == Side.CLIENT) {
				TileEntity tile = worldObj.getBlockTileEntity(x, y, z);
				if (tile != null && tile instanceof TileExtender) {
					TileExtender extender = (TileExtender) tile;
					char[] in = new char[ins];
					char[] out = new char[outs];

					for (int i = 0; i < in.length; i++)
						in[i] = 0;
					for (int i = 0; i < out.length; i++)
						out[i] = 0;
					int i = 0;
					for (Character c : resC) {

						if (resT.get(i) && in.length > c) {
							if (res.get(i))
								in[c] = 2;
							else
								in[c] = 1;
						} else if (!resT.get(i) && out.length > c)
							if (res.get(i))
								out[c] = 2;
							else
								out[c] = 1;
						i++;
					}
					extender.updateStatus(in, out);
					//LogHelper.info("ExtenderLiteUpdate executed");
				}

			} else if (side == Side.SERVER) {
				TileEntity tile = worldObj.getBlockTileEntity(x, y, z);
				if (tile != null && tile instanceof TileExtender) {
					TileExtender extender = (TileExtender) tile;
					extender.sheduleRemoteUpdate();
				}
			}
		}

	}

	public static void sendUpdateToClients(TileExtender extender) {
		PacketExtenderLiteData pkg = new PacketExtenderLiteData();
		pkg.setExtender(extender);
		pkg.setCoords(extender.xCoord, extender.yCoord, extender.zCoord);
		Packet packet = PacketTypeHandler.populatePacket(pkg);
		Side side = FMLCommonHandler.instance().getEffectiveSide();

		// only server can do!
		if (side == Side.SERVER) {
			// Server
			PacketDispatcher.sendPacketToAllAround(extender.xCoord,
					extender.yCoord, extender.zCoord, 32, extender
							.getWorldObj().getWorldInfo().getDimension(),
					packet);
		}
	}

	public static void requestUpdateFromServer(TileExtender extender) {
		PacketExtenderLiteData pkg = new PacketExtenderLiteData();
		pkg.setExtender(extender);
		pkg.setCoords(extender.xCoord, extender.yCoord, extender.zCoord);
		Packet packet = PacketTypeHandler.populatePacket(pkg);
		Side side = FMLCommonHandler.instance().getEffectiveSide();

		// only client can do!
		if (side == Side.CLIENT) {
			// Server
			PacketDispatcher.sendPacketToServer(packet);
		}
	}

}
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraftforge.common.ForgeDirection;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.relauncher.Side;
import de.squig.plc.PLC;
import de.squig.plc.event.ControllerDataEvent;
import de.squig.plc.event.PLCEvent;
import de.squig.plc.event.SearchEvent;
import de.squig.plc.event.SearchResponseEvent;
import de.squig.plc.event.SignalEvent;
import de.squig.plc.event.payloads.ControllerDataPayload;
import de.squig.plc.lib.StaticData;
import de.squig.plc.logic.Signal;
import de.squig.plc.logic.extender.ExtenderChannel;
import de.squig.plc.logic.extender.ExtenderChannel.TYPES;
import de.squig.plc.logic.extender.ExtenderChannelNetworkData;
import de.squig.plc.logic.extender.function.RedstoneFunction;
import de.squig.plc.logic.helper.LogHelper;
import de.squig.plc.network.PacketExtenderLiteData;
/**
 * PacketTypeHandler
 * 
 * Handler that routes packets to the appropriate destinations depending on what kind of packet they are
 * 
 * @author pahimar
 * @license Lesser GNU Public License v3 (http://www.gnu.org/licenses/lgpl.html)
 * 
 */

package de.squig.plc.tile;



public class TileExtender extends TilePLC implements IInventory {
	public enum TYPE {
		BASIC
	};

	private ItemStack[] extenderCards = new ItemStack[2];

	private TYPE type = null;

	private int range = StaticData.ExtenderBaseRange;

	// SOFT link
	private UUID connectedController = null;
	private String connectedControllerName = null;

	private List<SearchResponseEvent> controllersInRange = null;

	private List<ExtenderChannel> channelsIn = null;
	private List<ExtenderChannel> channelsOut = null;

	private List<ExtenderChannel> sheduledChannelUpdates;

	private List<ExtenderChannel> tickListener = new ArrayList<ExtenderChannel>();

	private boolean isRedstonePowered;

	private boolean remoteUpdate = false;
	private boolean neighbourUpdate = false;

	private boolean channelsConfigChangedForGui = false;
	
	// load shit
	private boolean needsLoad = false;
	private List<ExtenderChannelNetworkData> loadChannelDataIn = null;
	private List<ExtenderChannelNetworkData> loadChannelDataOut = null;

	// status
	private char[] instatus = new char[0];
	private char[] outstatus = new char[0];

	public TileExtender() {
		super(PLCEvent.TARGETTYPE.EXTENDER);
		channelsIn = new ArrayList<ExtenderChannel>();
		channelsOut = new ArrayList<ExtenderChannel>();
		sheduledChannelUpdates = new LinkedList<ExtenderChannel>();

		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side.equals(Side.CLIENT))
			needsLoad = true;

	}

	public void updateStatus(char[] ins, char[] outs) {
		instatus = ins;
		outstatus = outs;
	}

	public char[] getInputs() {
		return instatus;
	}

	public char[] getOutputs() {
		return outstatus;
	}

	/**
	 * bc3 support integration
	 * 
	 * @return
	 */
	public static TileExtender createInstance() {
		try {
			Class bc3Extender = PLC.class.getClassLoader().loadClass(