//set execution directory
process.chdir(__dirname); process.chdir('../');


//write process id file for N-Command
var fs = require('fs'); var pid = process.pid;
if (fs.existsSync('/svsi/divasUpdater')) fs.writeFile('/svsi/Volante/webSocketServer.out', pid);

//various default variables
var dev = true, verbose = false, cores = 1, online = false, server = false;
var cleanup_complete = false, discoversock = false;
var websockets = [], global = {};


//error and log file printing
function _str(args){
	var str = ''; for (var i=0; i<args.length; i++){if (typeof args[i] === 'undefined') continue; if (i == 0) str += args[i].toString(); else str += ' '+args[i].toString();}
	return str;
}
function _print(file, str, pre, suf){
	if (typeof pre !== 'string') pre = 'Node: '; if (typeof suf !== 'string') suf = '\n\n';
	if (fs.statSync(file)['size'] / 1000000 < 20.0) fs.appendFile(file, pre+str+suf, function(err){
		if (err) console.log('Websocket Server: Error appending to log file.', err);
	});
	else fs.writeFile(file, pre+str+suf, function(err){
		if (err) console.log('Websocket Server: Error writing to log file.', err);
	});
}
function log(){_print('log/log.txt', _str(arguments));}
function error(){_print('log/error.txt', _str(arguments));}
function jslog(){_print('log/jsconsole.txt', _str(arguments), '', '\n');}


//exit and error listeners
function onExit(immediate){
	if (immediate){
		process.on('exit', function(){}); process.on('beforeExit', function(){}); process.on('SIGINT', function(){});
		process.exit();
	}
	try {
		for (var i=0; i<websockets.length; i++) if (websockets[i].close) websockets[i].close();
		if (discoversock && discoversock.destroy) discoversock.destroy();
		if (server && server.close) server.close();
		onExit(true);
	}
	catch (e){
		onExit(true);
	}
}
process.on('uncaughtException', function(err){
	var str = '';
	if (typeof err === 'object'){
		if (err.stack) str += ('\nStacktrace: ' + err.stack.toString());
	} 
	else str += ('\nError: ' + err.toString() + '\n');
	error(str);
});
process.on('exit', onExit); process.on('beforeExit', onExit); process.on('SIGINT', onExit);


//node modules
var exec = require('child_process').exec;
var binaryjs = require('binaryjs');
var _express = require('express');
var request = require('request');
var cluster = require('cluster');
var _domain = require('domain');
var fstack = require('fstack');
var jstore = require('jstore');
var touch = require('touch');
var glob = require('glob');
var path = require('path');
var udp = require('dgram');
var zip = require('zip');
var tcp = require('net');
var tls = require('tls');
var os = require('os');


//fork process if cores > 1
if (cluster.isMaster) for (var i=1; i<cores; i++) cluster.fork();
cluster.on('online', function(worker){
	log('Websocket worker ' + worker.process.pid + ' online.');
});


//get user's documents directory
var _documents = getUserHome();
if (_documents) _documents = _documents.replace(/\\/g, '/');


//http server and websocket server
var express = _express();
express.get('/:file(*)', function(req, res, next){
	var file = req.params.file; var _path = 'files/' + file; res.download(_path);
});
express.use(_express.static('projects'));
express.use(function(err, req, res, next){
	if (404 == err.status){res.statusCode = 404; res.send('404 File not found.');} 
	else next(err);
});
var httpserver = require('http').createServer(express);
var websocket = require('socket.io')(httpserver);
server = httpserver.listen(3001);


