opts = {host, port, cipher, password}
function Client (opts) {
	this.id = opts.id;
	this.opts = opts;
	this.send = send; 
	this.isConnected = false;
	let client = this;

	let queue = [];
	let callbacks = {};
	let isBusy = true;
	let socket;

	connect(opts);

	function send (event, data, callback) {
		if (!client.isConnected && callback)
			return callback(new Error('CONNECTION'));

		queue.push({event, data, callback});
		next();
	}

	function next() {
		if (isBusy)
			return;
		
		isBusy = true;

		if (queue.length == 0) 
			return (isBusy = false);

		if (socket) {
			let msg = {
				id: genId(),
				event: queue[0].event, 
				data: queue[0].data
			}

			socket.write((!!opts.cipher ? encode(JSON.stringify(msg), opts.cipher, opts.password) : JSON.stringify(msg)) + '|', function (err) {
				if (err)
					return socket.emit('error', err);

				if (queue[0].callback)		
					callbacks[msg.id] = queue[0].callback;

				client.emit('send', queue[0]);
				queue.shift(); 
				isBusy = false;
				next();
			});
		}	
	}
	
	function connect (opts) {
		socket = net.connect({host: opts.host, port: opts.port});
		queue = [];
		callbacks = {};
		isBusy = true;

		socket.on('connect', function () {
			isBusy = false;
			client.isConnected = true;
			client.emit('connect', opts.id);
		});

		let buffer = '';
		socket.on('data', function(chunk) {
			buffer += chunk;
			let msgs = buffer.split('|');
			buffer = msgs.pop();

			msgs.forEach(function(packet){

				let msg;
				try { 
					msg = JSON.parse(!!opts.cipher ? decode(packet, opts.cipher, opts.password) : packet);
				} catch(err) { 
					return socket.emit('error', err);
				}

				client.emit('receive', msg);
	
				if (msg.id in callbacks) {	
					callbacks[msg.id](msg.data);
					delete callbacks[msg.id];			
					return;
				}
				
				if (msg.event != 'ACK') {
					client.emit(msg.event, msg.data);
					send('ACK', {id: msg.id, event: msg.event});
					return;
				}
			});
		});	

		socket.on('close', () => {
			setTimeout(connect, opts['reconnect-delay'] * 1000 || 30000, opts);
			if (client.isConnected) {
				client.isConnected = false;
				client.emit('disconnect', opts.id);	
			}
			client.isConnected = false;
		});

		socket.on('error', (err) => client.emit('error', err));		
	}	
}