function WU5(acX){
	var that = this;
	var WU_Interface = this;
	this.WU;
	this.isBusy = function(){
		return Busy;
	}
	var Busy = false;
	this.setWU = function(w){
		WU_Interface.WU = w;

		if(!(acX.version && compareVersionString(acX.version,Weblink_ActiveX_Current) >= 0)){
			WU_Interface.WU.$.triggerHandler('pluginNew');
		}
	};
	var activeX = acX;
	this.version = 5;
	this.getVersion = function(bool){
		if(bool) return {version:that.version,revision:acX.version};
		return that.version;
	}
	var services = [];
	
	var service = new Weblink_Service('detectDevices');
	services.push(service);
	var service = new Weblink_Service('writeConfiguration');
	//service.addValidator(Weblink_Validators.NO_STARTER_UPDATE);
	services.push(service);
	var service = new Weblink_Service('readConfiguration');
	//service.addValidator(Weblink_Validators.NO_STARTER_UPDATE);
	services.push(service);
	var service = new Weblink_Service('flash');
	//service.addValidator(Weblink_Validators.NO_STARTER_UPDATE);
	services.push(service);
	                
	this.getServices = function(){
		return services;
	};
	
	/**
	* Connect/Disconnect/GetSerial/GetBootLoader
	*/
	function connect(){
		//try to disconnect, if fail, no problem because it is normal.
		//It would succeed if and only if the device was still (from a previous flash or something...) powered up.
		try{disconnect();} catch(e){}
		
		if(!activeX.connect) throw "Connect failed";
	}
	function disconnect(){
		if(!activeX.disconnect) throw "Disonnect failed";
	}
	
	
	/**
	 * get error information
	 * 0=ok  	1=Weblink not connected, 2=Module not connected, 3=AxtiveX programming error
     *      	4=Invalid type, 5=Invalid firmware, 6=Invalid CTR
     *		    7=Failed to load OS, 8=MAC failed
     *      	9=Invalid blocksize, 10=can't unlock, 11=can't read feature, 12=can't write features
     *      	13=can't write key, 14=can't read key, 15=can't read starter feature
     *			16=can't write starter feature
	 */
	function getActiveXerror(error){
		switch(error){
		case 0: return 0; break;
		case 1: return new CableNotDetectedException(); break;
		case 2: return new DeviceNotDetectedException(); break;
		case 3: return new ActiveXNotSupportedException(); break;
		case 4: return new InvalidDeviceException(); break;
		case 5: return new InvalidFirmwareException(); break;
		case 6: return new InvalidCTRException(); break;
		case 7: return new FailedToLoadOSException(); break;
		case 8: return new MACFailedException(); break;
		case 9: return new InvalidBlockSizeException(); break;
		case 10: return new UnlockFailedException(); break;
		case 11: return new FailedToReadFeatureExceiption(); break;
		case 12: return new FailedToWriteFeatureExceiption(); break;
		case 13: return new FailedToReadKeyExceiption(); break;
		case 14: return new FailedToWriteKeyExceiption(); break;
		case 15: return new FailedToReadStarterFeatureExceiption(); break;
		case 16: return new FailedToWriteStarterFeatureExceiption(); break;
		default: return new DetectionErrorException(); break;
		}
	}
	
	
	/**
	 * Detects and broadcasts 
	 * dispatches: 'deviceDetected', 'deviceDetectComplete','deviceDetectError'
	 * throws: DeviceNotDetectedException,CableNotDetectedException,DetectionErrorException, PluginTimeOutException
	 */
	this.detectDevices = function(){
		if(Busy){
			WU_Interface.WU.$.triggerHandler('pluginBusy');
			return;
			}
		Busy = true;
		
		function getSerial(){
			var serial = activeX.serial;
			if(typeof(serial)=="undefined") throw "plugin unresponsive";
			if(serial == 0) throw "No device detected";
			return serial;
		}
		function getBootLoader(){
			var boot = activeX.bootloader;
			if(typeof(boot)=="undefined") throw "plugin unresponsive";
			if(boot == 0) throw "No device detected";
			return boot;
		}
		
		function getModule(){
			var Serial;
			var Boot;
			try{
				connect();
	            Serial = getSerial();
	            Boot = getBootLoader();
				var Type = Weblink_Types.getByBootLoader(Boot);
				tracking.addInfo('Detected module with bootloader '+Boot+' and serial '+Serial);
				if(Type == Weblink_Types.REMOTE_STARTER){
					WU_Interface.WU.$.triggerHandler('deviceDetected',[Type,{'BootLoader':Boot}]);
					return;
				}
				try{
					
					WU_Interface.WU.$.triggerHandler('deviceDetected',[Type,{'Serial':Serial,'BootLoader':Boot}]);

				} catch(e){
					that.WU.$.triggerHandler('deviceDetectError',[e]);
            	}
            } catch(e){
            	try{
            		error = activeX.error;
            		e = getActiveXerror(error);
            		that.WU.$.triggerHandler('deviceDetectError',[e]);
            	} catch(e){
            		//alertX(e);
            	}
            } finally {
           		disconnect();
         	}
         	
		}
		function getStarter(){
			function readStarterConf(rules){
				var Conf = [];
				
				if(typeof(rules)=='string'){
					rules = rules.replace('0x','');
					str = rules;
					rules = [];
					while(str.length != 0){
						var spl = str.slice(0,2);
						str = str.slice(2);
						rules.push(spl);
					}
				}
				try{
						connect();
						activeX.startToWrite;
					for(var i=0; i<rules.length; i++){
						
						res = activeX.getfeature(rules[i],2);
						
						if(res == "") throw "Read failed";
						Conf.push('0x'+rules[i]+':'+res);
						
					}
				} catch(e){
					WU_Interface.WU.$.triggerHandler('readError',[e]);
					return;
				} finally {
					disconnect();
				}
				result = Conf.join(',');
				try{
					var conf = new Weblink_Device_StarterConf(result);
					WU_Interface.WU.$.triggerHandler('readComplete',[conf]);
					return;
				} catch (e) {
					WU_Interface.WU.$.triggerHandler('readError',[e]);
				}
			}
			
			var Starter = new Weblink_Device(Weblink_Types.REMOTE_STARTER,{});
			var BrandAddress = new Array();
			BrandAddress.push('81');
			WU_Interface.WU.$.one('readError',function(evt,data){
				
				tracking.addInfo('Error reading starter eeprom');
				WU_Interface.WU.$.triggerHandler('starterDetectError');
			});
			WU_Interface.WU.$.one(
					'readComplete',
					function(evt,conf){

						tracking.addInfo('Starter eeprom read:'+conf.toString());
						var SI = new Weblink_ServerInterface();
						SI.$.one('errorLoading',function(){
							WU_Interface.WU.$.triggerHandler('starterDetectError');
						});
						SI.$.one('dataLoaded',function(evt,data){
							if(typeof(data.Manufacturer)=="undefined" || data.Manufacturer == false){WU_Interface.WU.$.triggerHandler('starterDetectError'); return;}
							Starter.setInfos(data);
							var hardwareSignature;
							var firmwareSignature;
							WU_Interface.WU.$.one('readComplete',
									function(evt,data){
										tracking.addInfo('Starter eeprom read:'+data);
										hardwareSignature = data;
										WU_Interface.WU.$.one('readComplete',
												function(evt,data){
													tracking.addInfo('Starter eeprom read:'+data);
													firmwareSignature = data;
													var SI = new Weblink_ServerInterface();
													SI.$.one('errorLoading',function(){
															WU_Interface.WU.$.triggerHandler('starterDetectError');
														});
													SI.$.one('dataLoaded',function(evt,data){
														for(x in data){
															Starter.setInfos(x,data[x]);
														}
														WU_Interface.WU.$.triggerHandler('deviceDetected',[Starter]);
														});
													SI.getStarterInfo(Starter.get('Manufacturer'),hardwareSignature,firmwareSignature,false);
												});
										readStarterConf(Starter.get('Manufacturer').FirmwareAddress);
									});	
							readStarterConf(Starter.get('Manufacturer').HardwareAddress);
						});
						SI.getStarterManufacturer(conf);
					});
			readStarterConf(BrandAddress);
			
			
		}
		var onDetect = function(evt,type,infos){
			if(type == Weblink_Types.BLADE_MODULE){
				WU_Interface.WU.$.one('starterDetectError',
						function(){
							Busy = false;
							WU_Interface.WU.$.triggerHandler('deviceDetectComplete');
				});
				getStarter();
			} else {
				Busy = false;
				WU_Interface.WU.$.triggerHandler('deviceDetectComplete');
				WU_Interface.WU.$.unbind('deviceDetected',onDetect);
			}
			
		};
		WU_Interface.WU.$.bind(
				'deviceDetected',onDetect
				);
		//set activeX authKey.
		if(typeof(tracking) != 'undefined'){
			tracking.setKey();
		}
		getModule();
		
	};
	
	/**
	 * Writes to device memory
	 * dispatches 'writeProgress','writeComplete','writeError'
	 * throws WriteException, PluginTimeOutException
	 */
	this.writeConfiguration = function(device, conf){
		if(Busy){
			WU_Interface.WU.$.triggerHandler('pluginBusy');
			return;
			}
		function writeModuleConf(conf){
			if(!(conf instanceof Weblink_Device_ModuleConf)){
				return WU_Interface.WU.$.triggerHandler('writeError',[new InvalidConfigurationException()]);
			}
			try{
				connect();
				if(!activeX.setfeature('08',1,conf.toString())){
					throw new ActiveXException(activeX.error);
				}
			} catch(e){
				disconnect();
				return WU_Interface.WU.$.triggerHandler('writeError',[e]);
			} finally {
				disconnect();
			}
			
			WU_Interface.WU.$.triggerHandler('writeComplete');
			
		}
		function writeStarterConf(conf){
			if(!(conf instanceof Weblink_Device_StarterConf)){
				return WU_Interface.WU.$.triggerHandler('writeError',[new InvalidConfigurationException()]);
			}
			var rules = conf.toMap();
			
			try{
				connect();
				activeX.startToWrite;
				for(x in rules){
					//What the hell...
					//TODO validate x and rules[x]
					
						if(!activeX.setfeature(x,2,rules[x])){
							throw new ActiveXException(activeX.error);
						}
					
				}
				tracking.addInfo('Starter eeprom write:'+conf.toString());
			} catch(e){
				disconnect();
				tracking.addInfo('Starter eeprom write failed:'+conf.toString());
				return WU_Interface.WU.$.triggerHandler('writeError',[e]);
			} finally {
				disconnect();
			}
			
			WU_Interface.WU.$.triggerHandler('writeComplete');
				
		}
		
		if(device.isModule()){
			writeModuleConf(conf);
		} else if(device.isStarter()){
			writeStarterConf(conf);
		}
		
	};
	
	/**
	 * Reads from device memory
	 * 
	 * dispatches 'readStart','readProgress','readComplete(result)','readError'
	 * throws ReadException, PluginTimeOutException
	 */
	this.readConfiguration = function(device,rules){
		if(Busy){
			WU_Interface.WU.$.triggerHandler('pluginBusy');
			return;
			}
		function readModuleConf(rules){
			var result = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
			if(typeof(rules)=="undefined") rules = '08';
				try{
					connect();
					var res = activeX.getfeature(rules,1);
					if(res == ""){
						throw new ReadException(activeX.error);
					}
					result = res;
				}catch(e){
					WU_Interface.WU.$.triggerHandler('readError',[e]);
					return false;
				} finally {
					disconnect();
				}
				try{
					var conf = new Weblink_Device_ModuleConf(result);
					WU_Interface.WU.$.triggerHandler('readComplete',[conf]);
					return;
				} catch (e) {
					WU_Interface.WU.$.triggerHandler('readError',[e]);
				}
				
			}
		
		function readStarterConf(rules){
			var Conf = [];
			
				if(typeof(rules)=='string'){
					rules = rules.replace('0x','');
					str = rules;
					rules = [];
					while(str.length != 0){
						var spl = str.slice(0,2);
						str = str.slice(2);
						rules.push(spl);
					}
				}
				try{
					connect();
					activeX.startToWrite;
				for(var i=0; i<rules.length; i++){
					
					res = activeX.getfeature(rules[i],2);
					
					if(res == "") throw "Read failed";
					Conf.push('0x'+rules[i]+':'+res);
					
				}
			} catch(e){
				WU_Interface.WU.$.triggerHandler('readError',[e]);
				return;
			} finally {
				disconnect();
			}
			result = Conf.join(',');
			try{
				var conf = new Weblink_Device_StarterConf(result);
				tracking.addInfo('Starter eeprom read:'+conf.toString());
				WU_Interface.WU.$.triggerHandler('readComplete',[conf]);
				return;
			} catch (e) {
				WU_Interface.WU.$.triggerHandler('readError',[e]);
			}
		}
		
		if(device.isModule()){
			readModuleConf(rules);
		} else if (device.isStarter()){
			readStarterConf(rules);
		}
	};
	
	
	/**
	 * Writes Firmware to device
	 * dispatches 'flashStart','flashDelay','flashError(error)','flashProgress(bytes,perc)','flashComplete'
	 * throws WriteException, PluginTimeOutException
	 * @param Weblink_Device device
	 */
	this.flash = function(device, firmware){
		var DELAY_AFTER_FLASH = 1500;
		if(device.getType() == Weblink_Types.SKIB){
			DELAY_AFTER_FLASH = 4000;
		}
		if(Busy){
			WU_Interface.WU.$.triggerHandler('pluginBusy');
			return;
			}
		Busy = true;
		if(!(device instanceof Weblink_Device)) throw new InvalidDeviceException();
		
		if(!(firmware instanceof Weblink_Firmware)) throw new InvalidFirmwareException();
		
		var zeros = 0;
		
		if(device.getType()==Weblink_Types.REMOTE_STARTER){
			var CodeSize = firmware.CodeLength;
		} else {
			var blocksize = device.getBlockSize();
			var CodeSize;
		}
		function writeBack(){
			$.ajax({
				url: '/weblink/updater/set-device-info',
				data: {
					serial: that.WU.getDevice(1).get('Serial'),
					firmwareid: firmware.id
				},
				type: 'GET',
				dataType: 'html',
				cache: false,
				success: function(data){},
				error: function(){}
			});
		}
		function getStatus(){
				
			var blocks = activeX.status;
			
			var error = activeX.error;
			if(error != 0){
				disconnect();
				Busy = false;
				tracking.addInfo('Flashed failed with error:'+error);
				WU_Interface.WU.$.triggerHandler('flashError',[getActiveXerror(error)]);
				return;
			}
			
			if(blocks<=0 && activeX.onreadystatechange == 4){
				//setTimeout(disconnect,300);
				setTimeout(
					function(){
						disconnect();
						Busy = false;
						WU_Interface.WU.$.triggerHandler('flashComplete');
						writeBack();
					},
				DELAY_AFTER_FLASH);
				return;
			} else if(!isNaN(parseInt(blocks)) && blocks != '0'){
				percentage = 100-(Math.round(parseInt(blocks)/CodeSize*100));
				if(typeof(tracking) != "undefined"){
					tracking.process['CompletePerc']=percentage+"%";
				}
				WU_Interface.WU.$.triggerHandler('flashProgress',[blocks,percentage]);
				
			}
			
			setTimeout(getStatus,150);
		}
		
		function getModuleCode(fw){
			var SI = new Weblink_ServerInterface();
			SI.$.one('dataLoaded',
				function(evt,data){
					CodeSize = ((data.code.length/2)-30)/blocksize;
					writeCode(data.code);
				});
			SI.$.one('errorLoading',
					function(){

						Busy = false;
						WU_Interface.WU.$.triggerHandler('flashError',[new FlashException('Could not retrieve code')]);
			});
			if(typeof(tracking) != 'undefined'){
				var currentTime = new Date();
				var second = currentTime.getSeconds();
				var minute = currentTime.getMinutes();
				var hour = currentTime.getHours();
				tracking.addInfo("Starting to get module code (" + hour + ":" + minute + ":" + second+")");
			}
			SI.getModuleCode(device,fw);	
		}
		function getStarterCode(fw){
			var SI = new Weblink_ServerInterface();
			SI.$.one('dataLoaded',
				function(evt,data){
					writeCode(data.code);
				});
			SI.$.one('errorLoading',
					function(){
						WU_Interface.WU.$.triggerHandler('flashError',[new FlashException('Could not retrieve code')]);
						Busy = false;
			});
			if(typeof(tracking) != 'undefined'){
				var currentTime = new Date();
				var second = currentTime.getSeconds();
				var minute = currentTime.getMinutes();
				var hour = currentTime.getHours();
				tracking.addInfo("Starting to get starter code (" + hour + ":" + minute + ":" + second+")");
			}
			SI.getStarterCode(device,fw);
		}
		function writeCode(code){
			try{
				connect();
				if(typeof(tracking) != 'undefined'){
					var currentTime = new Date();
					var second = currentTime.getSeconds();
					var minute = currentTime.getMinutes();
					var hour = currentTime.getHours();
					tracking.addInfo("Starting to write starter code (" + hour + ":" + minute + ":" + second+")");
				}
				if(device.isStarter()){
					activeX.flash(code,0,2,'0B');
				}else if(device.isModule()){
					activeX.flash(code,blocksize,1,'02');
				} else {
					throw new FlashException("Invalid device type");
				}
				WU_Interface.WU.$.triggerHandler('flashStarts');
				setTimeout(getStatus,500);
			} catch(e){
				WU_Interface.WU.$.triggerHandler('flashError',[e]);
				try {
					disconnect();
				} catch(e){}
			} finally {
				
			}
		}
		
		if(device.isModule()){
			getModuleCode(firmware);
		} else if(device.isStarter()){
			getStarterCode(firmware);
		}

	};
	
	this.unlock = function(){
		if(Busy){
			WU_Interface.WU.$.triggerHandler('pluginBusy');
			return;
			}
		Busy = true;
		var Time = TimeLimit = 10000;
		var ErrorLimit = Math.floor((0.3+Math.random()*0.7)*TimeLimit);
		var Delay = 500;
		function update(bool){
			Time-= Delay;
			var progress = Time/TimeLimit;
			WU_Interface.WU.$.triggerHandler('unlockProgress',[Time,100-(progress*100)]);
			if(bool == 0){
				if(Time > 0 ){
					setTimeout(function(){update(bool);},Delay);
				} else {
					Busy = false;
					WU_Interface.WU.$.triggerHandler('unlockComplete');
				}
			} else {
				if(Time > ErrorLimit ){
					setTimeout(function(){update(bool);},Delay);
				} else {
					Busy = false;
					WU_Interface.WU.$.triggerHandler('unlockError',[new WriteConfException('Unlock Failed.')]);
				}
			}
		}
		function unlock(){
			if(activeX.setport(2)){
				setTimeout(function(){update(0)},Delay);
			} else {
				setTimeout(function(){update(1)},Delay);
			}
		}
		unlock();
	};
	
}
WU5.prototype = new WU_Abstract();