//send and receive binary files through websocket
var binaryserver = new binaryjs.BinaryServer({server: httpserver, path: '/binaryjs'});
binaryserver.on('connection', function(client){
	var domain = _domain.create();
	domain.on('error', function(err){error('Binary Socket Error:', err.stack);});
	domain.run(function(){
		client.sendfile = function(filename, name){
			var file = fs.createReadStream(filename);
			client.send(file, {command: 'download', name: name});
		}
		client.on('error', function(err){
			log('binerr:', err.stack);
			error('Binary Socket Error:', err);
		});
		client.on('stream', function(stream, opts){log('got a stream', opts.command);
			switch (opts.command){
				case 'id':
					for (var i=0; i<websockets.length; i++){
						if (websockets[i].id == opts._id){
							websockets[i].binsocket = client; client.websocket = websockets[i];
							log('<-- binarysocket-connection -->', opts._id);
						}
					}
					break;
				case 'import_buttons':
					var file = fs.createWriteStream('files/buttonlib.zip'); stream.pipe(file);
					stream.on('end', function(){
						file.on('close', function(){
							zip.unzip('files/buttonlib.zip', 'projects/' + opts.puid, function(err){
								fstack.delete('files/buttonlib.zip');
								fs.readFile('projects/' + opts.puid + '/buttonlibrary.svsi', function(err, data){
									if (err) error('Import Buttons Error:', err);
									else {
										var str = data.toString(), obj = json_decode(str); str = str.replace(/projects\/[0-9]+\//g, 'projects/'+opts.puid+'/');
										client.websocket.emit('message', {command: 'import_buttons', data: str});
									}
								});
							});
						});
					});
					break;
				case 'import_templates':
					var file = fs.createWriteStream('files/templatelib.zip'); stream.pipe(file);
					stream.on('end', function(){
						file.on('close', function(){
							zip.unzip('files/templatelib.zip', 'projects/' + opts.puid, function(err){
								fstack.delete('files/templatelib.zip');
								fs.readFile('projects/' + opts.puid + '/templatelibrary.svsi', function(err, data){
									if (err) log('Import Templates Error:', err);
									else {
										var str = data.toString(), obj = json_decode(str); str = str.replace(/projects\/[0-9]+\//g, 'projects/'+opts.puid+'/');
										client.websocket.emit('message', {command: 'import_templates', data: str});
									}
								});
							});
						});
					});
					break;
				case 'import_project':
					var file = fs.createWriteStream('files/' + opts.filename); stream.pipe(file);
					var puid = makePuidSync(false); makeDir('projects/' + puid);
					file.on('close', function(){
						zip.unzip('files/' + opts.filename, 'projects/' + puid, function(err){
							if (err) log('Import Project Error:', err);
							if (fs.existsSync('files/' + opts.filename)) fs.unlinkSync('files/' + opts.filename);
							var name = fs.readFileSync('projects/' + puid + '/name.txt').toString();
							var projectstr = fs.readFileSync('projects/' + puid + '/project.svsi').toString();
							project = json_decode(projectstr);
							var oldid = '';
							if (project){
								if (project.puid) oldid = project.puid;
								if (project.core && project.core.puid) oldid = project.core.puid;
							}
							if (oldid) projectstr = projectstr.replace(new RegExp(oldid, 'g'), puid);
							projectstr = projectstr.replace(/projects\/[0-9]*\//g, 'projects/'+puid+'/');
							fs.writeFileSync('projects/' + puid + '/project.svsi', projectstr);
							client.websocket.emit('message', {command: 'project_imported', puid: puid, name: name});
						});
					});
					break;
				case 'image':
					//log('got an image', opts.__id);
					if (!opts.puid) opts.puid = opts.__id; else opts.puid = parseInt(opts.puid);
					var puid = makePuidSync(opts.puid);
					makeDir('projects/' + puid); makeDir('projects/' + puid + '/images');
					opts.filename = opts.filename.replace(/\(|\)/g, '');
					var file = fs.createWriteStream('projects/' + puid + '/images/' + opts.filename); stream.pipe(file);
					file.on('close', function(){
						client.websocket.emit('message', {command: 'image', message: 'image uploaded', 
							puid: puid, callback_id: opts.callback_id, filename: 'projects/' + puid + '/images/' + opts.filename});
					});
					break;
				case 'module':
					var file = fs.createWriteStream('files/' + opts.filename); stream.pipe(file);
					file.on('close', function(){
						makeDir('js/modules/' + opts.manufacturer);
						fstack.move('files/' + opts.filename, 'js/modules/' + opts.manufacturer + '/' + opts.filename,
							function(err){
								client.websocket.emit('message', {command: 'module', message: 'module uploaded:'+opts.filename, callback_id: opts.callback_id});
							});
					});
					break;
				default: break;
			}
		});
	});
});


//only accepts connections from self 'localhost', ie: from wallcon appCode
var appserver = tcp.createServer(function(socket){
	socket.unref(); //allow program to close if socket is open
	socket.on('data', function(data){
		var str = data.toString();
		if (!str){socket.write('No data received.'); socket.destroy(); return;}
		var opts = json_decode(str);
		if (!opts || !opts.command){socket.write('Invalid Message.'); socket.destroy(); return;}
		switch (opts.command){
			case 'security':
				socket.write('Security sent.'); socket.destroy();
				for (var i=0; i<websockets.length; i++){
					if (websockets[i].__ip == '127.0.0.1' || websockets[i].__ip == 'localhost'){
						websockets[i].emit('message', {command: 'security'});
					}
				}
				break;
			case 'homepage':
				socket.write('Hompage sent.'); socket.destroy();
				for (var i=0; i<websockets.length; i++){
					if (websockets[i].__ip == '127.0.0.1' || websockets[i].__ip == 'localhost'){
						websockets[i].emit('message', {command: 'homepage', url: opts.url});
					}
				}
				break;
			default:
				socket.write('Invalid Command.'); socket.destroy(); return;
		}
	});
});
appserver.listen(50510, 'localhost');


//would accept connections from other devices
var cmdserver = tcp.createServer(function(socket){
	socket.unref(); //allow program to close if socket is open
	socket.on('data', function(data){
		var str = data.toString();
			if (!str){socket.write('No data received.'); socket.destroy(); return;}
		var opts = json_decode(str);
			if (!opts || !opts.command){socket.write('Invalid Message.'); socket.destroy(); return;}
		switch (opts.command){
			case 'tcpclient':
				socket.write('Not Implemented.'); socket.destroy(); return;
			case 'udpclient':
				socket.write('Not Implemented.'); socket.destroy(); return;
			default:
				socket.write('Invalid Command.'); socket.destroy(); return;
		}
	});
});
if (0) cmdserver.listen(50520);


function time(){
	return Math.round(new Date().getTime() / 1000);
}

function millitime(){
	return new Date().getTime();
}


if (0){
	var acount = 0;
	var keepalive = {started: false, last_call: time(), interval: false,
		start: function(){
			keepalive.started = true; log('starting...');
			keepalive.interval = setInterval(function(){
				var locked = true;
				var test1 = bash('xset', ['s', 'noblank'], function(self, data){}, function(self){locked = false;});
				setTimeout(function(){
					if (locked){
						//log('locked...1');
						test1.child.kill('SIGKILL'); 
						var test2 = bash('killall', ['-9', 'Xorg'], function(self, data){}, function(self){log('killed Xorg')});
						
						fs.readFile('xorg_log.txt', function(err, data){
							if (err) return; fs.writeFile('xorg_log.txt', parseInt(data) + 1, function(err){log(err)});
						});
						
						//fs.writeFile('log/crash'+(++acount)+'.txt', '');
						//var readableStream = fs.createReadStream('log/log.txt', {autoClose: true});
						//var writableStream = fs.createWriteStream('log/crash'+(acount)+'.txt');
						//readableStream.pipe(writableStream);
						
						//log('locked...2');
						
					}
					else {
						//log('not locked');
					}
				}, 8000);
			}, 16000);
		},
		stop: function(){if (keepalive.interval !== false) clearInterval(keepalive.interval); keepalive.started = false;}
	};
	keepalive.start();
}

function bash(cmd, args, cb_stdout, cb_end){
	if (!(this instanceof bash)) return new bash(cmd, args, cb_stdout, cb_end);
	var spawn = require('child_process').spawn, child = spawn(cmd, args), self = this; self.exit = 0; // send a cb to set 1 when cmd exits
	child.stdout.on('data', function(data){cb_stdout(self, data)}); child.stdout.on('end', function(){cb_end(self)});
	this.child = child;
}

setTimeout(function(){
	getLocalObject('volume', function(data){
		if (data === '___not_found'){
			exec('amixer set Playback '+20); setLocalObject('volume', 20);
		}
		else exec('amixer set Playback '+data);
	});
}, 3000);

tcppersistents = {};

//websocket connection from web browser
websocket.on('connection', function(socket){

	if (1||dev) log('<-- websocket-connection -->', socket.id);
	
	socket._sockets = []; websockets.push(socket);
	socket.__ip = socket.request.connection.remoteAddress;
	socket.emit('message', {command: 'set_id', websocket_id: socket.id});
	
	socket.on('disconnect', function(){
		if (dev) log('<-- websocket-disconnection -->', socket.id);
		if (verbose) log(socket); removeFromArray(websockets, socket);
		for (var i=socket._sockets.length-1; i>=0; i--) socket._sockets[i].destroy();
	});
	
	socket.on('message', function(opts, callback){
		
        //if (1||(dev && verbose)) log('Message Received: ', opts);
		if (opts.command !== 'tcpclient') log('msg', opts.command);

		if (!opts || !opts.command) return;
		if (typeof callback !== 'function') callback = function(data){log('callback', data);};
	   
		switch (opts.command){
		
			case 'print_file': if (opts.data) log('print: '+opts.data+'\n\n'); break;
			case 'keepalive': if (!keepalive.started) keepalive.start(); keepalive.last_call = time(); break;
			case 'debugging': socket.debugging = !(!!socket.debugging); break;
			case 'devtools': exec('xdotool key ctrl+shift+k'); break;
			//case 'fullscreen': if (socket.__ip === '127.0.0.1' || socket.__ip === 'localhost') exec('xdotool key F11'); break;
			case 'refresh': exec('xdotool key F5'); break;
			case 'shortbeep': exec('aplay /var/www/htdocs/panels/sounds/shortbeep.wav &'); break;
			case 'beep': if (socket.__ip === '127.0.0.1' || socket.__ip === 'localhost') exec('aplay /var/www/htdocs/panels/sounds/beep.wav &'); break;
			case 'beep_volume': exec('amixer set Playback '+opts.volume); setLocalObject('volume', opts.volume); break;
			case 'get_volume': getLocalObject('volume', function(data){
					if (data === '___not_found'){
						exec('amixer set Playback '+20); setLocalObject('volume', 20); callback(20);
					}
					else callback(data);
				});
				break;
			case 'jslog': 
				for (var i=0; i<websockets.length; i++){
					if (websockets[i].debugging) websockets[i].emit('message', {command: 'jslog', args: opts.args});
				}
				break;
			case 'jserror':
				for (var i=0; i<websockets.length; i++){
					if (websockets[i].debugging) websockets[i].emit('message', {command: 'jserror', args: opts.args});
				}
				break;
			case 'getStatus': fs.readFile('/svsi/tmp/stat.txt', {}, function(e, b){if (e) callback(false); else callback(b.toString('ascii'));}); break;
			//case 'jsconsole': fs.readFile('log/jsconsole.txt', {}, function(e, b){if (e) callback(false); else callback(b.toString('ascii'));}); break;
			case 'request':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					if (!opts.request) opts.request = 'get';
					if (opts.url) request[opts.request]({url: opts.url, form: opts.form, formData: opts.form}, function(err, response, data){
						if (!err && response.statusCode == 200){
							callback(data);
						}
						else callback(false);
					});
					else callback(false);
				});
				break;
			case 'dirname':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					callback(process.cwd().replace(/\\/g, '/')+'/');
				});
				break;
			case 'ip_addresses':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					var interfaces = os.networkInterfaces();
					var addresses = [];
					for (var k in interfaces){
						for (var k2 in interfaces[k]){
							var address = interfaces[k][k2];
							if (address.family === 'IPv4' && !address.internal){
								addresses.push(address.address);
							}
						}
					}
					callback(addresses);
				});
				break;
			case 'write_module':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					if (opts.manufacturer && opts.filename && opts.data){
						makeDir('js/modules/' + opts.manufacturer);
						fs.writeFile('js/modules/' + opts.manufacturer + '/' + opts.filename, opts.data, function(err){
							if (err) callback('Error writing file.');
							else callback('Success writing file: ' + opts.filename);
						});
					}
				});
				break;
			case 'discover':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					if (undef(opts.timeout)) opts.timeout = 60;
					discoversock = new udpSocket({ip: '255.255.255.255', port: 50001, 
						websocket: socket, onData: function(data){
							this.websocket.emit('message', {
								command: 'unitlist',
								data: data.toString()
							});
						}, onListen: function(){
							discoversock.setBroadcast(true);
							var bclist = [0x98,0x96,0x14,0x19,0xb5,0xb4,0xb6,0xb7,0xb8,0xb9];
							for (var i=0; i<bclist.length; i++){
								var buffer = new Buffer(10);
								buffer[0] = 0x01; buffer[1] = bclist[i]; buffer[2] = 0xca; buffer[3] = 0xfe; buffer[4] = 0x12;
								buffer[5] = 0x34; buffer[6] = 0xff; buffer[7] = 0xff; buffer[8] = 0xff; buffer[9] = 0xff;
								discoversock.sendbuffer(buffer);
							}
						}
					});
					discoversock.listen(50004);
					setTimeout(function(){
						if (discoversock) discoversock.destroy();
						discoversock = false;
					}, opts.timeout*1000);
				});
				break;
			case 'tcpclose': case 'udpclose':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					for (var i=socket._sockets.length; i--;){
						if ((!undef(opts.sock_id) && socket._sockets[i].sock_id == opts.sock_id) ||
							(socket._sockets[i].ip == opts.ip && socket._sockets[i].port == opts.port)){
							var sock = socket._sockets[i];
							socket.emit('message', {
								command: 'return_data',
								notice: {message: 'Socket connection closed by client.', type: opts.command.substring(0,3),
											ip: sock.ip, port: sock.port, sock_id: sock.sock_id},
								sock_id: socket._sockets[i].sock_id,
								close: true
							});
							sock.destroy();
						}
					}
				});
				break;
			case 'tcplistener':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					opts.websocket = socket;
					var server = new tcpListener(opts);
					socket._sockets.push(server);
				});
				break;
			case 'tcppersistent':
				//console.log('tcppersistent', opts);
				if (undef(opts.noDelay)) opts.noDelay = false; if (undef(opts.allowHalfOpen)) opts.allowHalfOpen = true;
				var tcpsocket = false;
				if (tcppersistents[opts.sock_id]){
					tcpsocket = tcppersistents[opts.sock_id];
					if (opts.message) tcpsocket.write(asciiToHex(opts.message).replace(/ /g, ''), 'hex');
				}
				else {
					tcpsocket = tcppersistents[opts.sock_id] = tcp.Socket({allowHalfOpen: opts.allowHalfOpen});
					if (!tcpsocket) break;
					tcpsocket.on('data',  function(data){
						//console.log('streamdata', data.toString('ascii'));
						var str = data.toString('ascii'); socket.emit('message', {command: 'return_data', data: str, sock_id: opts.sock_id, close: false});
					});
					tcpsocket.on('error', function(err){
						var code = err.hasOwnProperty('errno') ? err.errno : err.code; var help = getErrorHelp(code);
						//console.log('streamerr', code, help);
						socket.emit('message', {
							command: 'return_data', error: {
								error: {code: err.code, errno: err.errno, syscall: err.syscall, type: 'tcp', help: help},
								ip: opts.ip, port: opts.port, sock_id: opts.sock_id
							},
							sock_id: opts.sock_id, close: true
						});
						tcpsocket.destroy();
					});
					tcpsocket.connect(opts.port, opts.ip, function(data){//console.log('tessssst connecting!!!!!');
						if (opts.noDelay) tcpsocket.setNoDelay(true);
						if (opts.message) tcpsocket.write(asciiToHex(opts.message).replace(/ /g, ''), 'hex');
					});
				}
				break;
			case 'tcppersistentclose':
				if (tcppersistents[opts.sock_id]){
					socket.emit('message', {command: 'return_data', sock_id: opts.sock_id, close: true});
					tcppersistents[opts.sock_id].destroy(); tcppersistents[opts.sock_id] = false;
				}
				break;
			case 'tcpclient': case 'tcpsend': case 'tcpopen':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					if (opts.command === 'tcpopen'){opts.autoclose = false; opts.timeout = false;}
					for (var i=0; i<socket._sockets.length; i++){
						if ((!undef(opts.sock_id) && socket._sockets[i].sock_id == opts.sock_id) ||
							(socket._sockets[i].ip == opts.ip && socket._sockets[i].port == opts.port &&
								socket._sockets[i].type == 'tcp')){
							if (!undef(opts.autoclose)) socket._sockets[i].autoclose = opts.autoclose;
							if (!undef(opts.message)) socket._sockets[i].send(opts);
							return;
						}
					}
					opts.websocket = socket;
					var sock = new tcpSocket(opts);
					if (!undef(opts.message)) sock.send(opts);
					sock.open();
					socket._sockets.push(sock);
				});
				break;
			case 'udpclient': case 'udpsend': case 'udpopen': case 'udplistener':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					if (opts.command === 'udpopen' || opts.command === 'udplistener'){
						opts.autoclose = false; opts.timeout = false;
					}
					if (opts.command === 'udplistener'){
						opts.ip = '0.0.0.0'; opts.listen = opts.port;
						opts.onData = function(data, info){
							if (dev) log('udp received: ', data.toString());
							this.websocket.emit('message', { 
								command: 'return_data', data: {data: data.toString(), info: info},
								sock_id: this.sock_id, close: false
							});
						};
					}
					else {
						for (var i=0; i<socket._sockets.length; i++){
							if ((!undef(opts.sock_id) && socket._sockets[i].sock_id == opts.sock_id) ||
								(socket._sockets[i].ip == opts.ip && socket._sockets[i].port == opts.port &&
									socket._sockets[i].type == 'udp')){
								if (!undef(opts.autoclose)) socket._sockets[i].autoclose = opts.autoclose;
								if (!undef(opts.message)) socket._sockets[i].send(opts);
								return;
							}
						}
					}
					opts.websocket = socket;
					var sock = new udpSocket(opts);
					if (!undef(opts.listen)) sock.listen(opts.listen);
					if (!undef(opts.message)) sock.send(opts);
					socket._sockets.push(sock);
				});
				break;
			case 'export_project':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					makeDir('files');
					zip.zip('projects/' + opts.id + '/', 'files/' + opts.id + '.zip', function(err){
						socket.binsocket.sendfile('files/' + opts.id + '.zip', opts.id + '.zip');
					});
				});
				break;
			case 'set_project_var':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					callback(setProjectVar(opts.id, opts.name, opts.data));
				});
				break;
			case 'get_project_var':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					callback(getProjectVar(opts.id, opts.name));
				});
				break;
			case 'set_var':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					//callback(setLocalObject(opts.name, opts.data));
					setLocalObject(opts.name, opts.data, callback);
				});
				break;
			case 'get_var':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					//callback(getLocalObject(opts.name));
					getLocalObject(opts.name, callback);
				});
				break;
			case 'delete_module':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					fs.unlink('modules/' + opts.manufacturer + '/' + opts.filename);
				});
				break;
			case 'duplicate_project':
				log('DUPLICATING PROJECT');
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					makePuid(false, function(puid){
						opts.data = opts.data.replace(new RegExp(opts.puid, 'g'), puid);
						makeDir('projects/' + puid); makeDir('projects/' + puid + '/images');
						fs.writeFile('projects/' + puid + '/project.svsi', opts.data);
						fs.writeFile('projects/' + puid + '/name.txt', opts.name);
						fstack.copy('projects/'+opts.puid+'/images', 'projects/'+puid+'/images', function(err){
							log('fstack copied'); callback({name: opts.name, puid: puid});
						});
					});
				});
				break;
			case 'write_project':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					makePuid(opts.puid, function(puid){
						//var newdata = opts.data.replace(/projects\/[0-9]*\//g, 'projects/'+puid+'/');
						//var newdata = opts.data.replace(/projects\/[0-9]*\//g, 'projects/'+puid+'/');
						var newdata = opts.data;
						makeDir('projects/' + puid); makeDir('projects/' + puid + '/images');
						var temp = json_decode(newdata); temp.core.puid = puid; newdata = json_encode(temp);
						fs.writeFile('projects/' + puid + '/project.svsi', newdata);
						fs.writeFile('projects/' + puid + '/name.txt', opts.name);
						callback({name: opts.name, puid: puid});
					});
				});
				break;
			case 'check_projectname':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					callback('0');
				});
				break;
			case 'delete_image':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					fs.unlink('projects/' + opts.id + '/images/' + opts.filename);
				});
				break;
			case 'read_image_filenames':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					if (undef(opts.puid)) callback(false);		
					else scanDir('projects/' + opts.puid + '/images', function(files){
						callback(json_encode(files));
					});
				});
				break;
			case 'get_disk_filesize':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					callback(0); return; dir.getSize(path, function(err, size){
						if (err) callback(0);
						else callback(size);
					});
				});
				break;
			case 'read_project':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack); callback(false);});
				domain.run(function(){
					var running = 0;
					var interval = setInterval(function(){ //try 5 times
						if (running >= 5){clearInterval(interval); callback(false); return;}
						try {
							if (undef(opts.puid)){callback('no_puid'); return;}
							var tstr = ''; var str = fs.readFileSync('projects/' + opts.puid + '/project.svsi').toString();
							touch('projects/' + opts.puid + '/name.txt', {time: opts.time});
							str = str.replace(/projects\/[0-9]*\//g, 'projects/'+opts.puid+'/');
							callback(str); running = 5;
						}
						catch (e){running++;};
					}, 500);
				});
				break;
			case 'read_allmodules':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					scanDir('js/modules', function(manufacturers, err){
						if (!manufacturers.length){callback('[]'); return;}
						var ncap = new capsule(manufacturers.length, function(modules){
							var output = [];
							for (var i=0; i<modules.length; i++) for (var j=0; j<modules[i].length; j++) output.push(modules[i][j]);
							callback(json_encode(output));
						});
						for (var i=0; i<manufacturers.length; i++){
							scanDir('js/modules/' + manufacturers[i], function(files){
								var modules = [];
								for (var j=0; j<files.length; j++){
									var module = {
										manufacturer: manufacturers[this], make: files[j].replace('.js', ''),
										file: 'js/modules/' + manufacturers[this] + '/' + files[j], path: ''
									};
									modules.push(module);
								}
								ncap.push(modules);
							}.bind(i));
						}
					});
				});
				break;
			case 'export_buttons':
				var t = 1;
				log('export_buttons', t++);
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					if (opts.puid) opts.id = opts.puid; if (!opts.id) return;
					if (!socket.binsocket) return; var data = opts.data; if (!data) return;		
					fs.writeFileSync('projects/' + opts.id + '/buttonlibrary.svsi', json_encode(data));
					zip.multizip(['projects/' + opts.id + '/buttonlibrary.svsi', 'projects/' + opts.id + '/images'], 'files/buttonlib.zip', function(err){
						socket.binsocket.sendfile('files/buttonlib.zip', 'buttonlib.zip');
					});
				});
				break;
			case 'export_templates':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					if (opts.puid) opts.id = opts.puid; if (!opts.id) return;
					if (!socket.binsocket) return; var data = opts.data; if (!data) return;
					fs.writeFileSync('projects/' + opts.id + '/templatelibrary.svsi', json_encode(data));
					zip.multizip(['projects/' + opts.id + '/templatelibrary.svsi', 'projects/' + opts.id + '/images'], 'files/templatelib.zip', function(err){
						socket.binsocket.sendfile('files/templatelib.zip', 'templatelib.zip');
					});
				});
				break;
			case 'read_allmanufacturers':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					scanDir('js/modules', function(manufacturers){
						callback(json_encode(manufacturers));
					});
				});
				break;
			case 'delete_project':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					if (opts.puid) fstack.delete('projects/' + opts.puid, function(err){
						if (err) callback(false); else callback(true);
					});
				});
				break;
			case 'read_projectnames':
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					getProjectList(function(projects){
						callback(json_encode(projects));
					});
				});
				break;
			default:
				var domain = _domain.create();
				domain.on('error', function(err){error('onMessage "'+opts.command+'" Error:', opts, err.stack);});
				domain.run(function(){
					callback('unknown command');
				});
				break;
		}
	});
	
});


