/*jshint bitwise: false*/

/**
  Message Bus functionality.

  @class MessageBus
  @namespace Discourse
  @module Discourse
**/
Discourse.MessageBus = (function() {
  // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
  var callbacks, clientId, failCount, interval, shouldLongPoll, queue, responseCallbacks, uniqueId;
  var me;

  uniqueId = function() {
    return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r, v;
      r = Math.random() * 16 | 0;
      v = c === 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
    });
  };

  clientId = uniqueId();
  responseCallbacks = {};
  callbacks = [];
  queue = [];
  interval = null;
  failCount = 0;

  var isHidden = function() {
    if (document.hidden !== void 0) {
      return document.hidden;
    } else if (document.webkitHidden !== void 0) {
      return document.webkitHidden;
    } else if (document.msHidden !== void 0) {
      return document.msHidden;
    } else if (document.mozHidden !== void 0) {
      return document.mozHidden;
    } else {
      // fallback to problamatic window.focus
      return !Discourse.get('hasFocus');
    }
  };

  shouldLongPoll = function() {
    return me.alwaysLongPoll || !isHidden();
  };

  me = {
    enableLongPolling: true,
    callbackInterval: 60000,
    maxPollInterval: 3 * 60 * 1000,
    callbacks: callbacks,
    clientId: clientId,
    alwaysLongPoll: false,
    stop: false,

    // Start polling
    start: function(opts) {
      var poll,
        _this = this;
      if (!opts) opts = {};

      poll = function() {
        var data, gotData;
        if (callbacks.length === 0) {
          setTimeout(poll, 500);
          return;
        }
        data = {};
        _.each(callbacks, function(callback) {
          data[callback.channel] = callback.last_id === void 0 ? -1 : callback.last_id;
        });
        gotData = false;
        _this.longPoll = $.ajax(Discourse.getURL("/message-bus/") + clientId + "/poll?" + (!shouldLongPoll() || !_this.enableLongPolling ? "dlp=t" : ""), {
          data: data,
          cache: false,
          dataType: 'json',
          type: 'POST',
          headers: {
            'X-SILENCE-LOGGER': 'true'
          },
          success: function(messages) {
            failCount = 0;
            _.each(messages,function(message) {
              gotData = true;
              _.each(callbacks, function(callback) {
                if (callback.channel === message.channel) {
                  callback.last_id = message.message_id;
                  callback.func(message.data);
                }
                if (message.channel === "/__status") {
                  if (message.data[callback.channel] !== void 0) {
                    callback.last_id = message.data[callback.channel];
                  }
                }
              });
            });
          },
          error: failCount += 1,
          complete: function() {
            if (gotData) {
              setTimeout(poll, 100);
            } else {
              interval = _this.callbackInterval;
              if (failCount > 2) {
                interval = interval * failCount;
              } else if (!shouldLongPoll()) {
                // slowning down stuff a lot when hidden
                // we will need to add a lot of fine tuning here
                interval = interval * 4;
              }
              if (interval > _this.maxPollInterval) {
                interval = _this.maxPollInterval;
              }
              setTimeout(poll, interval);
            }
            _this.longPoll = null;
          }
        });
      };
      poll();
    },

    // Subscribe to a channel
    subscribe: function(channel, func, lastId) {
      callbacks.push({
        channel: channel,
        func: func,
        last_id: lastId
      });
      if (this.longPoll) {
        return this.longPoll.abort();
      }
    },

    // Unsubscribe from a channel
    unsubscribe: function(channel) {
      // TODO proper globbing
      var glob;
      if (channel.indexOf("*", channel.length - 1) !== -1) {
        channel = channel.substr(0, channel.length - 1);
        glob = true;
      }
      callbacks = $.grep(callbacks,function(callback) {
        if (glob) {
          return callback.channel.substr(0, channel.length) !== channel;
        } else {
          return callback.channel !== channel;
        }
      });
      if (this.longPoll) {
        return this.longPoll.abort();
      }
    }
  };

  return me;
})();
