public abstract class Circuit implements Serializable, ITickNotified {
	protected List<CircuitObject> objects;

	protected CircuitMap map;

	protected TileController controller;
	protected CircuitSimulator simulator;

	protected List<CircuitElement> simulationList = null;
	protected List<Object> commitList = null;
	protected List<CircuitObject> watchList = null;

	protected boolean evaluated = false;

	protected boolean gotUpdatedForSimulator = true;
	protected boolean needsSimulation = true;
	protected long needsSimInTick = -1;
	protected long simulationTime = -1;

	public Circuit(TileController controller, int width, int height) {
		map = new CircuitMap(width, height);
		objects = new ArrayList<CircuitObject>();
		this.controller = controller;

		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side == Side.SERVER) {
			// TickHandler.getInstance().addListener(this);
			simulator = new CircuitSimulator(this);
		}
	}

	public void addCircuitObject(CircuitObject object) {
		objects.add(object);
	}

	public void onDestroy() {
		TickHandler.getInstance().removeListener(this);
	}

	public CircuitObject getByType(short typeid, int number) {
		Class type = CircuitObject.getClassForType(typeid);
		for (CircuitObject obj : objects)
			if (obj.getClass().equals(type) && number == obj.getLinkNumber())
				return obj;
		return null;
	}

	public CircuitObject getByType(Class typ, int number) {
		for (CircuitObject obj : objects)
			if (obj.getClass().equals(typ))
				if (obj.getLinkNumber() == number)
					return obj;
		return null;
	}

	public List<CircuitObject> getByType(Class type) {
		List result = new ArrayList();
		for (CircuitObject obj : objects)
			if (obj.getClass().equals(type))
				result.add(obj);
		return result;
	}

	public CircuitMap getMap() {
		return map;
	}

	public List<CircuitObject> getObjects() {
		return objects;
	}

	public void saveElementsTo(DataOutputStream data, boolean all)
			throws IOException {
		List<CircuitElement> elements;
		if (all)
			elements = map.getAllElement();
		else
			elements = map.getChangedElements(true);
		data.writeShort(elements.size());
		for (CircuitElement ele : elements)
			ele.saveTo(data);

	}

	public void saveObjectsTo(DataOutputStream data, boolean all)
			throws IOException {
		List<CircuitObject> tosave = new LinkedList<CircuitObject>();
		for (CircuitObject obj : objects) {
			if (all || obj.isChanged()) {
				tosave.add(obj);
			}
		}
		data.writeShort(tosave.size());
		for (CircuitObject obj : tosave) {
			obj.saveTo(data, all);
		}

	}

	public void saveStateTo(DataOutputStream data) throws IOException {

	}

	public void injectState(CircuitStateNetworkData netState) {

	}

	public void injectElements(List<CircuitElementNetworkData> elements,
			boolean all) {

		if (all) {
			getMap().empty();
		}
		List<CircuitElement> toFire = new ArrayList<CircuitElement>();
		for (CircuitElementNetworkData netElement : elements) {
			CircuitElement element;
			if ((element = getMap().getElementAt(netElement.mapX,
					netElement.mapY)) != null)
				getMap().removeElement(element);

			element = CircuitElement.createFromPacket(this, netElement);
			getMap().addElement(element, -1, -1, false);
			toFire.add(element);
		}

		for (CircuitElement ele : toFire) {
			ele.afterLoad();
			if (FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) {
				ele.setChanged(true);
			}
		}

		if (FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER)
			for (CircuitElement element : getMap().getAllElement()) {
				if (element instanceof Line) {
					Line line = (Line) element;
				}
			}
		gotUpdatedForSimulator = true;
		evaluated = false;

	}

	public void injectObjects(List<CircuitObjectNetworkData> objects,
			boolean all) {
		for (CircuitObjectNetworkData cond : objects) {
			CircuitObject obj = getByType(cond.getTypeID(), cond.getLinkName());
			obj.setFlags(cond.getFlags());
			if (cond.getData() != null) {
				if (!cond.getData().isTransmitNonStatic()
						|| cond.getData().isTransmitStatic()) {
					CircuitObjectData dd = cond.getData();
					CircuitObjectData ds = obj.getObjData();
					int i = 0;
					for (Object ob : ds.getData()) {
						boolean isStatic = ds.getStatics().get(i);
						if ((isStatic && !dd.isTransmitStatic())
								|| (!isStatic && !dd.isTransmitNonStatic())) {
						} else {
							ds.set(i, dd.get(i));
						}
						i++;
					}
				} else
					obj.setObjData(cond.getData());
			} else obj.setObjData(null);
			obj.setChanged(true);
		}
	}

	public void savePoweredTo(DataOutputStream data) throws IOException {
		String injmap = "";
		data.writeInt(map.width * map.height);
		for (int y = 0; y < map.height; y++) {
			for (int x = 0; x < map.width; x++) {
				CircuitElement ele = map.getElementAt(x, y);
				if (ele != null && !(ele instanceof Deleted)) {
					data.writeBoolean(ele.isPowered());
					injmap += "" + ele.isPowered();
				} else {
					data.writeBoolean(false);
				}
			}
		}
		// LogHelper.info(injmap);
	}

	public void injectPowered(PoweredMapNetworkData netPowered) {
		int i = 0;
		String injmap = "";
		for (int y = 0; y < map.height; y++)
			for (int x = 0; x < map.width; x++) {
				CircuitElement ele = map.getElementAt(x, y);
				if (ele != null && !(ele instanceof Deleted)) {
					ele.setPowered(netPowered.getMap().get(i));

					injmap += "" + ele.isPowered();
				}
				i += 1;
			}

	}

	public static List<CircuitElementNetworkData> loadElementsFrom(
			DataInputStream data, boolean all) throws IOException {
		List<CircuitElementNetworkData> eles = new ArrayList<CircuitElementNetworkData>();
		int num = data.readShort();
		for (int i = 0; i < num; i++) {
			eles.add(CircuitElement.read(data));
		}
		return eles;
	}

	public static List<CircuitObjectNetworkData> loadObjectsFrom(
			DataInputStream data, boolean all) throws IOException {
		List<CircuitObjectNetworkData> objs = new ArrayList<CircuitObjectNetworkData>();
		int num = data.readShort();
		for (int i = 0; i < num; i++) {
			objs.add(CircuitObject.readFrom(data));
		}
		return objs;
	}

	public static CircuitStateNetworkData loadStateFrom(DataInputStream data)
			throws IOException {
		return null;
	}

	public static PoweredMapNetworkData loadPoweredFrom(DataInputStream data)
			throws IOException {
		int size = data.readInt();
		List<Boolean> map = new ArrayList<Boolean>();
		for (int i = 0; i < size; i++) {
			map.add(data.readBoolean());
		}
		return new PoweredMapNetworkData(size, map);

	}

	public TileController getController() {
		return controller;
	}

	public void onTick(long tick) {
		if (controller != null)
			// if (controller.getState() == TileController.STATE_RUN)
			simulator.onTick(tick);
	}

	public List<CircuitElement> getSimulationList() {
		return simulationList;
	}

	public void setSimulationList(List<CircuitElement> simulationList) {
		this.simulationList = simulationList;
	}

	public boolean isEvaluated() {
		return evaluated;
	}

	public void setEvaluated(boolean evaluated) {
		this.evaluated = evaluated;
	}

	public List<Object> getCommitList() {
		return commitList;
	}

	public void setCommitList(List<Object> commitList) {
		this.commitList = commitList;
	}

	public CircuitSimulator getSimulator() {
		return simulator;
	}

	public boolean isNeedsSimulation() {
		return needsSimulation;
	}

	public void setNeedsSimulation(boolean needsSimulation) {
		this.needsSimulation = needsSimulation;
	}

	public long getNeedsSimInTick() {
		return needsSimInTick;
	}

	public void setNeedsSimInTick(long needsSimInTicks) {
		this.needsSimInTick = needsSimInTicks;
	}

	public long getSimulationTime() {
		return simulationTime;
	}

	public void setSimulationTime(long simulationTime) {
		this.simulationTime = simulationTime;
	}

	public List<CircuitObject> getWatchList() {
		return watchList;
	}

	public void setWatchList(List<CircuitObject> watchList) {
		this.watchList = watchList;
	}

} public interface ISignalListener {
	public void onSignal(Signal signal);
} 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 DisabledFunction extends ExtenderFunction {
	public static List<ExtenderChannel.TYPES> myIoTypes = new ArrayList<ExtenderChannel.TYPES>() {
		{
			add(ExtenderChannel.TYPES.INPUT);
			add(ExtenderChannel.TYPES.OUTPUT);
		}
	};
	public DisabledFunction() {
		super(0, "OFF",myIoTypes,null);
	}
	
} public abstract class ExtenderFunction {
	
	private String displayName;
	private List<ExtenderChannel.TYPES>  ioTypes;
	private List<ExtenderTrigger> triggers = null;

	private int id;
	
	
	private static List<ExtenderFunction> functions = null;
	
	
	protected ExtenderFunction(int id, String displayName, List<ExtenderChannel.TYPES> ioTypes ,List<ExtenderTrigger> triggers) {
		
		this.displayName = displayName;
		this.triggers = triggers;
		this.ioTypes = ioTypes;
		this.id = id;
	}
	
	public static ExtenderFunction disabledFunction = new DisabledFunction();
	public static List<ExtenderFunction> getAviavableFunction(ExtenderChannel channel) {
		 if (functions == null) {
			 functions = new ArrayList<ExtenderFunction>();
			 functions.add (disabledFunction);
			 functions.add (new RedstoneFunction());
			 //functions.add (new InventoryFunction());
			 functions.add (new BC3Function());
			 
		 }
		 List<ExtenderFunction> res = new ArrayList<ExtenderFunction>();
		 for (ExtenderFunction fncts : functions) {
			 if (fncts.ioTypes.contains(channel.getType()))
				 res.add(fncts);
		 }
		 return res;
	}
	
	public static ExtenderFunction getFunction(ExtenderChannel chn, int functionid) {
		if (chn == null && functionid == 0)
			return disabledFunction;
		for (ExtenderFunction fnct : getAviavableFunction(chn)) 
			if (fnct.getId() == functionid)
				return fnct;
		
		return null;
		
	}

	public void onDestroy(ExtenderChannel channel) {
	}
	public void onCreate(ExtenderChannel channel) {
	}
	public void onUpdate(ExtenderChannel channel,long worldTotalTime) {
	}
	
	
	
	

	public String getDisplayName() {
		return displayName;
	}
	
	public void onTriggerChanged() {
		
	}
	
	

	public int getId() {
		return id;
	}




	public List<ExtenderTrigger> getTriggers() {
		return triggers;
	}

	public String getTriggerValue() {
		// TODO Auto-generated method stub
		return "no value";
	}

	public void onSignal(ExtenderChannel channel, Signal signal) {
		
	}
	
} public class ExtenderTrigger {
	private String name;
	private boolean hasValue;
	private int triggerId;
	ExtenderChannel.TYPES channelType;
	public ExtenderTrigger(String name, boolean hasValue, int triggerid, ExtenderChannel.TYPES channelType) {
		this.name = name;
		this.hasValue = hasValue;
		this.triggerId = triggerid;
		this.channelType = channelType;
	}
	public String getName() {
		return name;
	}
	public boolean hasValue() {
		return hasValue;
	}
	public int getTriggerId() {
		return triggerId;
	}
	public ExtenderChannel.TYPES getChannelType() {
		return channelType;
	}
	
	
} 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 abstract class CircuitObject {
	private static Class objects[] = new Class[32];
	private static List<Class> objectsData[] = new List[32];
	private static List<Boolean> objectsStatics[] = new List[32];
	
	private List<GuiFunction> guiFunctions = null;
	
	
	private List<Class> dataTypes;
	
	protected Circuit circuit;
	
	protected short linkNumber = -1;
	protected String name = null;
	protected short flags = 0;
	
	protected boolean changed = true;
	
	
	private List<CircuitObjectInputPin> inputs;
	private List<CircuitObjectOutputPin> outputs;
	
	protected CircuitObjectData objData = null;
	
	
	
	
	public CircuitObject(Circuit circuit, List<Class> dataTypes, List<Boolean> statics) {
		this.circuit = circuit;
		this.dataTypes = dataTypes;
		if (dataTypes != null)
			objData = new CircuitObjectData(dataTypes, statics);
		inputs = new ArrayList<CircuitObjectInputPin>();
		outputs = new ArrayList<CircuitObjectOutputPin>();
	}
	
	
	protected void setGuiFunctions(List<GuiFunction> guiFunctions) {
		this.guiFunctions = guiFunctions;
	}
	
	public Circuit getCircuit() {
		return circuit;
	}


	public long getNextActivation() {
		return -1;
	}
	
	public void preSimulation() {
	}
	public void postSimulation() {
	}

	public CircuitObjectInputPin getInputPin(ElementFunction funct) {
		return null;
	}
	public CircuitObjectOutputPin getOutputPin(ElementFunction funct) {
		return null;
	}
	
	
	public void addInputPin(CircuitObjectInputPin pin) {
		inputs.add(pin);
	}
	public void addOutputPin(CircuitObjectOutputPin pin) {
		outputs.add(pin);
	}

	public CircuitObjectInputPin getInputPin(int number) {
		return inputs.get(number);
	}
	public CircuitObjectOutputPin getOutputPin(int number) {
		return outputs.get(number);
	}

	public List<CircuitObjectInputPin> getInputs() {
		return inputs;
	}

	public List<CircuitObjectOutputPin> getOutputs() {
		return outputs;
	}

	public String getName() {
		return name;
	}
	
	
	public String saveState() {
		return "";
	}
	public void loadState(String state) {
		
	}
	
	public void setFlags(short flags) {
		this.flags = flags;
	}
	public short getFlags() {
		return flags;
	}
	
	
	public static CircuitObjectNetworkData readFrom(DataInputStream data) throws IOException {
		short type = data.readShort();
		short linkNumberc = data.readShort();
		short flags = data.readShort();
		CircuitObjectData dt = null;
		
		if (objectsData[type] != null)
			dt = CircuitObjectData.readFromStream(objectsData[type],objectsStatics[type], data);
		CircuitObjectNetworkData obj = new CircuitObjectNetworkData(type,linkNumberc, flags, dt);
		return obj;
	}
	
	public void saveTo(DataOutputStream data, boolean all) throws IOException {
		data.writeShort(getCircuitObjectId(this.getClass()));
		data.writeShort(getLinkNumber());
		data.writeShort(flags);
		if (objData != null) {
			Side side = FMLCommonHandler.instance().getEffectiveSide();
			if (side.equals(Side.CLIENT))
				objData.setTransmitNonStatic(all);
			objData.saveToStream(data);
		}
	}


	public short getLinkNumber() {
		return linkNumber;
	}
	public void setLinkNumber(short nmbr) {
		linkNumber = nmbr;
	}

	
	public void commit() {
		
	}

	
	public boolean isChanged() {
		return changed;
	}


	public void setChanged(boolean changed) {
		this.changed = changed;
	}


	public String getData() {
		return "";
	}
	public void setData(String data) {
		
	}
	
	
	public static void addCircuitObjectType(int id, Class type, List<Class> dataTypes, List<Boolean> dataStatics) {
		objects[id] = type;
		objectsData[id] = dataTypes;
		objectsStatics[id] = dataStatics;
	}
	public static short getCircuitObjectId(Class type) {
		for (short i = 0; i < objects.length; i++)
			if (type.equals(objects[i]))
				return i;
		return -1;
	}
	public static Class getClassForType(short type) {
		return objects[type];
	}


	public List<GuiFunction> getGuiFunctions() {
		return guiFunctions;
	}


	public CircuitObjectData getObjData() {
		return objData;
	}


	public void setObjData(CircuitObjectData objData) {
		this.objData = objData;
	}
	
	
} 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(
					"de.squig.plc.bc3.compat.TileExtenderBC3");
			Constructor constructor = bc3Extender.getConstructor(null);
			TileExtender res = (TileExtender) constructor.newInstance(null);
			return res;
		} catch (Exception ex) {
			return new TileExtender();
		}
	}

	public boolean isSidePowered(ForgeDirection side) {
		return getSideSignal(side).equals(Signal.ON);
	}

	public Signal getSideSignal(ForgeDirection side) {
		Signal ret = Signal.OFF;
		for (int i = 0; i < channelsOut.size(); i++) {
			ExtenderChannel chn = channelsOut.get(i);
			if (chn.getFunction() instanceof RedstoneFunction
					&& (chn.getSide() == 6 || chn.getSide() == side.ordinal()))
				ret = ret.getHigherSignal(chn.getSignal());

		}
		return ret;
	}

	public void sheduleNeighbourUpdate() {
		neighbourUpdate = true;
	}

	public void sheduleRemoteUpdate() {
		remoteUpdate = true;
	}

	@Override
	public void onDestroy() {
		if (connectedController != null)
			PLC.instance.getNetworkBroker().removeMulticastListener(
					connectedController, this);
		super.onDestroy();
	}

	@Override
	public void onChunkUnload() {
		if (connectedController != null)
			PLC.instance.getNetworkBroker().removeMulticastListener(
					connectedController, this);
		super.onChunkUnload();
	}

	@Override
	protected void initialize() {
		super.initialize();

	}

	@Override
	public void validate() {
		super.validate();
	};

	@Override
	public boolean canUpdate() {
		return true;
	};

	public void addChannelToShedule(ExtenderChannel chn) {
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side.equals(Side.CLIENT))
			return;
		if (!sheduledChannelUpdates.contains(chn))
			sheduledChannelUpdates.add(chn);

	}

	@Override
	public void updateEntity() {
		super.updateEntity();
		if (needsLoad) {
			loadData();
		}
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side.equals(Side.CLIENT))
			return;
		long now = worldObj.getTotalWorldTime();
		if (sheduledChannelUpdates.size() > 0) {
			List<ExtenderChannel> tr = new LinkedList<ExtenderChannel>();
			for (ExtenderChannel chn : sheduledChannelUpdates) {
				if (now >= chn.getSheduledOn() && chn.getFunction() != null) {
					chn.getFunction().onUpdate(chn, now);
					chn.setSheduledOn(-1);
					tr.add(chn);
				}
			}
			sheduledChannelUpdates.removeAll(tr);
		}
		if (remoteUpdate) {
			PacketExtenderLiteData.sendUpdateToClients(this);
			remoteUpdate = false;
		}
		if (neighbourUpdate) {
			worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord,
					StaticData.BlockExtender);
			neighbourUpdate = false;
		}
	};

	private void loadData() {
		needsLoad = false;
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side.equals(Side.CLIENT)) {
			PacketExtenderLiteData.requestUpdateFromServer(this);
			return;
		}

		for (ExtenderChannelNetworkData dt : loadChannelDataIn) {
			injectChannel(ExtenderChannel.TYPES.INPUT, dt);
		}
		for (ExtenderChannelNetworkData dt : loadChannelDataOut) {
			injectChannel(ExtenderChannel.TYPES.OUTPUT, dt);
		}

		if (connectedController != null) {
			PLC.instance.getNetworkBroker().addMulticastListener(
					connectedController, this);
			requestUpdateFromController();
		}

		loadChannelDataIn = null;
		loadChannelDataOut = null;

	}

	public void readFromNBT(NBTTagCompound nbtTagCompound) {
		super.readFromNBT(nbtTagCompound);
		needsLoad = true;

		if (nbtTagCompound.hasKey("cuuid")) {
			connectedController = UUID.fromString(nbtTagCompound
					.getString("cuuid"));
			connectedControllerName = nbtTagCompound.getString("cname");
		}

		NBTTagList tagList = nbtTagCompound.getTagList("Items");
		this.extenderCards = new ItemStack[this.getSizeInventory()];
		for (int i = 0; i < tagList.tagCount(); ++i) {
			NBTTagCompound tagCompound = (NBTTagCompound) tagList.tagAt(i);
			byte slot = tagCompound.getByte("Slot");
			if (slot >= 0 && slot < this.extenderCards.length) {
				this.extenderCards[slot] = ItemStack
						.loadItemStackFromNBT(tagCompound);
			}
		}

		loadChannelDataIn = new ArrayList<ExtenderChannelNetworkData>();
		for (int i = 0; i < 64; i++) {
			if (nbtTagCompound.hasKey("I-" + i)) {
				try {
					InputStream is = new ByteArrayInputStream(
							nbtTagCompound.getByteArray("I-" + i));
					DataInputStream dis = new DataInputStream(is);
					loadChannelDataIn.add(ExtenderChannel.readFrom(dis));
				} catch (IOException ex) {
					LogHelper.error("exception durring reading data!");
				}
			}
		}
		loadChannelDataOut = new ArrayList<ExtenderChannelNetworkData>();
		for (int i = 0; i < 32; i++) {
			if (nbtTagCompound.hasKey("O-" + i)) {
				try {
					InputStream is = new ByteArrayInputStream(
							nbtTagCompound.getByteArray("O-" + i));
					DataInputStream dis = new DataInputStream(is);
					loadChannelDataOut.add(ExtenderChannel.readFrom(dis));
				} catch (IOException ex) {
					LogHelper.error("exception durring reading data!");
				}
			}
		}

	}

	public void writeToNBT(NBTTagCompound nbtTagCompound) {
		super.writeToNBT(nbtTagCompound);

		if (connectedController != null) {
			nbtTagCompound.setString("cuuid", connectedController.toString());
			nbtTagCompound.setString("cname", connectedControllerName);
		} else {
			nbtTagCompound.removeTag("cuuid");
			nbtTagCompound.removeTag("cname");
		}

		NBTTagList tagList = new NBTTagList();
		for (int currentIndex = 0; currentIndex < this.extenderCards.length; ++currentIndex) {
			if (this.extenderCards[currentIndex] != null) {
				NBTTagCompound tagCompound = new NBTTagCompound();
				tagCompound.setByte("Slot", (byte) currentIndex);
				this.extenderCards[currentIndex].writeToNBT(tagCompound);
				tagList.appendTag(tagCompound);
			}
		}
		nbtTagCompound.setTag("Items", tagList);

		for (int i = 0; i < 64; i++) {
			if (i < channelsIn.size()) {
				try {
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					DataOutputStream w = new DataOutputStream(baos);

					ExtenderChannel chan = channelsIn.get(i);
					chan.saveTo(w);
					w.flush();

					byte[] result = baos.toByteArray();
					nbtTagCompound.setByteArray("I-" + i, result);

				} catch (IOException ex) {
					LogHelper.error("exception durring writing data!");
				}
			} else {
				if (nbtTagCompound.hasKey("I-" + i))
					nbtTagCompound.removeTag("I-" + i);
			}
		}
		for (int i = 0; i < 32; i++) {
			if (i < channelsOut.size()) {
				try {
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					DataOutputStream w = new DataOutputStream(baos);

					ExtenderChannel chan = channelsOut.get(i);
					chan.saveTo(w);
					w.flush();

					byte[] result = baos.toByteArray();
					nbtTagCompound.setByteArray("O-" + i, result);

				} catch (IOException ex) {
					LogHelper.error("exception durring writing data!");
				}
			} else {
				if (nbtTagCompound.hasKey("O-" + i))
					nbtTagCompound.removeTag("O-" + i);
			}
		}

	}

	public void sendBroadcastSearch() {
		controllersInRange = new ArrayList<SearchResponseEvent>();
		SearchEvent event = new SearchEvent(this,
				PLCEvent.TARGETTYPE.CONTROLLER, null);
		PLC.instance.getNetworkBroker().fireEvent(event);
	}

	public List<SearchResponseEvent> getControllerInRange() {
		return controllersInRange;
	}

	private void createChannels(SearchResponseEvent linkto) {
		channelsIn.clear();
		channelsOut.clear();
		tickListener.clear();
		char[] ins = new char[linkto.getInChannels()];
		char[] outs = new char[linkto.getOutChannels()];

		for (int i = 0; i < linkto.getInChannels(); i++) {
			ExtenderChannel channel = new ExtenderChannel(this,
					ExtenderChannel.TYPES.INPUT, i);

			channelsIn.add(channel);
			ins[i] = 0;

		}
		for (int i = 0; i < linkto.getOutChannels(); i++) {
			ExtenderChannel channel = new ExtenderChannel(this,
					ExtenderChannel.TYPES.OUTPUT, i);

			channelsOut.add(channel);
			outs[i] = 0;
		}
		updateStatus(ins, outs);
	}

	public void checkRedstonePower() {
		boolean isRedstonePowered = worldObj.isBlockIndirectlyGettingPowered(
				xCoord, yCoord, zCoord);

		for (ExtenderChannel channel : channelsIn) {
			if (channel.getFunction() instanceof RedstoneFunction)
				channel.onRedstoneChanged(isRedstonePowered, false);
		}

	}

	public void injectChannel(TYPES input, ExtenderChannelNetworkData dt) {
		ExtenderChannel chnn = null;
		List<ExtenderChannel> chns = channelsIn;
		if (input.equals(ExtenderChannel.TYPES.OUTPUT))
			chns = channelsOut;

		for (ExtenderChannel chn : chns)
			if (chn.getType().ordinal() == dt.getType())
				if (chn.getNumber() == dt.getNumber()) {
					chnn = chn;
					break;
				}
		if (chnn == null) {
			chnn = new ExtenderChannel(this,
					ExtenderChannel.TYPES.values()[dt.getType()],
					dt.getNumber());
			chns.add(chnn);
		}
		chnn.inject(dt);
	}

	public void onEvent(PLCEvent event) {
		if (event instanceof ControllerDataEvent) {
			ControllerDataEvent eventd = (ControllerDataEvent) event;
			for (ControllerDataPayload load : eventd.getPayload()) {
				if (load.channel < channelsOut.size()) {
					ExtenderChannel chn = channelsOut.get(load.channel);
					chn.onSignal(load.signal);
				}
			}

		} else if (event instanceof SignalEvent) {
			SignalEvent events = (SignalEvent) event;
			int chn = events.getChannel();

			ExtenderChannel channel;
			if (chn < channelsOut.size()
					&& (channel = channelsOut.get(chn)) != null)
				channel.onSignal(events.getSignal());
			else
				LogHelper.warn("cound not route event to channel "
						+ events.getChannel());
		} else if (event instanceof SearchResponseEvent) {
			if (controllersInRange == null)
				controllersInRange = new ArrayList<SearchResponseEvent>();
			controllersInRange.add((SearchResponseEvent) event);
			return;
		}

	}

	public void link(SearchResponseEvent linkto) {
		connectedController = linkto.getUuid();
		connectedControllerName = linkto.getName();
		createChannels(linkto);

	}

	public void linkFromPackage(UUID controller, String controllerName) {
		if (connectedController != null)
			PLC.instance.getNetworkBroker().removeMulticastListener(
					connectedController, this);
		boolean requestUpdate = (this.connectedController == null || !this.connectedController
				.equals(controller));
		connectedController = controller;
		connectedControllerName = controllerName;
		if (connectedController != null)
			PLC.instance.getNetworkBroker().addMulticastListener(
					connectedController, this);

		if (requestUpdate)
			requestUpdateFromController();

	}

	public void unlink() {
		if (connectedController != null)
			PLC.instance.getNetworkBroker().removeMulticastListener(
					connectedController, this);
		connectedController = null;
		connectedControllerName = null;
		channelsIn.clear();
		channelsOut.clear();
		tickListener.clear();
		sheduleNeighbourUpdate();
		sheduleRemoteUpdate();
	}

	public void requestUpdateFromController() {
		PLC.instance.getNetworkBroker().fireEvent(
				new ControllerDataEvent(this, connectedController, null));
	}

	public UUID getConnectedController() {
		return connectedController;
	}

	public String getConnectedControllerName() {
		return connectedControllerName;
	}

	public List<ExtenderChannel> getChannelsIn() {
		return channelsIn;
	}

	public List<ExtenderChannel> getChannelsOut() {
		return channelsOut;
	}

	/**
	 * Returns the number of slots in the inventory.
	 */
	public int getSizeInventory() {
		return this.extenderCards.length;
	}

	@Override
	public void setInventorySlotContents(int slot, ItemStack stack) {
		extenderCards[slot] = stack;
		if (stack != null && stack.stackSize > getInventoryStackLimit()) {
			stack.stackSize = getInventoryStackLimit();
		}
		checkInterfaces();
		onSlotChange();
	}

	@Override
	public ItemStack getStackInSlot(int slot) {
		return extenderCards[slot];
	}

	@Override
	public ItemStack getStackInSlotOnClosing(int slot) {
		ItemStack stack = getStackInSlot(slot);
		if (stack != null) {
			setInventorySlotContents(slot, null);
		}
		return stack;
	}

	public String getInvName() {
		return "container.extender";
	}

	public int getInvCheckSum() {
		int sum = 0;
		if (extenderCards[0] != null)
			sum += extenderCards[0].stackSize;
		return sum;
	}
	
	
	public int getInventoryStackLimit() {
		return 64;
	}

	private void checkInterfaces() {
		
		// redstone...
		Class chk = RedstoneFunction.class;
		boolean update = false;
		int space = 0;
		if (extenderCards[0] != null)
			space = extenderCards[0].stackSize;

		for (ExtenderChannel chn : channelsIn) {
			if (chn.getFunction() instanceof RedstoneFunction) {
				if (space > 0)
					space--;
				else {
					chn.setFunction(0);
					update = true;
				}
			}
		}
		for (ExtenderChannel chn : channelsOut) {
			if (chn.getFunction() instanceof RedstoneFunction) {
				if (space > 0)
					space--;
				else {
					chn.setFunction(0);
					update = true;
				}
			}
		}
		if (update) {
			channelsConfigChangedForGui = true;
		}
	}
	
	public void onSlotChange () {
		channelsConfigChangedForGui = true;
	}

	private int countFunction(Class funct) {
		int count = 0;
		for (ExtenderChannel chn : channelsIn) {
			if (chn.getFunction() instanceof RedstoneFunction)
				count++;
		}
		for (ExtenderChannel chn : channelsOut) {
			if (chn.getFunction() instanceof RedstoneFunction)
				count++;
		}
		return count;
	}

	public boolean hasInterfaceFor(Class funct) {
		if (funct.equals(RedstoneFunction.class)) {
			if (extenderCards[0] == null)
				return false;
			int count = extenderCards[0].stackSize;
			int fnct = countFunction(RedstoneFunction.class);
			LogHelper.info("space: "+count+" has:"+fnct);
			if (count <= fnct)
				return false;
		}
		return true;
	}

	public void openChest() {

	}

	public void closeChest() {

	}

	@Override
	public ItemStack decrStackSize(int slot, int amt) {
		ItemStack stack = getStackInSlot(slot);
		if (stack != null) {
			if (stack.stackSize <= amt) {
				setInventorySlotContents(slot, null);
			} else {
				stack = stack.splitStack(amt);
				if (stack.stackSize == 0) {
					setInventorySlotContents(slot, null);
				}
			}
		}
		checkInterfaces();
		onSlotChange();
		return stack;
	}

	public boolean isChannelsConfigChangedForGui() {
		if (channelsConfigChangedForGui) {
			channelsConfigChangedForGui = false;
			return true;
		} else return false;
	}

}
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
import net.minecraftforge.common.ForgeDirection;
import de.squig.plc.logic.Circuit;
import de.squig.plc.logic.ISignalListener;
import de.squig.plc.logic.Signal;
import de.squig.plc.logic.extender.function.DisabledFunction;
import de.squig.plc.logic.extender.function.ExtenderFunction;
import de.squig.plc.logic.extender.function.ExtenderTrigger;
import de.squig.plc.logic.extender.function.RedstoneFunction;
import de.squig.plc.logic.helper.LogHelper;
import de.squig.plc.logic.objects.CircuitObject;
import de.squig.plc.tile.TileExtender;
package de.squig.plc.logic.extender;




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;