//tcp socket class
function tcpSocket(opts){

	if (undef(opts) || undef(opts.ip) || undef(opts.port)) return;
	
	var self = this;
	var allowHalfOpen = opts.hasOwnProperty('allowHalfOpen') ? opts.allowHalfOpen : false;

	this.ip = opts.ip;
	this.port = opts.port;
	this.sock_id = opts.sock_id;
	this.type = 'tcp';
	this.secure = !undef(opts.secure) ? opts.secure : false;
	
	this.websocket = opts.hasOwnProperty('websocket') ? opts.websocket : false;
	this.autoclose = opts.hasOwnProperty('autoclose') ? opts.autoclose : false;
	this.blocking = opts.hasOwnProperty('blocking') ? opts.blocking : true;
	this.timeout = opts.hasOwnProperty('timeout') ? opts.timeout : false;
	this.nodelay = opts.hasOwnProperty('nodelay') ? opts.nodelay : false;
	this.last_line = opts.hasOwnProperty('last_line') ? opts.last_line : false;
	this.EOM = opts.hasOwnProperty('EOM') ? opts.EOM : false;
	
	this._timeout = false;
	
	this.blocked = true;
	this.queued_msgs = [];
	this._ignore = 0;
	this.chunked_data = '';
	
	this.onConnect = opts.hasOwnProperty('onConnect') ? opts.onConnect : function(){
		self.blocked = false;
		if (self.nodelay) self.socket.setNoDelay(true);
		self.send(false, true);
	};
	this.onData = opts.hasOwnProperty('onData') ? opts.onData : function(data){
	
		var str = data.toString('ascii'); //log('data', str);
	
		if (0&&dev) log('tcp received: '+str);
		
		if (self.last_line || self.EOM) self.chunked_data += str;
		
		if (!self.queued_msgs[0]){
			var close = false;
			if (self.EOM && str.indexOf(self.EOM) !== -1){
				close = true;
			}
			if (self.last_line){
				var t = self.chunked_data.split('\r');
				for (var i=0; i<t.length; i++) if (t[i] == self.last_line) close = true;
			}
			if (close){
				self.websocket.emit('message', {
					command: 'return_data', data: self.chunked_data,
					sock_id: self.sock_id, close: false
				});
				self.chunked_data = '';
				self.destroy();
			}
			return;
		}
		
		var sock_id = self.queued_msgs[0].sock_id;
		if (self.sock_id == sock_id) var close = false;
		else var close = true;

		if (self._ignore === 0) self.websocket.emit('message', { 
			command: 'return_data', data: str,
			sock_id: sock_id, close: close
		});
		if (self._ignore > 0) self._ignore--;
		
		self.blocked = false;
		
		self.queued_msgs.shift();
		if (self.queued_msgs[0]){
			if (self.queued_msgs[0].timeout !== false) 
				self.setTimeout(self.queued_msgs[0].timeout);
			self.send(self.queued_msgs[0], true);
		}
		else if (self.autoclose) self.destroy();
	};
	this.onError = opts.hasOwnProperty('onError') ? opts.onError : function(err){ log(err);
		if (verbose) error(err, self.ip, self.port);
		var code = err.hasOwnProperty('errno') ? err.errno : err.code;
		var help = getErrorHelp(code);
		if (self.websocket) self.websocket.emit('message', {
			command: 'return_data', error: {
				error: {code: err.code, errno: err.errno, syscall: err.syscall, type: 'tcp', help: help},
				ip: self.ip, port: self.port, sock_id: self.sock_id
			},
			sock_id: self.sock_id, close: true
		});
		self.destroy();
	};
	
	if (!this.secure){
		this.socket = tcp.Socket({allowHalfOpen: allowHalfOpen});
		this.socket.on('data', this.onData);
		this.socket.on('error', this.onError);
		this.socket.parent = this;
	}

}
tcpSocket.prototype = {
	open: function(){
		if (!this.secure) this.socket.connect(this.port, this.ip, this.onConnect);
		else {
			this.socket = tls.connect(this.port, this.ip, {port: this.port, host: this.ip, rejectUnauthorized: false}, this.onConnect);
			this.socket.on('data', this.onData);
			this.socket.on('error', this.onError);
			this.socket.parent = this;
		}
	},
	send: function(msg, resending){
		if (!msg && this.queued_msgs[0]) msg = this.queued_msgs[0];
		if (typeof msg !== 'object') msg = {};
		
		var message = msg.hasOwnProperty('message') ? msg.message : '';
		if (msg.onData) this.onData = msg.onData;
		
		if (!resending) this.queued_msgs.push(msg);
		
		if (!this.blocked){
			this.blocked = true;
			this.socket.write(asciiToHex(message).replace(/ /g, ''), 'hex');
			if (msg.timeout !== false) this.setTimeout(msg.timeout);
		}
	},
	pause: function(){
		this.socket.pause();
	},
	resume: function(){
		this.socket.resume();
	},
	end: function(){
		this.destroy();
	},
	close: function(){
		this.destroy();
	},
	destroy: function(){
		this.socket.destroy();
		if (this.websocket){
			this.websocket.emit('message', {
				command: 'return_data', close: true, sock_id: this.sock_id
			});
			for (var i=0; i<this.queued_msgs.length; i++){
				if (this.sock_id != this.queued_msgs[i].sock_id) 
					this.websocket.emit('message', {
						command: 'return_data', close: true, sock_id: this.queued_msgs[i].sock_id
					});
			}
		}
		if (this._timeout !== false){
			clearTimeout(this._timeout);
			this._timeout = false;
		}
		if (this.websocket) removeFromArray(this.websocket._sockets, this);
	},
	setTimeout: function(newtimeout){
		if (this._timeout !== false) clearTimeout(this._timeout);
		this._timeout = false;
		if (!undef(newtimeout)){
			this.timeout = newtimeout;
			this._timeout = setTimeout(function(){
				if (this.queued_msgs[0] && this.queued_msgs[0].expect_response) this._ignore++;
				if (this.queued_msgs.length > 1){
					this.websocket.emit('message', {
						command: 'return_data',
						warning: {message: 'Socket connection timed out.', type: 'tcp', 
							ip: this.queued_msgs[0].ip, port: this.queued_msgs[0].port, 
							sock_id: this.queued_msgs[0].sock_id},
						sock_id: this.queued_msgs[0].sock_id,
						close: true
					});
					this.blocked = false;
					this.queued_msgs.shift();
					if (this.queued_msgs[0]) this.send(this.queued_msgs[0], true);
				}
				else {
					this.websocket.emit('message', {
						command: 'return_data',
						warning: {message: 'Socket connection timed out.', type: 'tcp', 
							ip: this.ip, port: this.port, sock_id: this.sock_id},
						sock_id: this.sock_id,
						close: true
					});
					this.destroy();
				}
			}.bind(this), this.timeout);
		}
	}
}


