/******************************************************************************
NVR module for SVSi Panel Builder scripting.

Sample Startup Script:
myNVR = NVR("169.254.160.248"); // create object

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.

switch(stream, channel = 1)
Switch the record video stream number of specified channel. Default channel is 1.
stream may also be a unit name or IP address. Controller will attempt to translate using internal database.

switchAudio(stream, channel = 1)
Switch the record audio stream number of specified channel. Default channel is 1.
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.

reboot()
Reboot the NVR.

audioOff(channel = -1)
Mute the channel. Default is -1 to mute all channels.

audioOn(channel = -1)
Unmute the channel. Default is -1 to unmute all channels.

setMode(mode)
Set mode of NVR. Valid values 1, 2, 3, or 1000, 2000, 3000.

record(stream=already set, channel=first available, duration=60, description=generated)
Record a stream on a channel, for a specified duration in minutes, with specified description.

Parameter defaults...
stream default is the value that NVR channel is currently set to.
channel default is first available. If no channels are available or NVR is in 1000/2000 mode, then channel 1 is chosen. 
duration default is 60 minutes.
description must be printable ASCII characters and cannot contain \r, \n, ", or :
If not set, a default description will be generated based on the parameters specified.

dsrecord(stream1 = already set, stream2 = already set, duration = 60, description = generated)
Command is for 2000 mode only. Record a dual-sync stream with specified duration and description. 

Parameter defaults...
stream1 default is the value NVR channel 1 is currently set to.
stream2 default is the value NVR channel 2 is currently set to.
duration default is 60 minutes.
description must be printable ASCII characters and cannot contain \r, \n, ", or :
If not set, a default description will be generated based on the parameters specified.

play(file, loop = 0, channel = 1)
Play a recording. file is in "f.########.index" format. loop is 1 or 0. 
Default is 0 to not loop. Default channel is 1.

playLatest(loop = 0, channel = 1)
Play latest recording.  Like play but without the file-name argument.

stopRecord(channel = -1)
Stop recording on a channel. Default is -1 to stop all channels.

stop(channel =  -1)
Stop playing on a channel. Default is -1 to stop all channels.

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

	//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 = NVR("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, 1);', comment: '//can pass in another module object or the stream #, second argument is channel #'},
		{name: 'Switch Audio', func: 'myModule.switchAudio(123, 1);', comment: '//can pass in another module object or the stream #, second argument is channel #'},
		{name: 'Mute', func: 'myModule.audioOff();', comment: '//mute'},
		{name: 'Unmute', func: 'myModule.audioOn();', comment: '//unmute'},
		{name: 'Set Mode', func: 'myModule.setMode(1);', comment: '//Set mode of NVR. Valid values 1, 2, 3, or 1000, 2000, 3000.'},
		{name: 'Record', func: 'myModule.record(123, 1, 60, "My recording.");', comment: '//Record a stream on a channel, for a specified duration in minutes, with specified description.'},
		{name: 'Dual Sync Record', func: 'myModule.dsrecord(123, 124, 60, "My recording.");', comment: '//Command is for 2000 mode only. Record a dual-sync stream with specified duration and description.'},
		{name: 'Play', func: 'myModule.play("filename", true, 1);', comment: '//Play a recording. loop is 1 or 0 (or true/false). Default is 0 to not loop. Default channel is 1.'},
		{name: 'Stop Record', func: 'myModule.stopRecord(-1);', comment: '//Stop recording on a channel. Default is -1 to stop all channels.'},
		{name: 'Stop', func: 'myModule.stop(-1);', comment: '//Stop playback on a channel. Default is -1 to stop all channels.'},
		{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    ***********************/

	/* 
	* NVR function returns an object representing an NVR unit
	* ip is the ip address, port is the port to open socket on,
	* onError is a function to be called on errors
	*/
	Modules.NVR = Modules.SVSi.NVR = function(ip, port, onError){
			
		var obj = {};

		var cleanDescription = function(desc){
			//remove non-printable, non-ascii
			var teststring = desc.replace(/[^\x20-\x7E]+/g, '');
			//remove other bad characters
			teststring = teststring.replace(new RegExp('"', "g"), ' ');
			teststring = teststring.replace(new RegExp(':', "g"), ';');
			teststring = teststring.replace(new RegExp('\r', "g"), '');
			teststring = teststring.replace(new RegExp('\n', "g"), '');
			return teststring;
		};
		
		var getChannelStream = function(channel){
			streamObj = {};
			if (channel == 1 && obj.dvrmode == "2"){
				streamObj['play'] = obj.s_play0b;
				streamObj['recv'] = obj.s_recv0b;
				streamObj['reca'] = obj.s_reca0b;
			
			}
			else {
				if (obj.dvrmode == "3"){
					streamObj['play'] = obj['s_play'+channel+'a'];
					streamObj['recv'] = obj['s_recv'+channel+'a'];
					streamObj['reca'] = obj['s_reca'+channel+'a'];
				}
				else {
					streamObj['play'] = obj.s_play0a;
					streamObj['recv'] = obj.s_recv0a;
					streamObj['reca'] = obj.s_reca0a;
				}
			}
			return streamObj;
		};

		obj.ip = ip;
		obj.rawstatus = '';
		obj.error = false;
		obj.port = !undef(port) ? port : '50001';
		obj.onError = !undef(onError) ? onError : noop;
		obj.dvrmode = '1';
		obj.status = {};
		
		obj.setPort = function(port){ obj.port = port; };
		
		obj._updateInterval = false;

		//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, channel){
			if (undef(channel)) channel = 1;
			channel--;
			tcpclient(obj.ip, obj.port, asciiToHex('dvr mode is '+obj.dvrmode+'\r'));	
			if (typeof stream !== 'number') {
				if (typeof stream === 'object' && stream.ip) stream = stream.ip;
				else stream = getStreamFromUnit(stream);
			}
			if (obj.dvrmode == "1"){ 
				tcpclient(obj.ip, obj.port, asciiToHex('recswitch:0,'+stream+',*\r'));	
			}
			if (obj.dvrmode == "2"){
				if (channel == 1){
					tcpclient(obj.ip, obj.port, asciiToHex('recswitch:0,'+stream+','+obj['s_reca0a']+','+obj['s_recv0b']+','+obj['s_reca0b']+'\r'));	
				} 
				else {
					tcpclient(obj.ip, obj.port, asciiToHex('recswitch:0,'+obj['s_recv0a']+','+obj['s_reca0a']+','+stream+','+obj['s_reca0b']+'\r'));	
				}
			}
			if (obj.dvrmode == "3"){
				tcpclient(obj.ip, obj.port, asciiToHex('recswitch:'+channel+','+stream+',*'+'\r'));	
			}
		};
		
		obj.stream = obj.switch;
		
		obj.switchAudio = function(stream, channel){
			if (undef(channel)) channel = 1;
			channel--;
			if (obj.dvrmode == "1"){ 
				tcpclient(obj.ip, obj.port, asciiToHex('recswitch:0,'+obj['s_recv0a']+','+stream+'\r'));	
			}
			if (obj.dvrmode == "2"){
				if (channel == 1){
					tcpclient(obj.ip, obj.port, asciiToHex('recswitch:0,'+obj['s_recv0a']+','+stream+','+obj['s_recv0b']+','+obj['s_reca0b']+'\r'));	
				} 
				else {
					tcpclient(obj.ip, obj.port, asciiToHex('recswitch:0,'+obj['s_recv0a']+','+obj['s_reca0a']+','+obj['s_recv0b']+','+stream+'\r'));	
				}
			}
			if (obj.dvrmode == "3"){
				tcpclient(obj.ip, obj.port, asciiToHex('recswitch:'+channel+','+obj['s_recv'+channel+'a']+','+stream+'\r'));	
			}
		};
		
		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('reboot:reboot\r\n'));
		};
				
		obj.audioOff = function(channel){
			if (undef(channel)) channel = 0;
			channel--;
			if (channel < 0) channel = -1;		
			tcpclient(obj.ip, obj.port, asciiToHex('audiodisable:'+channel+'\r'));
		};
				
		obj.audioOn = function(channel){
			if (channel == undefined) channel = 0;
			channel--;
			if (channel < 0) channel = -1;		
			tcpclient(obj.ip, obj.port, asciiToHex('audioenable:'+channel+'\r'));
		};
		
		obj.setMode = function(mode){
			var modeInt = parseInt(mode, 10);
			if (isNaN(modeInt)) modeInt = 1;
			if (modeInt >= 1000) modeInt = Math.round(modeInt / 1000);
			tcpclient(obj.ip, obj.port, asciiToHex('dvrmode:'+modeInt+'\r'));
			obj.dvrmode = modeInt.toString();
		};

		obj.record = function(stream, channel, duration, description){
			if (obj.dvrmode == "1" || obj.dvrmode == "2") channel = 1;		
			if (undef(duration)) duration = 60;
			if (undef(channel) && obj.dvrmode == "3"){
				var freechannel = obj.isrec.indexOf("0");
				if (freechannel > -1) channel = freechannel+1;
				else channel = 1;
			}
			channel--;
			if (undef(stream)){
				streamObj = getChannelStream(channel);
				stream = streamObj['recv'];
			}
			if (undef(description)){
				description = project.name + ' recording stream ' + stream +' for ' +duration+' min';
			}
			description = cleanDescription(description);
			tcpclient(obj.ip, obj.port,
				asciiToHex('record:'+channel+','+stream+','+stream+','+(duration*60)+','+description+'\r'));		
		};

		obj.dsrecord = function(stream1, stream2, duration, description){
			if (undef(stream1)){
				streamObj = getChannelStream(0);
				stream1 = streamObj['recv'];
			}
			if (undef(stream2)){
				streamObj = getChannelStream(1);
				stream2 = streamObj['recv'];
			}
			if (undef(duration)) duration = 60;
			if (undef(description)){
				description = project.name+' recording stream '+stream1+' and '+stream2+' for '+duration+' min';
			}
			description = cleanDescription(description);
			tcpclient(obj.ip, obj.port,
				asciiToHex('record:0,'+stream1+','+stream1+','+stream2+','+stream2+','+(duration*60)+','+description+'\r'));		
		};

		obj.play = function(file, loop, channel){
			if (undef(channel)) channel = 1;
			if (!loop) loop = 0; else loop = 1;
			tcpclient(obj.ip, obj.port, asciiToHex('play:'+channel+','+file+','+loop+'\r'));
		};
		
		obj.playLatest = function(loop, channel){
			if (undef(channel)) channel = 1;
			if (!loop) loop = 0; else loop = 1;
			var data = ''; if (!port) port = 50002;
			tcpclient(obj.ip, port, asciiToHex('list\r'), function(rdata){
				data += rdata;
			}, {last_line: '*', autoclose: false, timeout: false, onDestroy: function(){
				var tokens = data.split('+');
				for (var i=0; i<tokens.length; i++){
					tokens[i] = tokens[i].split('\r');
					for (var j=0; j<tokens[i].length; j++){
						tokens[i][j] = tokens[i][j].split(':');
					}
				}
				var _tokens = [];
				for (var i=0; i<tokens.length; i++){
					var t = {};
					for (var j=0; j<tokens[i].length; j++){
						if (tokens[i][j][0]) t[tokens[i][j][0]] = tokens[i][j][1];
						switch (tokens[i][j][0]){
							case 'start': t[tokens[i][j][0]] = parseInt(t[tokens[i][j][0]]); break;
						}
					}
					_tokens.push(t);
				}
				_tokens.sort(function(a, b){return a.start - b.start;});
				var file = false;
				for (var i=0; i<tokens[tokens.length-1].length; i++){
					if (tokens[tokens.length-1][i][0] === 'filename') file = tokens[tokens.length-1][i][1];
				}
				if (file) tcpclient(obj.ip, port, asciiToHex('play:'+channel+','+file+','+loop+'\r'));
				else console.log('NVR Error: No file selected for playLatest function');
			}});
		}
		
		obj.stopRecord = function(channel){
			if (obj.dvrmode == "1" || obj.dvrmode == "2" || undef(channel)){
				channel = 0; //-1 will stop all
			}
			channel--;
			tcpclient(obj.ip, obj.port, asciiToHex('recstop:'+channel+'\r'));	
		};
		
		obj.stop = function(channel){
			if (obj.dvrmode == "1" || obj.dvrmode == "2" || undef(channel)){
				channel = 0; //-1 will stop all
			}
			channel--;
			tcpclient(obj.ip, obj.port, asciiToHex('stop:'+channel+'\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.getStatus();
		
		return obj;
		
	}
	