// XMLHttpRequest messaging library
// Copyright Karl Henrik Falck <f@lck.nu> 2007
// http://henrikfalck.com/messagelib

function Messenger(opts) {
  var self = this;
  this.initializeUrl = opts.initializeUrl;
  this.refreshUrl = opts.refreshUrl;
  this.sendUrl = opts.sendUrl;
  this.dispatchMessage = opts.dispatch;
  this.dispatchStatistics = opts.census;
  this.statistics = !!opts.census;
  this.dispatchOwnMessages = opts.feedback;
  this.dispatchOwnMessagesDirectly = !!opts.immediateFeedback;
  this.initialMessages = opts.initial;
  this.dispatchError = opts.error;
  this.maxAge = opts.maxAge;

  this.req = null;
  this.running = false;
  this.stopping = false;
  this.requesting = false;
  this.updateTimeout = null;
  this.lastUpdate = 0;
  this.minInterval = 10;
  this.outgoing = [];

  this.version = 1;

  this.start = function() {
    if (this.running) return;
    this.createRequestObject();
    this.request(this.initializeUrl, (this.statistics ? "statistics=yes&" : "") + (this.initialMessages ? "initial=" + this.initialMessages + "&": "") + (this.maxAge ? "age=" + this.maxAge : "") + "version=" + this.version);
  }
  
  this.stop = function() { this.stopping = true; }

  this.send = function(msg) {
    if (!msg) return;
    msg = "" + msg;
    if (msg.length == 0) return;
    this.outgoing.push(msg);
    if (this.dispatchOwnMessages && this.dispatchOwnMessagesDirectly)
      this.dispatchMessage(this.id, msg, 0);
    if (this.statistics && this.ispassive) {
      this.passive--;
      this.active++;
      this.ispassive = false;
      this.statisticsUpdated();
    }
    this.scheduleUpdate(1);
  }

  this.handleResponse = function(res) {
    if (res.id) this.id = res.id;
    if (res.l) this.minInterval = res.l;
    if (!res.i) this.stop();
    if (this.statistics && !isNaN(res.a) && !isNaN(res.p))
      this.updateStatistics(res.a, res.p, res.y);
    if (res.m) {
      var len = res.m.length;
      for (var i = 0; i < len; ++i) {
	try {
	  var m = res.m[i];
	  this.dispatchMessage(m[0], m[1], m[2]);
	} catch (e) { }
      }
    }
    this.lastUpdate = new Date().getTime();
    this.scheduleUpdate(res.i);
    this.requesting = false;
  }

  this.handleRequestFailed = function() {
    this.scheduleUpdate(30);
    this.requesting = false;
  }

  this.scheduleUpdate = function(secs) {
    clearTimeout(this.updateTimeout);
    this.updateTimeout = setTimeout(function() { self.requestUpdate(); }, secs * 1000);
  }

  this.requestUpdate = function() {
    if (this.requesting ||
	new Date().getTime() - this.lastUpdate < this.minInterval * 1000) {
      this.scheduleUpdate(1);
      return;
    }
    if (this.outgoing.length == 0)
      this.request(this.refreshUrl);
    else
      this.request(this.sendUrl, this.formatOutgoingMessages());
  }

  this.formatOutgoingMessages = function() {
    var str = "c=" + this.outgoing.length + "&";
    for (var i = 0; m = this.outgoing.shift(); ++i) {
      str += "m" + i + "=" + encodeURIComponent(m);
      if (this.outgoing.length > 0) str += "&";
      if (this.dispatchOwnMessages && !this.dispatchOwnMessagesDirectly)
	this.dispatchMessage(this.id, m, 0);
    }
    return str;
  }

  this.updateStatistics = function(a, p, y) {
    this.active = a;
    this.passive = p;
    this.ispassive = (y == "p");
    if (!y) this.passive++;
    this.statisticsUpdated();
  }

  this.statisticsUpdated = function() {
    if (this.dispatchStatistics)
      this.dispatchStatistics(this.active, this.passive);
  }

  this.request = function(url, data) {
    this.requesting = true;
    this.req.open("POST", url, true);
    this.req.onreadystatechange = this.onStateChange;
    this.req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    this.req.send(data);
  }

  this.onStateChange = function() {
    if (self.req.readyState != 4) return;
    if (self.req.status != 200) { self.handleRequestFailed(); return; }
    if (self.req.responseText == "banned") { self.error("banned!"); self.stop(); return; }
    try {
      eval("var res = " + self.req.responseText + ";");
    } catch (e) { self.error("eval failed"); self.stop(); return; }
    try {
      self.handleResponse(res);
    } catch (e) { self.handleRequestFailed(); }
  }

  this.createRequestObject = function() {
    if (window.XMLHttpRequest)
      this.req = new XMLHttpRequest();
    else if (window.ActiveXObject)
      this.req = new ActiveXObject("Microsoft.XMLHTTP");
  }

  this.error = function(str) {
    if (this.dispatchError)
      this.dispatchError(str);
  }
}

Messenger.Create =
  function(baseUrl,
	   serviceName,
	   opts) {
  if (!opts) var opts = {shortUrls: false};
  if (!opts.scriptFileName) opts.scriptFileName = "message.php";
  if (baseUrl.charAt(baseUrl.length - 1) != "/") baseUrl += "/";
  var pattern = opts.shortUrls ? baseUrl + "%s/%a" : baseUrl + opts.scriptFileName + "?service=%s&action=%a";
  var actions = ["initialize", "refresh", "send"];
  for (var i = 0; a = actions[i]; i++)
    opts[a + "Url"] = pattern.replace("%s", serviceName).replace("%a", a);
  return new Messenger(opts);
}