//udp socket class
function udpSocket(opts){

	if (undef(opts) || undef(opts.ip) || undef(opts.port)) return;
	
	var self = this;
	
	this.ip = opts.ip;
	this.port = opts.port;
	this.sock_id = opts.sock_id;
	this.type = 'udp';
	
	this.socket = udp.createSocket("udp4");
	this.socket.unref();
	
	this.websocket = opts.hasOwnProperty('websocket') ? opts.websocket : false;
	this.broadcast = opts.hasOwnProperty('broadcast') ? opts.broadcast : false;
	this.multicast = opts.hasOwnProperty('multicast') ? opts.multicast : false;
	this.autoclose = opts.hasOwnProperty('autoclose') ? opts.autoclose : false;
	this.blocking = opts.hasOwnProperty('blocking') ? opts.blocking : true;
	this.timeout = opts.hasOwnProperty('timeout') ? opts.timeout : false;
	
	this._timeout = false;
	
	this.blocked = false;
	this.queued_msgs = [];
	this._ignore = 0;
	
	this.onListen = opts.hasOwnProperty('onListen') ? opts.onListen : false;
	this.onData = opts.hasOwnProperty('onData') ? opts.onData.bind(this) : function(data){
		if (dev) log('udp received: ', data.toString());
		
		if (!self.queued_msgs[0]) return;
		var sock_id = self.queued_msgs[0].sock_id;
		if (self.sock_id == sock_id) var close = false;
		else var close = true;

		if (self._ignore === 0) self.websocket.emit('message', { 
			command: 'return_data', data: data.toString('ascii'),
			sock_id: sock_id, close: close
		});
		if (self._ignore > 0) self._ignore--;
		
		self.blocked = false;
		self.queued_msgs.shift();
		if (self.queued_msgs[0]){
			if (self.queued_msgs[0].timeout !== false) 
				self.setTimeout(self.queued_msgs[0].timeout);
			self.send(self.queued_msgs[0], true);
		}
		else if (self.autoclose) self.destroy();
	};
	this.onError = opts.hasOwnProperty('onError') ? opts.onError : function(err){
		error(err, self.ip, self.port);
		var code = err.hasOwnProperty('errno') ? err.errno : err.code;
		var help = getErrorHelp(code);
		if (self.websocket) self.websocket.emit('message', {
			command: 'return_data', error: {error: {code: err.code, errno: err.errno,
				syscall: err.syscall, type: 'udp', help: help},
				ip: self.ip, port: self.port, sock_id: self.sock_id},
				sock_id: self.sock_id, close: true
		});
		self.destroy();
	};
	
	this.socket.on('error', this.onError);
	if (this.broadcast) this.setBroadcast(true);
	
}
udpSocket.prototype = {
	send: function(msg, resending){
		if (!msg && this.queued_msgs[0]) msg = this.queued_msgs[0];
		if (typeof msg !== 'object') msg = {};
		
		var message = msg.hasOwnProperty('message') ? msg.message : '';
		if (msg.onData){
			this.onData = msg.onData;
			this.socket.on('message', this.onData);
		}
		
		if (!resending){
			this.queued_msgs.push(msg);
			if (!msg.expect_response) this._ignore++;
		}

		if (!this.blocked){
			this.blocked = true;
			var buffer = new Buffer(asciiToHex(message).replace(/ /g,''), 'hex');
			this.socket.send(buffer, 0, buffer.length, this.port, this.ip, function(err, bytes){
				if (err && this.autoclose) this.destroy();
			}.bind(this));
			if (msg.timeout !== false) this.setTimeout(msg.timeout);
		}
	},
	sendbuffer: function(data){
		var self = this;
		var message = new Buffer(data);
		this.socket.send(message, 0, message.length, this.port, this.ip, function(err, bytes){
			if (err && self.autoclose) self.close();
		});
	},
	listen: function(port){
		if (this.onListen) this.socket.on('listening', this.onListen);
		this.socket.bind(port);
		this.socket.on('message', this.onData);
	},
	setBroadcast: function(flag){
		this.socket.setBroadcast(flag);
	},
	end: function(){
		this.destroy();
	},
	close: function(){
		this.destroy();
	},
	destroy: function(){
		this.socket.close();
		if (this.websocket){
			this.websocket.emit('message', {
				command: 'return_data', close: true, sock_id: this.sock_id
			});
			for (var i=0; i<this.queued_msgs.length; i++){
				if (this.sock_id != this.queued_msgs[i].sock_id) 
					this.websocket.emit('message', {
						command: 'return_data', close: true, sock_id: this.queued_msgs[i].sock_id
					});
			}
		}
		if (this._timeout !== false){
			clearTimeout(this._timeout);
			this._timeout = false;
		}
		if (this.websocket) removeFromArray(this.websocket._sockets, this);
	},
	setTimeout: function(newtimeout){
		if (this._timeout !== false) clearTimeout(this._timeout);
		this._timeout = false;
		if (!undef(newtimeout)){
			this.timeout = newtimeout;
			this._timeout = setTimeout(function(){
				if (this.queued_msgs.length > 1){
					this.websocket.emit('message', {
						command: 'return_data',
						warning: {message: 'Socket connection timed out.', type: 'udp', 
							ip: this.queued_msgs[0].ip, port: this.queued_msgs[0].port, 
							sock_id: this.queued_msgs[0].sock_id},
						sock_id: this.queued_msgs[0].sock_id,
						close: true
					});
					this.blocked = false;
					this.queued_msgs.shift();
					if (this.queued_msgs[0]) this.send(this.queued_msgs[0], true);
				}
				else {
					this.websocket.emit('message', {
						command: 'return_data',
						warning: {message: 'Socket connection timed out.', type: 'udp', 
							ip: this.ip, port: this.port, sock_id: this.sock_id},
						sock_id: this.sock_id,
						close: true
					});
					this.destroy();
				}
			}.bind(this), this.timeout);
		}
	}
}


