/******************************************************************************
N_Series 1000,2000,3000 encoders and decoders module for SVSi Panel Builder scripting.

Example Startup Script:
myDecoder = N_Series("169.254.160.248"); // create object

Example button:
myDecoder.switch(300); //perform switch command based on number
myDecoder.switch(myEncoder); //perform switch based on object defined earlier

startUpdating()
Automatically update status at a periodic interval.

stopUpdating()
Stop automatically updating status.

getStatus(callBack)
Refreshes the status of the device. The callBack is a function to be executed when complete.

Example use of callBack:
myDecoder.getStatus(function(){
	if(myDecoder.status.stream == 5) {
		myDecoder.dviOff();
	}
});

switch(stream)
Switch the video of the unit to a stream number.
stream may also be an SVSi object defined during startup script. 

switchAudio(stream)
Switch the audio of the unit to a stream number.
stream may also be a unit name or IP address. Controller will attempt to translate using internal database.

delay(milliseconds = 500) 
Pause execution for specified milliseconds. Default is 500 milliseconds.

reboot()
Reboot the device.

modeon()
Turn on video output of the device.

modeoff()
Turn off video output of the device.

cropref(c1=0,c2=0,c3=0,c4=0)
Crop the video (based on 1080p reference resolution). Default is 0 to turn off crop.

lineoutVolume(volLeft = null, volRight = undefined)
Set lineout volume of left or right (or both). Set parameter to null to not change that channel's volume.

Example:
lineoutVolume(50); // set both
lineoutVolume(50, null); // set left
lineoutVolume(null, 50); // set right

audioOn()
Unmute audio.

audioOff()
Mute audio.

audioFollow()
Set audio to follow video. This is identical to switchAudio(0);

dviOn()
Enable DVI.

dviOff()
Disable DVI.

localPlay(playlist = 1)
Set device to Local Play or Host Play. Default is playlist 1.

hostPlay(playlist = 1)
Identical to localPlay command.

live()
Set device to live play.

enableHdmiAudio()
Enable HDMI audio.

disableHdmiAudio()
Disable HDMI audio.

autoHdmiAudio()
Set HDMI audio to auto.

enableCC()
Enable Color Space correction.

disableCC()
Disable Color Space correction.

autoCC()
Set Color Space correction to auto.

sendIR(irname)
Send previously stored ir command.

sendIRraw(ir)
Send raw IR code.

serial(serial)
Send previously stored serial command.

serialHex(serialhex)
Send raw serial bytes based on space deliminated hex code.

*******************************************************************************/

	//define namespace if it doesn't exist
	if (!Modules.SVSi) Modules.SVSi = {};

	//Command list displays in the dropdowns in the Panel Builder script editor under 'modules'
	module_command_list = [
		{name: 'Constructor', func: 'myModule = N_Series("169.254.1.2", 50001);', comment: '//port is optional'},
		{name: 'Get Status', func: 'myModule.getStatus(function onComplete(){});', comment: '//function passed in will run on complete'},
		{name: 'Start Updating', func: 'myModule.startUpdating(2);', comment: '//will begin updating module object\'s status'},
		{name: 'Stop Updating', func: 'myModule.stopUpdating();', comment: '//will stop updating module object\'s status'},
		{name: 'Switch', func: 'myModule.switch(123);', comment: '//can pass in another module object or the stream #'},
		{name: 'Switch Audio', func: 'myModule.switchAudio(123);', comment: '//can pass in another module object or the stream #'},
		{name: 'Mute', func: 'myModule.audioOff();', comment: '//mute'},
		{name: 'Unmute', func: 'myModule.audioOn();', comment: '//unmute'},
		{name: 'Audio Follow', func: 'myModule.audioFollow();', comment: '//Set audio to follow video. This is identical to switchAudio(0);'},
		{name: 'Mode On', func: 'myModule.modeon();', comment: '//turn on encoder output of the device.'},
		{name: 'Mode Off', func: 'myModule.modeoff();', comment: '//turn off encoder output of the device.'},
		{name: 'Reboot', func: 'myModule.reboot();', comment: '//reboot the device.'},
		{name: 'Lineout Volume', func: 'myModule.lineoutVolume(null, null);', comment: '//set lineout volume of left or right (or both). set parameter to null to not change that channel\'s volume.'},
		{name: 'Crop Reference', func: 'myModule.cropref(0,0,0,0);', comment: '//crop the video (based on 1080p reference resolution). default is 0 to turn off crop.'},
		{name: 'DVI On', func: 'myModule.dviOn();', comment: '//Enable DVI.'},
		{name: 'DVI Off', func: 'myModule.dviOff();', comment: '//Disable DVI.'},
		{name: 'Local Play', func: 'myModule.localPlay(1);', comment: '//Set device to Local Play or Host Play. Default is playlist 1.'},
		{name: 'Host Play', func: 'myModule.hostPlay(1);', comment: '//Set device to Local Play or Host Play. Default is playlist 1.'},
		{name: 'Live Play', func: 'myModule.live();', comment: '//Set device to Live Play'},
		{name: 'Enable HDMI Audio', func: 'myModule.enableHdmiAudio();', comment: '//Enable HDMI Audio'},
		{name: 'Disable HDMI Audio', func: 'myModule.disableHdmiAudio();', comment: '//Disable HDMI Audio'},
		{name: 'Auto HDMI Audio', func: 'myModule.autoHdmiAudio();', comment: '//Auto HDMI Audio'},
		{name: 'Enable CC', func: 'myModule.enableCC();', comment: '//Enable Color Space Correction'},
		{name: 'Disable CC', func: 'myModule.disableCC();', comment: '//Disable Color Space Correction'},
		{name: 'Auto CC', func: 'myModule.autoCC();', comment: '//Auto Color Space Correction'},
		{name: 'Send IR', func: 'myModule.sendIR("name");', comment: '//Send previously stored ir command.'},
		{name: 'Send IR Raw', func: 'myModule.sendIRraw();', comment: '//Send raw IR code.'},
		{name: 'Serial', func: 'myModule.serial();', comment: '//Send previously stored serial command.'},
		{name: 'Serial Hex', func: 'myModule.serialHex();', comment: '//Send raw serial bytes based on space deliminated hex code.'},
		{name: 'Print', func: 'myModule.print();', comment: '//Prints this module object and all of its properties to the Javascript console.'},
		{name: 'Popup', func: 'myModule.popup();', comment: '//Displays an alert dialog with this module object and all of its properties.'},
	];
	
	/**************************    Utility Functions    ************************/
	function getStatusOfUnit(obj, onComplete, delay) {
		tcpclient(obj.ip, obj.port, asciiToHex('getStatus\r'), function(data){
			var numberKeys = [
				/*N4000*/ "againl","againr","dm_cgain","dm_fgain","dm_slgain","dm_srgain","gpihighevntdly","gratarpint",
				"headphonevol_l","headphonevol_r","inputgainleft","inputgainright","lineoutvol_l","lineoutvol_r","rxaudiodelay",
				"outstream","txstream","rxstream","stream","txaudiodelay","txsample","unsolstatusint",
				/*N_Series*/ "baud","discoveryintervalsec","discoveryport","dvicevtdly","dvidevtdly", "garpint","nd_a_drop",
				"nd_a_drop1s","nd_f_drop","nd_f_drop1s","nd_v_drop","nd_v_drop1s","playlist","streamaudio","unsolstint",
				/*NVR*/ "copypct","dvrmode","free","mpegpcnt","s_play0a","s_play0b","s_play1a","s_play2a","s_play3a","s_play4a",
				"s_play5a","s_play6a","s_play7a","s_play8a","s_play9a","s_reca0a","s_reca0b","s_reca1a","s_reca2a","s_reca3a",
				"s_reca4a","s_reca5a","s_reca6a","s_reca7a","s_reca8a","s_reca9a","s_recv0a","s_recv0b","s_recv1a","s_recv2a",
				"s_recv3a","s_recv4a","s_recv5a","s_recv6a","s_recv7a","s_recv8a","s_recv9a","stream","ttl",
				/*WP*/ "audiostream","w0stream","w1stream","w2stream","w3stream"
			];
			var keyList = ["stream"];
			obj.status = {}; obj.rawstatus = data;
			var str = obj.rawstatus;
			while (str.indexOf('\r') != -1){
				var key = str.substring(0, str.indexOf(':')).toLowerCase();
				var valueString = str.substring(str.indexOf(':')+1, str.indexOf('\r')), valueFloat = parseFloat(valueString);
				if (numberKeys.indexOf(key) != -1) obj.status[key] = valueFloat;	
				else obj.status[key] = valueString;
				keyList.push(key);
				str = str.substring(str.indexOf('\r')+1);
			}
			if (obj.rawstatus === '') obj.error = 'Empty response returned. Maybe no connection to unit?';
			if (obj.error && obj.onError) obj.onError(data, obj.error);
			if (onComplete) onComplete(data);
		});
	};
	function getStreamFromUnit(name){
		for (var i=0; i<unitlist.length; i++) if (unitlist[i].ip == name) return unitlist[i].videostream;
		for (var i=0; i<unitlist.length; i++) if (unitlist[i].name == name) return unitlist[i].videostream;
		for (var i=0; i<unitlist.length; i++) if (unitlist[i].mac.toUpperCase() == name.toUpperCase()) return unitlist[i].videostream;
		return false;
	};
	function getAudioStreamFromUnit(name){
		for (var i=0; i<unitlist.length; i++) if (unitlist[i].ip == name) return unitlist[i].audiostream;
		for (var i=0; i<unitlist.length; i++) if (unitlist[i].name == name) return unitlist[i].audiostream;
		for (var i=0; i<unitlist.length; i++) if (unitlist[i].mac.toUpperCase() == name.toUpperCase()) return unitlist[i].audiostream;
		return false;
	};
	/************************    End of Utility Functions    ***********************/
	
	/* 
	* N_Series function returns an object representing an N-Series unit
	* ip is the ip address, port is the port to open socket on,
	* onError is a function to be called on errors
	*/
	Modules.N_Series = Modules.SVSi.N_Series = function(ip, port, opts){
			
		if (undef(opts)) opts = {};
		
		var obj = {};
		
		//properties
		obj.ip = !undef(ip) ? ip : '169.254.1.2';
		obj.port = !undef(port) ? port : '50001';
		obj.serialport = opts.hasOwnProperty('serialport') ? opts.serialport : '50004';
		obj.onError = opts.hasOwnProperty('onError') ? opts.onError : function(err){console.log(err)};
		obj.serial_interface = opts.hasOwnProperty('serial_interface') ? opts.serial_interface : false;
		obj.ir_interface = opts.hasOwnProperty('ir_interface') ? opts.ir_interface : false;
		obj._updateInterval = false;
		obj.connection = false;
		obj.status = {};
		
		obj.protocol = opts.hasOwnProperty('protocol') ? protocol : 'tcp';
		obj.statusprotocol = opts.hasOwnProperty('statusprotocol') ? statusprotocol : 'udp';
		
		//sets port, and reestablishes socket to that port
		obj.setPort = function(port){ 
			obj.port = port;
			if (obj.connection){
				obj.connection.destroy();
				obj.tcpopen();
			}
		};
		
		//gets the status of the unit and fills it in as properties on the object
		obj.getStatus = function(onComplete){ getStatusOfUnit(obj, onComplete); };
		
		//gets the status of the unit and fills it in as properties on the object
		//also sets a timeout to call the function again and calls onUpdate if specified
		obj.startUpdating = function(interval, onUpdate){
			if (undef(interval)) interval = 3;
			if (undef(onUpdate)) onUpdate = noop;
			obj._updateInterval = setInterval(function(){
				obj.getStatus(onUpdate);
			}, interval*1000);
		};
		
		obj.stopUpdating = function(){ clearInterval(obj._updateInterval); };
		
		obj.switch = function(stream){
			if (typeof stream != 'number'){
				if (typeof stream === 'object' && stream.status && stream.status.stream) 
					stream = stream.status.stream;
			}
			tcpclient(obj.ip, obj.port, asciiToHex('set:'+stream));
		};
		
		obj.stream = obj.switch;
		
		obj.switchAudio = function(stream){
			if (typeof stream != 'number'){
				if (typeof stream === 'object' && stream.status && stream.status.stream) 
					stream = stream.status.stream;
			}
			tcpclient(obj.ip, obj.port, asciiToHex('seta:'+stream));
		};
		
		obj.audioStream = obj.switchAudio;
		
		obj.delay = function(milliseconds){
			if (undef(milliseconds)) milliseconds = 500;
			var start = new Date().getTime();
			for (var i = 0; i < 1e7; i++){
				if ((new Date().getTime() - start) > milliseconds){
					break;
				}
			}
		};
		
		obj.reboot = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('setSettings:reboot:reboot\r\n'));
		};
		
		obj.modeon = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('txenable\r'));
		};
		
		obj.modeoff = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('txdisable\r'));
		};
		
		obj.cropref = function(c1,c2,c3,c4){
			if (undef(c1)) c1 = 0; 
			if (undef(c2)) c2 = 0;
			if (undef(c3)) c3 = 0; 
			if (undef(c4)) c4 = 0;
			tcpclient(obj.ip, obj.port, asciiToHex('cropref:'+c1+','+c2+','+c3+','+c4+'\r'));
		};
		
		obj.lineoutVolume = function(volLeft, volRight){
			if (undef(volRight)){
				tcpclient(obj.ip, obj.port, asciiToHex('lovol:'+volLeft+'\r'));
				return;
			}
			else if (!undef(volLeft) && (!volRight && volRight !== 0)){
				tcpclient(obj.ip, obj.port, asciiToHex('lovolleft:'+volLeft+'\r'));
				return;
			}
			else if ((!volLeft && volLeft !== 0) && !undef(volRight)){
				tcpclient(obj.ip, obj.port, asciiToHex('lovolright:'+volRight+'\r'));
				return;
			}
			else {
				tcpclient(obj.ip, obj.port, asciiToHex('lovolleft:'+volLeft+'\r'));
				tcpclient(obj.ip, obj.port, asciiToHex('lovolright:'+volRight+'\r'));
			}
		};
		
		obj.audioOn = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('unmute\r'));
		};
		
		obj.audioOff = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('mute\r'));
		};
		
		obj.audioFollow = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('seta:0\r'));
		};
		
		obj.dviOn = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('dviOn\r'));
		};
		
		obj.dviOff = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('dviOff\r'));
		};
		
		obj.localPlay = function(playlist){
			if (undef(playlist)) playlist = 1;
			tcpclient(obj.ip, obj.port, asciiToHex('local:'+playlist+'\r'));
		};
		
		obj.hostPlay = function(playlist){
			obj.localPlay(playlist);
		};
		
		obj.live = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('live\r'));
		};
		
		obj.enableHdmiAudio = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('setSettings:hdmiAudio:on\r'));
		};
		
		obj.disableHdmiAudio = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('setSettings:hdmiAudio:off\r'));
		};
		
		obj.autoHdmiAudio = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('setSettings:hdmiAudio:auto\r'));
		};
		
		obj.enableCC = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('setSettings:colorSpaceCor:on\r'));
		};
		
		obj.disableCC = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('setSettings:colorSpaceCor:off\r'));
		};
		
		obj.autoCC = function(){
			tcpclient(obj.ip, obj.port, asciiToHex('setSettings:colorSpaceCor:auto\r'));
		};
		
		obj.sendIR = function(ir){
			tcpclient(obj.ip, obj.port, asciiToHex('sendir:'+ir+'\r'));
		};
		
		obj.sendIRraw = function(ir){
			tcpclient(obj.ip, obj.port, asciiToHex('sendirraw:'+ir+'\r'));
		};
		
		obj.serial = function(serial){
			tcpclient(obj.ip, obj.port, asciiToHex('sendser:'+serial+'\n\r'));
		};
		
		obj.serialHex = function(serialhex, callback){
			tcpclient(obj.ip, 50004, serialhex, callback);
		};
		
		obj.tpc = function(tpcID){
			tcpclient(Server.name, 50020, asciiToHex("tpc " + obj.ip + " " + tpcID + "\n\r"));
        };
		
		obj.serialLibrary = function(libID){
			tcpclient(Server.name, 50020, asciiToHex("serial " + obj.ip + " " + libID + "\n\r"));
        };

		obj.print = function(){
			console.log(obj);
		};
		
		obj.popup = function(){
			var output = '';
			for (var property in obj){
				if (typeof obj[property] !== 'function') output += property + ': ' + obj[property]+'; \n\n';
			}
			alert(output);
		};
		
		obj.inputLevel = function(newLevel){
			var gainlevel;
			switch (newLevel){
				default: case 0: gainlevel = 0; break;
				case 1: gainlevel = -1.5; break;
				case 2: gainlevel = -3; break;
				case 3: gainlevel = -4.5; break;
				case 4: gainlevel = -6; break;
				case 5: gainlevel = -7.5; break;
				case 6: gainlevel = -9; break;
				case 7: gainlevel = -10.5; break;
				case 8: gainlevel = -12; break;
			}
			tcpclient(obj.ip, obj.port, asciiToHex('setSettings:inputGainleft:' + gainlevel +
				"\rsetSettings:inputGainright:" + gainlevel + "\r" ));
		};
		
		if (obj.serial_interface){
			obj.serial_interface.ip = obj.ip;
			obj.serial_interface.port = obj.serialport;
			var intermediate = obj.serial_interface;
			obj.serial_interface = {};
			for (var prop in intermediate){
				if (intermediate.hasOwnProperty(prop)){
					if (typeof intermediate[prop] === 'function') 
						obj.serial_interface[prop] = intermediate[prop].bind(obj);
					else obj.serial_interface[prop] = intermediate[prop];
				}
			}
		}
		
		if (obj.ir_interface){
			obj.ir_interface.ip = obj.ip;
			obj.ir_interface.port = obj.serialport;
			var intermediate = obj.ir_interface;
			obj.ir_interface = {};
			for (var prop in intermediate){
				if (intermediate.hasOwnProperty(prop)){
					if (typeof intermediate[prop] === 'function') 
						obj.ir_interface[prop] = intermediate[prop].bind(obj);
					else obj.ir_interface[prop] = intermediate[prop];
				}
			}
		}
		
		obj.getStatus();
		
		return obj;
		
	};
	