function SimPubClient() {
	var self = this,
		ENDM = "\r\n",
		_conn = null,
		_buffer = "",
		_subs = {},
		_cbOpen, _cbClose, _cbRead, _processCommand, _processMessage;
	
	// Public callbacks:
	
	self.onOpen = function() {};
	self.onClose = function(code) {};
	self.onMessage = function(message) {};
	
	// Public methods:
	
	self.connect = function(host, port) {
		_conn = new self.transport();
		_conn.onread = _cbRead;
		_conn.onclose = _cbClose;
		_conn.onopen = _cbOpen;
		_conn.open(host, port);
	};
	
	self.close = function() {
		_conn.close();
	};
	
	self.send = function(o) {
		var s;
		if (_processCommand(o)) {
			s = JSON.stringify(o);
			_conn.send(s + ENDM);
		}
	};
	
	self.resubscribe = function() {
		var node;
		for (node in _subs) if (_subs.hasOwnProperty(node)) {
			self.send({ cmd: "subscribe", node: node, retrieve: true, after: _subs[node] });
		}
	};
	
	// Private stuff:
	
	_cbOpen = function() {
		try {
			self.onOpen();
		} catch (ex) {}
	};
	
	_cbClose = function(code) {
		try {
			self.onClose(code);
		} catch (ex) {}
	};
	
	_cbRead = function(s) {
		var msgs, msg, i, o;
		_buffer += s;
		msgs = _buffer.split(ENDM);
		_buffer = msgs[msgs.length - 1];
		for (i=0; i < msgs.length - 1; ++i) {
			msg = msgs[i];
			try {
				o = JSON.parse(msg);
				if (!_processMessage(o)) continue;
				self.onMessage(o);
			} catch (ex) {}
		}
	};
	
	_processCommand = function(o) {
		if (o.cmd == "subscribe") {
			if (!(o.node in _subs)) _subs[o.node] = -1;
		} else if (o.cmd == "unsubscribe") {
			delete _subs[o.node];
		}
		return true;
	};
	
	_processMessage = function(o) {
		var i, item, id;
		if ((o.type == "itemsPublished") || (o.type == "itemsRetrieved")) {
			if (!(o.node in _subs)) return false;
			for (i=0; i<o.items.length; ++i) {
				item = o.items[i];
				id = item.id || -1;
				if (id > _subs[o.node]) _subs[o.node] = id;
			}
		}
		return true;
	};
}

SimPubClient.prototype.transport = TCPSocket;