//tcp listener class
function tcpListener(opts){

	if (undef(opts) || undef(opts.port)) return;
	
	var self = this;
	
	this.websocket = opts.hasOwnProperty('websocket') ? opts.websocket : false;
	
	var onEnd = opts.hasOwnProperty('onEnd') ? opts.onEnd : noop;
	var onBind = opts.hasOwnProperty('onBind') ? opts.onBind : noop;
	var onConnection = opts.hasOwnProperty('onConnection') ? opts.onConnection : function(socket){
		socket.on('data', function(data){
			self.websocket.emit('message', { 
				command: 'return_data', data: {data: data.toString('ascii'), info: {
					localPort: socket.localPort,
					localAddress: socket.localAddress,
					remotePort: socket.remotePort,
					remoteAddress: socket.remoteAddress,
					bytesRead: socket.bytesRead,
					bytesWritten: socket.bytesWritten
				}},
				sock_id: self.sock_id, close: false
			});
			socket.destroy();
		});
		socket.on('close', function(){console.log("!CLOSED")});
		socket.on('error', function(){console.log("!ERROR")});
		//self.server.sockets.push(socket);
	};
	
	this.port = opts.port;
	this.sock_id = opts.sock_id;
	this.server = tcp.createServer(onConnection);
	
	this.server.listen(opts.port, onBind);
	this.server.parent = this;
	this.server.sockets = [];
	
	//log(this.server);
	
}
tcpListener.prototype = {
	close: function(callback){
		this.server.close(callback);
		removeFromArray(this.websocket._sockets, this);
	},
	destroy: function(callback){
		this.server.close(callback);
		removeFromArray(this.websocket._sockets, this);
	}
}


