/*
 * Copyright (c) 2011 TWIMPACT UG (haftungsbeschraenkt). All rights reserved.
 */

(function($, w) {

  // IE specific code
  $.ajaxTransport(function(options, originalOptions, jqXHR) {
    var xdr;

    return {
      send: function(_, completeCallback) {
        xdr = new XDomainRequest();
        xdr.onload = function() {
          if (xdr.contentType.match(/\/json/)) {
            options.dataTypes.push("json");
          }

          completeCallback(200, 'success', { text: xdr.responseText });
        };
        xdr.onerror = xdr.ontimeout = function() {
          completeCallback(400, 'failed', { text: xdr.responseText });
        };

        xdr.open(options.type, options.url);
        xdr.send(options.data);
      },
      abort: function() {
        if (xdr) {
          xdr.abort();
        }
      }
    };
  });

  // ensure we have initialized the namespace for twimpact;
  w.Twimpact = w.Twimpact || {};

  Twimpact.Streaming = function(options) {
    var byteCounter = 0;
    var eventCounter = 0;
    var eventStart = 0;
    var eventRate = 0;

    var streamUrl, stream;
    var initialConnect = true;
    var eventHandler = {};

    var webSocketChunk = null;
    var settings = {
      reconnect: false,
      enableXDR: true,
      dataType: "json",
      converters: {
        json: function(d) {
          if (typeof d === 'string') {
            byteCounter += d.length;
            var msg = (webSocketChunk ? webSocketChunk : "") + d;
            try {
              var parsedJson = $.parseJSON(msg);
              webSocketChunk = null;
              return parsedJson;
            } catch(e) {
              webSocketChunk = msg.split(/[\r\n]+/).pop();
            }
          } else {
            return d;
          }
        }
      },
      // event handling proxy
      open: _open,
      error: _error,
      close: _close,
      message: _message,
      // low level functionality
      handleOpen: _handleOpen,
      handleMessage: _handleMessage,
      handleSend: _handleSend
    };

    $.extend(true, settings, options || {});

    function _open(event) {
      initialConnect = false;
      eventStart = Date.now().getTime();
      var open = eventHandler['open'];
      if (typeof open === 'function') {
        open(event);
      }
    }

    function _error(event) {
      var error = eventHandler['error'];
      if (typeof error === 'function') {
        error(event);
      }
    }

    function _close(event) {
      // handle case where initial connect fails due to WebSocket incompatibilies
      if (!event.wasClean && initialConnect &&
          event.currentTarget && event.currentTarget.toString().indexOf('WebSocket') != -1) {
        if (console.log) console.log("initial connect error, downgrading to streaming");
        settings.type = "http";
        settings.reconnect = true;
        streamUrl = streamUrl.replace("ws://", "http://");
        stream = $.stream(streamUrl, settings);
        return;
      }

      var close = eventHandler['close'];
      if (typeof close === 'function') {
        close(event);
      }
    }

    function _message(event) {
      var message = eventHandler['message'];
      if (typeof message === 'function' && typeof event.data === 'object') {
        eventCounter++;
        message(event);
      }
    }

    function _handleOpen(text, message) {
      message.index = text.indexOf("<!-- EOD -->") + 12;
      return true;
    }

    // this message parser is only used for XHR streaming requests
    function _handleMessage(text, message) {
      if (!message.json) {
        message.json = [];

        var received = text.substring(message.index, text.length);
        var oldChunk = message.chunk;
        if (message.chunk) {
          received = message.chunk + received;
          delete message.chunk;
        }
        message.index = text.length;
        var lines = received.split(/[\n\r]+/);

//        console.log(
//            "index="+(message.index)+", "+
//            "length="+Twimpact.Util.byteSize(text.length)+", "+
//            "chunk="+Twimpact.Util.byteSize(oldChunk ? oldChunk.length : "")+", "+
//            "received="+Twimpact.Util.byteSize(received.length)+", "+
//            "lines="+lines.length+" ("+message.handledLines+"/"+text.split(/[\r\n]+/).length+")");


        // iterate through lines, parse and push on queue, add rest to chunk
        $.each(lines, function(idx, line) {
          if (!line.match(/^[\s]*$/)) {
            try {
              message.json.push($.parseJSON(line));
              message.handledLines = message.handledLines ? message.handledLines + 1 : 1;
              byteCounter += line.length;
            } catch(e) {
              // only add junk parts if we do not have found a newline
              if (idx == lines.length - 1) message.chunk = (message.chunk || "") + line;
            }
          }
        });
        text = "";
      }

      if (message.json && message.json.length > 0) {
        message.data = message.json.pop();
        return true;
      } else delete message.json;

      return false;
    }

    function _handleSend(t) {
      if (t !== "send") {
        return false;
      }
    }

    return {
      isStreaming: function() {
        return stream.options.type ? stream.options.type.match(/http/) : false;
      },

      // the event reception rate in events per second
      eventRate: function() {
        var currentTime = Date.now().getTime();
        if (currentTime - eventStart > 100) {
          var currentRate = eventCounter / (currentTime - eventStart) * 1000;
          eventStart = currentTime;
          eventCounter = 0;
          eventRate = currentRate * 0.2 + eventRate * 0.8;
        }
        return eventRate;
      },

      configure: function(options) {
        $.extend(true, settings, options || {});
      },

      connect: function(url) {
        if (stream) stream.close();
        if (typeof url === 'string') streamUrl = url;
        stream = $.stream(streamUrl, settings);
        return this;
      },

      disconnect: function() {
        if (stream) stream.close();
      },

      open: function(f) {
        eventHandler['open'] = f;
        return this;
      },

      error: function(f) {
        eventHandler['error'] = f;
        return this;
      },

      close: function(f) {
        eventHandler['close'] = f;
        return this;
      },

      message: function(f) {
        eventHandler['message'] = f;
        return this;
      },

      receivedBytes: function() { return byteCounter; }
    };
  }
})(jQuery, window);

