/* Push client */
if (typeof Push == "undefined") Push = {};

Push.Client = function (url) {
	/* Url to reach the server */
	this.server = url || "";
	/* Client state: new, connecting, connected, disconnecting, closed, error */
	this.state = "new";
	/* Client mode: stream | longpoll (TODO: implement stream mode) */
	this.mode = null;
	/* Subscriber id, sent by server in connection message */
	this.id = null;
	/* Custom message handler */
	this.msghandler = null;
	/* Custom error handler */
	this.errhandler = null;
};

/* Arrange for cross sub domain XHR */
Push.Client.joinDomain = function (server, callback) {
	var ifr = document.createElement("iframe");
	ifr.style.width = "7em";
	ifr.style.height = "7em";
	ifr.style.position = "absolute";
	ifr.style.top = "-77em";
	ifr.style.zIndex = "-20";
	document.body.appendChild(ifr);
	document.domain = "wa-research.ch";
	ifr.setAttribute("src", server + "/static/join.html");
	$(ifr).ready(callback);
};

Push.Client.prototype = {
	/* Make a @command request to the server. */
	makeRequest: function (command, query) {
		var self = this;

		function new_XMLHttpRequest () {
			try { return new XMLHttpRequest(); } catch(e) {}
			try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
			try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
			return null;
		}

		var xhr = new_XMLHttpRequest();
		xhr.open("POST", this.server + "/" + command);
		xhr.onreadystatechange = function() {
			self.onreadystatechange(xhr);
		};

		if (query) {
			query = JSON.stringify(query);
			xhr.setRequestHeader("Content-Type", "application/json");
		}

		try {
			xhr.send(query);
		} catch (e) {
			this.handleError(e);
		}
	},

	/* Handle a server message. React to server messages or call msghandler to
	 * handle the normal ones. */
	handleMessage: function (msg) {
		if (msg.type == "server" && msg.action == "unregister") {
			/* Server wants us to disconnect */
			return this.close(msg.reason);
		}

		if (this.state == "connecting")
		{
			if (msg.type == "server" && msg.action == "register")
			{
				/* Connection message */
				this.id = msg.id;
				this.mode = msg.mode;
				this.state = "connected";
			}
		}

		if (this.state == "connected") {
			if (this.msghandler && msg.type == "message") {
				/* User message */
				this.msghandler.call(this, msg);
			}
		}
	},

	/* Issue a connect request.
	 *
	 * @query must specify a "routing" properly */
	connect: function (query) {
		this.state = "connecting";
		this.makeRequest("connect", query);
		/* Unload gracefully when closing the window */
		var self = this;
		$(window).unload(function () { self.unload() });
	},

	/* Disconnect from the server nicely */
	unload: function () {
		if (this.state == "connected")
			this.makeRequest("disconnect/" + this.id);
	},

	/* Resume the connection in longpoll mode */
	resume: function () {
		if (this.mode == "longpoll")
			this.makeRequest("resume/" + this.id);
	},

	/* Disconnect from the server */
	disconnect: function () {
		if (this.state == "connected") {
			this.state = "disconnecting";
			this.makeRequest("disconnect/" + this.id);
		} else {
			this.close("disconnect");
		}
	},

	/* Mark the client closed */
	close: function (reason) {
		this.state = "closed";
		this.mode = null;
		this.id = null;
	},

	/* Close the client and call the user handler */
	handleError: function (e) {
		this.close("error");
		this.state = "error";
		if (this.errhandler)
			this.errhandler(e);
	},

	/* Handle request state change */
	onreadystatechange: function (xhr) {
		if (xhr.readyState == 4)
		{
			/* We've issued a bad request */
			if (xhr.status < 200 || xhr.status >= 300)
				return this.handleError(xhr.statusText || "Connection broken");

			/* Dismiss if disconnecting */
			if (this.state == "disconnecting") {
				this.state = "disconnected";
				return;
			}

			/* Server should always respond in json */
			if (xhr.getResponseHeader("Content-Type") != "application/json")
				return this.handleError("Bad content type");

			/* Messages are JSON expressions, one each line */
			var lines = xhr.responseText.split(/\r?\n/);

			for (var i = 0; i < lines.length; i ++) {
				if (lines[i].length == 0)
					continue;
				try {
					var msg = eval("(" + lines[i] + ")");
				} catch (e) {
					return this.handleError("Parser error");
				}

				if (!msg.type)
					return this.handleError("Invalid data");

				/* Message is valid, handle it */
				this.handleMessage(msg);
			}

			/* If nothing went wrong, resume right away */
			if (this.state == "connected")
				this.resume();
		}
	}
};