//utility functions
function makeDir(dir){
	if (!fs.existsSync(dir)) fs.mkdirSync(dir);
}

function scanDir(dir, onComplete, prefix){
	if (undef(prefix)) var prefix = '';
	glob("*", {cwd: prefix+dir}, function(err, files){
		onComplete(files, err);
	});
}

function scanDirSync(dir, prefix){
	if (undef(prefix)) var prefix = '';
	return glob.sync("*", {cwd: prefix+dir});
}

function getProjectList(callback){
	scanDir('projects', function(projects, err){
		if (!projects || !projects.length || err){callback([]); return;}
		var results = [];
		var temp = [];
		for (var i=0; i<projects.length; i++){
			var file = 'projects/'+projects[i]+'/name.txt';
			if (fs.existsSync(file)) results.push({name: fs.readFileSync(file).toString(), 
				puid: projects[i], fromDB: false, mtime: fs.statSync(file).mtime.getTime()});
			else fstack.delete('projects/'+projects[i], function(err){});
		}
		results.sort(function(a, b){return b.mtime - a.mtime;});
		callback(results);
	});
}

function makePuid(puid, callback){
	if (undef(puid) || puid == false) var puid = parseInt(Math.random()*10000000000);
	scanDir('/projects', function(projects){
		var match = true;
		while (match){
			match = false;
			for (var i=0; i<projects.length; i++){
				if (projects[i] == puid){
					puid = parseInt(Math.random()*10000000000);
					match = true;
				}
			}
		}
		callback(puid);
	});
}

function makePuidSync(puid){
	if (undef(puid) || puid === false) puid = parseInt(Math.random()*10000000000);
	else return parseInt(puid);
	var projects = scanDirSync('/projects');
	var match = true;
	while (match){
		match = false;
		for (var i=0; i<projects.length; i++){
			if (projects[i] == puid){
				puid = parseInt(Math.random()*10000000000);
				match = true;
			}
		}
	}
	return puid;
}

function capsule(total, callback){
	var self = this;
	this.total = total;
	this.callback = callback;
	this.finished = 0;
	this.results = [];
	this.push = function(obj){
		self.finished++;
		self.results.push(obj);
		if (self.finished == self.total) self.callback(self.results);
	}
}

function getUserHome(){
	return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
}

function noop(){
	//no operation :)
}

function undef(obj){
	return (typeof obj === 'undefined');
}

function removeFromArray(array, item){
    for (var i in array){
        if (array[i]==item){
            array.splice(i,1);
            break;
        }
    }
}

function asciiToHex(str){
	str = str.toString();
	var arr = [];
	for (var i = 0, l = str.length; i < l; i ++){
		var hex = Number(str.charCodeAt(i)).toString(16);
		arr.push(hex.length > 1 && hex || "0" + hex);
	}
	return arr.join(' ');
}

function hexToAscii(hex){
	var _hex = hex.toString();
	var str = '';
	for (var i = 0; i < _hex.length; i += 2)
		str += String.fromCharCode(parseInt(_hex.substr(i, 2), 16));
	return str;
}

function getMachineAddresses(){
	var interfaces = os.networkInterfaces();
	var addresses = [];
	for (var k in interfaces){
		for (var k2 in interfaces[k]){
			var address = interfaces[k][k2];
			if (address.family === 'IPv4' && !address.internal){
				addresses.push(address.address);
			}
		}
	}
	return addresses;
}

function setLocalObject(name, data, callback){
	if (!callback) callback = noop;
	storage.init('websocket/localData').set(name, data, function(err){if (err) callback(false); else callback(true);});
	/*var file = 'websocket/localData';
	try {var _data = fs.readFileSync(file, 'utf8');}
	catch(err){
		if (err.code == 'ENOENT'){var _data = '';}
	}
	var obj = json_decode(_data);
	if (data){
		obj[name] = data;
		fs.writeFile(file, json_encode(obj), {mode: 0664});
		return true;
	}
	return false;*/
}

function getLocalObject(name, callback){
	if (!callback) callback = noop;
	storage.init('websocket/localData').get(name, function(r){callback(r);});
	/*var file = 'websocket/localData';
	try {var data = fs.readFileSync(file, 'utf8');}
	catch(err){
		if (err.code == 'ENOENT'){var data = '';}
	}
	var obj = json_decode(data);
	if (obj[name]) return obj[name];
	else return 'Server: Error reading object.';*/
}

function setProjectVar(id, name, data){
	if (undef(id) || undef(data)) return;
	var file = 'projects/'+id+'/vars.svsi';
	try {var _data = fs.readFileSync(file, 'utf8');}
	catch(err){
		if (err.code == 'ENOENT'){var _data = '';}
	}
	var obj = json_decode(_data);
	if (data){
		obj[name] = data;
		fs.writeFile(file, json_encode(obj), {mode: 0664});
		return true;
	}
	return false;
}

function getProjectVar(id, name){
	if (undef(id) || undef(name)) return;
	var file = 'projects/'+id+'/vars.svsi';	
	try {var data = fs.readFileSync(file, 'utf8');}
	catch(err){
		if (err.code == 'ENOENT'){var data = '';}
	}
	var obj = json_decode(data);
	if (obj[name]) return obj[name];
	else return 'Server: Error reading object.';
}

var ringbuffer = function(size, init){
	if (!(this instanceof ringbuffer)) return new ringbuffer(size, init);
	var self = []; if (size) self.size = size; else self.size = 0;
	self.push = function(){
		var len = Array.prototype.push.apply(self, arguments);
		self.splice(0, self.length - self.size);
		if (len >= 0 && len <= self.size) return len;
		else return self.size;
	}
	if (init) for (var i=0; i<init.length; i++) self.push(init[i]);
	return self;
};

var waitfor = function(cond, func, delay){
	if (!delay) delay = 300; if (cond()) func(); else setTimeout(function(){waitfor(cond, func, delay);}, delay);
};

var storage = {
	queue: ringbuffer(500), locked: {},
	create: function(path, obj, callback){if (undef(obj)) obj = {}; if (typeof obj === 'function'){callback = obj; obj = {};} jstore.write(path, obj, callback);},
	init: function(path, opts){
		if (!opts) opts = {};
		if (!opts.passwords) return new function(){
			var self = this, queue = storage.queue;
			var check = function(){
				if (queue.length) waitfor(function(){return !storage.locked[path];}, function(){var q = queue.shift(); q.exec();});
			};
			this.get = function(name, callback){
				if (undef(name)){callback('___undefined_invalid'); check(); return;}
				if (storage.locked[path]){queue.push({exec: self.get.bind(null, name, callback)}); return;} else storage.locked[path] = true;
				jstore.read(path, function(err, obj){
					if (!callback) callback = noop; if (err || !obj || !obj.length) obj = {}; else obj = obj[0];
					if (name instanceof Array && obj[name[0]] && name.length === 1) name = name[0];
					if (name instanceof Array && obj[name[0]] && name.length > 1){
						var temp = obj[name[0]], error = false;
						for (var i=1; i<name.length-1; i++){if (temp[name[i]]) temp = temp[name[i]]; else {error = true; break;}}
						if (!error){storage.locked[path] = false; callback(temp[name[name.length-1]]); check(); return;}
					}
					else if (typeof obj[name] !== 'undefined'){storage.locked[path] = false; callback(obj[name]); check();} 
					else {storage.locked[path] = false; callback('___not_found'); check();}
				});
			};
			this.set = function(name, _obj, callback){
				if (undef(name) || undef(_obj)){callback('___undefined_invalid'); check(); return;}
				if (storage.locked[path]){queue.push({exec: self.set.bind(null, name, _obj, callback)}); return;} else storage.locked[path] = true;
				jstore.read(path, function(err, obj){
					if (!callback) callback = noop; 
					if (err || !obj || !obj.length){obj = {};} else obj = obj[0];
					if (name instanceof Array && obj[name[0]] && name.length === 1) name = name[0];
					if (name instanceof Array && obj[name[0]] && name.length > 1){
						var temp = obj[name[0]], error = false;
						for (var i=1; i<name.length-1; i++){if (temp[name[i]]) temp = temp[name[i]]; else {error = true; break;}}
						if (!error) temp[name[name.length-1]] = _obj; else {storage.locked[path] = false; callback('___not_found'); check(); return;}
					}
					else obj[name] = _obj;
					jstore.write(path, obj, function(err){storage.locked[path] = false; callback(err); check();});
				});
			};
			this.remove = function(name, callback){
				if (undef(name)){callback('___not_found'); check(); return;}
				if (storage.locked[path]){queue.push({exec: self.remove.bind(null, name, callback)}); return;} else storage.locked[path] = true;
				jstore.read(path, function(err, obj){
					if (!callback) callback = noop; if (err || !obj || !obj.length) obj = {}; else obj = obj[0];
					if (name instanceof Array && obj[name[0]] && name.length === 1) name = name[0];
					if (name instanceof Array && obj[name[0]] && name.length > 1){
						var temp = obj[name[0]], error = false;
						for (var i=1; i<name.length-1; i++){if (temp[name[i]]) temp = temp[name[i]]; else {error = true; break;}}
						if (!error) delete temp[name[name.length-1]];
					}
					else delete obj[name]; 
					jstore.write(path, obj, function(err){storage.locked[path] = false; callback(err); check();});
				});
			};
		};
		else return new function(){
			var self = this, queue = storage.queue;
			var check = function(){
				if (queue.length) waitfor(function(){return !storage.locked[path];}, function(){var q = queue.shift(); q.exec();});
			};
			this.get = function(name, password, callback){
				if (undef(name)){callback('___undefined_invalid'); check(); return;}
				if (storage.locked[path]){queue.push({exec: self.get.bind(null, name, password, callback)}); return;} else storage.locked[path] = true;
				if (typeof password === 'function'){callback = password; password = false} if (!callback) callback = noop;
				jstore.read(path, function(err, obj){
					if (err || !obj || !obj.length) obj = {}; else obj = obj[0];
					if (name instanceof Array && obj[name[0]] && name.length === 1) name = name[0];
					if (name instanceof Array && obj[name[0]] && name.length > 1){
						if (obj[name[0]].password){
							if (opts.admin || password === obj[name[0]].password){
								var temp = obj[name[0]].data, error = false;
								for (var i=1; i<name.length-1; i++){if (temp[name[i]]) temp = temp[name[i]]; else {error = true; break;}}
								if (!error){storage.locked[path] = false; callback(temp[name[name.length-1]]); check(); return;}
							}
							else {storage.locked[path] = false; callback('___invalid_password'); check();}
						}
					}
					else if (typeof obj[name] !== 'undefined'){
						if (obj[name].password){
							if (opts.admin || password === obj[name].password){storage.locked[path] = false; callback(obj[name].data); check();}
							else {storage.locked[path] = false; callback('___invalid_password'); check();}
						} 
						else {storage.locked[path] = false; callback(obj[name].data); check();}
					}
					else {storage.locked[path] = false; callback('___not_found'); check();}
				});
			};
			this.set = function(name, _obj, password, callback){
				if (undef(name) || undef(_obj)){callback('___undefined_invalid'); check(); return;}
				if (storage.locked[path]){queue.push({exec: self.set.bind(null, name, _obj, password, callback)}); return;} else storage.locked[path] = true;
				if (typeof password === 'function'){callback = password; password = false} if (!callback) callback = noop;
				jstore.read(path, function(err, obj){
					if (err || !obj || !obj.length) obj = {}; else obj = obj[0];
					if (name instanceof Array && obj[name[0]] && name.length === 1) name = name[0];
					if (name instanceof Array && obj[name[0]] && name.length > 1){
						if (obj[name[0]].password){
							if (opts.admin || password === obj[name[0]].password){
								var temp = obj[name[0]].data, error = false;
								for (var i=1; i<name.length-1; i++){if (temp[name[i]]) temp = temp[name[i]]; else {error = true; break;}}
								if (!error) temp[name[name.length-1]] = _obj; else {storage.locked[path] = false; callback('___not_found'); check(); return;}
							}
							else {storage.locked[path] = false; callback('___invalid_password'); check(); return;}
						}
					}
					else if (obj[name]){
						if (obj[name].password){
							if (opts.admin || password === obj[name].password) obj[name] = {password: password, data: _obj};
							else {storage.locked[path] = false; callback('___invalid_password'); check(); return;}
						}
						else obj[name] = {password: false, data: _obj};
					}
					else {if (password) obj[name] = {password: password, data: _obj}; else obj[name] = {password: false, data: _obj};}
					jstore.write(path, obj, function(err){storage.locked[path] = false; callback(err); check();});
				});
			};
			this.remove = function(name, password, callback){
				if (undef(name)){callback('___undefined_invalid'); check(); return;}
				if (storage.locked[path]){queue.push({exec: self.remove.bind(null, name, password, callback)}); return;} else storage.locked[path] = true;
				if (typeof password === 'function'){callback = password; password = false} if (!callback) callback = noop;
				jstore.read(path, function(err, obj){
					if (err || !obj || !obj.length) obj = {}; else obj = obj[0];
					if (name instanceof Array && obj[name[0]] && name.length === 1) name = name[0];
					if (name instanceof Array && obj[name[0]] && name.length > 1){
						if (obj[name[0]].password){
							if (opts.admin || password === obj[name[0]].password){
								var temp = obj[name[0]].data, error = false;
								for (var i=1; i<name.length-1; i++){if (temp[name[i]]) temp = temp[name[i]]; else {error = true; break;}}
								if (!error && !undef(temp[name[name.length-1]])) delete temp[name[name.length-1]]; 
								else {storage.locked[path] = false; callback('___not_found'); check(); return;}
							}
							else {storage.locked[path] = false; callback('___invalid_password'); check(); return;}
						}
					}
					else if (obj[name]){if (obj[name].password){
						if (opts.admin || password === obj[name].password) delete obj[name]; 
						else {storage.locked[path] = false; callback('___invalid_password'); check();}
					} else delete obj[name];} else callback('___not_found');
					jstore.write(path, obj, function(err){storage.locked[path] = false; callback(err); check();});
				});
			};
		}
	},
	read: function(path, callback){jstore.read(path, callback);},
	read_all: function(path, callback){jstore.read(path+'*', callback);}
};

Function.prototype.toJSON = Function.toString;

function json_encode(obj){
	return JSON.stringify(obj, null, '\t');
}

function json_decode(str, report){
	if (!str) return {};
	try {
		var obj = JSON.parse(str.trim() || 'null', function(a,b){
			if (!undef(b)){
				if (b === null || b === false) return b;
				if (b.match && b.match(/^function[\w\W]+\}$/)){
					try {b = eval('b=0||'+b);}
					catch (e){log('error decoding:', b, e);}
				}
				return b;
			}
		});
		return obj;
	}
	catch (e){return false;}
}

function time(){
	return Math.round(new Date().getTime() / 1000);
}

function millitime(){
	return new Date().getTime();
}

function getErrorHelp(errno){
	switch (errno){
		case 101: case 'EADDRNOTAVAIL':
			return 'Endpoint address not available. This error occurs when a match for the requested address is not found in the routing table.';
		case 102: case 'EADDRINUSE':
			return 'Another socket is already bound to the address specified in bind. Make sure closesocket was called on the other socket bound to the same address. Note that closesocket can take a few minutes to release the socket.';
		case 104: case 'EARPFULL':
			return 'ARP table full. This error is reported by send when the ARP cache is full.';
		case 105: case 'EBADBAUD':
			return 'Invalid baud rate. This error occurs when an invalid baud rate is passed.';
		case 111: case 'ECONNREFUSED':
			return 'Connection refused. This error can occur during an attempt to connect a TCP socket. It is reported when a reset or an unexpected SYNC message is received.  Is the port number you entered correct?';
		case 113: case 'EHOSTUNREACH':
			return 'Server cannot connect to device.  Are both devices powered up and on the same network?  If so, another TCP connection may be blocking this one or the IP Address of the device to connect to may be incorrect.';
		case 115: case 'EIFACECLOSED':
			return 'Server\'s Interface is closed.';
		case 120: case 'EISCONN':
			return 'Socket is already connected. This error is detected when a connect or listen call is made for a socket which is already connected or listening.';
		case 124: case 'ENETDOWN':
			return 'Network is down. This error is reported if a send times out or the network is unable to transmit.';
		case 125: case 'ENETUNREACH':
			return 'Network unreachable (keep alive failed). This error occurs when keep alive closes a TCP socket due to the remote host not responding to keep alive packets.';
		case 128: case 'ENOTCONN':
			return 'Socket is not connected. This error occurs when an operation such as send or recv was performed on a socket which was in an invalid state for the attempted operation.';
		case 140: case 'ETIMEDOUT':
			return 'Timeout. An operation such as connect, recv, or send timed out. For TCP, this error could occur during a send operation if the remote host is not responding with acknowledgments. This could also occur if the remote host is not responding to an ARP request.';
		case 'EPIPE':
			return 'This socket has been ended by the other party.  In response to this error, the server will attempt to close and remove the socket.'
		default:
			return 'Unknown Error Code';
	}
}


// database module/functions (solely for postgres, use another module for other databases)
var DBWrapper = require('node-dbi').DBWrapper;
var DBExpr = require('node-dbi').DBExpr;
var dbConnectionConfig = {
	host: 'localhost', database: '',
	user: '', password: '',
};
// example params for WHERE: [['first_name LIKE ?', '%John%'], ['last_name=?', 'Foo']]
function dbSelect(){
	return (new DBWrapper('pg', dbConnectionConfig)).getSelect();
}
function dbFetchAll(statement, params, callback){
	var dbWrapper = new DBWrapper('pg', dbConnectionConfig);
	dbWrapper.connect(); if (dev) log('DB connection open.');
	dbWrapper.fetchAll(statement, params, function(err, result){
		callback(err, result);
	});
	dbWrapper.close(function(err){
		if (dev) log('DB connection closed.');
		if (err) error(err);
	});
}
function dbInsert(table, params, callback){
	var dbWrapper = new DBWrapper('pg', dbConnectionConfig);
	dbWrapper.connect(); if (dev) log('DB connection open.');
	dbWrapper.insert(table, params, function(err){
		if (err) error(err, table, params);
		else log('Insert: ', table, params);
	});
	dbWrapper.close(function(err){
		if (dev) log('DB connection closed.');
		if (err) error(err);
	});
}
function dbUpdate(table, params, where, callback){
	var dbWrapper = new DBWrapper('pg', dbConnectionConfig);
	dbWrapper.connect(); if (dev) log('DB connection open.');
	dbWrapper.update(table, params, where, function(err){
		if (err) error(err, table, params, where);
		else log('Update: ', table, params);
	});
	dbWrapper.close(function(err){
		if (dev) log('DB connection closed.');
		if (err) error(err);
	});
}
function dbDelete(table, where, callback){
	var dbWrapper = new DBWrapper('pg', dbConnectionConfig);
	dbWrapper.connect(); if (dev) log('DB connection open.');
	dbWrapper.remove(table, where, function(err) {
		if (err) error(err, table, where);
		else log('Remove: ', table, where);
	});
	dbWrapper.close(function(err){
		if (dev) log('DB connection closed.');
		if (err) error(err);
	});
}