if ( typeof(console) == "undefined" ) {
	console = (function() {
    var logger = function() {};
		return {
			log: logger,
			error: logger
		};
	})();
}

function binding(method, scope) {
	for (var i = 2, bound_args = []; i < arguments.length; i++) {
		bound_args.push(arguments[i]);
	}
	return function() {
		for (var i = 0, args = []; i < arguments.length; i++) {
			args.push(arguments[i]);
		}
		return method.apply(scope, bound_args.concat(args));
	}
}

(function(jQuery){

   if ( typeof(log_exception) === "undefined" ) {
     function log_exception(e){
       console.error(e.name, e.message, e);
     }
   }

  var DomHandler = {};

  function initDomHandler(elem){
    elem = $(elem);

    if ( !elem.data('handler-loaded') ) {
      elem.data('handler-loaded', true);
      var handlerName = elem.attr('data-handler');

      if ( !handlerName ) return;

      var klass = window[handlerName];

      if ( klass ) {
        elem.data('handler', new klass(elem[0]));
      } else {
        console.error('init failed: DomHandler ' + handlerName + ' was not found.', elem[0]);
      }
    }
  }

  function anchorFromEvent(e){
    var anchor = $(e.target);

    if ( anchor.attr('nodeName').toLowerCase() != 'a' ) {
      anchor = anchor.parents('a').eq(0);
      if ( anchor.length == 0 ) return null;
    }

    return anchor;
  }


  function handleAnchorClick(e){

    var anchor = anchorFromEvent(e);

    if ( anchor ) {
      var url = anchor.attr('href');
      var isDefaultPrevented = (e.originalEvent.returnValue === false);
      var isPropagationStopped = e.originalEvent.cancelBubble;

      if ( !isDefaultPrevented && !isPropagationStopped && url != '#' ) {

		  if ( url && (url.match(/^#/) || (url.match(/#/) &&
										   url.match(/^(https?:\/\/[^\/]+)/)[1] == document.location.href.match(/^(https?:\/\/[^\/]+)/)[1]))) {
		  if (url && (url.match(/^[^#]/))) {
            url = url.match(/#.*$/)[0];
          }
          if ( DomHandler.hashHrefClicked(url) !== false ) return true;
        } else {
          url = DomHandler.ajaxCallWillSubmit(url);
          if ( url == null ) return true;

          new AjaxRequest(url, anchor);
        }
      }

      e.preventDefault();
      e.stopImmediatePropagation();
    }
  }

  function AjaxRequest(url, initiator, data){
    this.initiator = $(initiator);
    this.initiatorChain = this.initiator.add(this.initiator.parents());

    var prevRequest = DomHandler.pendingRequest;
    DomHandler.pendingRequest = this;
    if ( prevRequest ) prevRequest.abort();

    this.submit(url, data);

    this.callbackEvent({type : 'dhAjax:submit', xhr : this.xhr, url : url});
  }


  var ajaxEventDidBubble = false;

  $(function(){
    $('body').bind('dhAjax:callbackTriggered', function(){
      ajaxEventDidBubble = true;
    })
  });


  var currentSuffix = '', forcedRefresh = false;

  AjaxRequest.prototype = {
    submit : function(url, data){
      this.url = url;

      var self = this;
      this.request = {
        url : url,
        data: data || {},
        dataType : 'json',
        error : function(x,s,e){ return self.error(x,s,e) },
        success : function(r,s){ return self.success(r,s) },
        complete : function(x,s){ return self.complete(x,s) }
      }

      this.xhr = jQuery.ajax(this.request);
    },

    abort : function(){
      this.callbackEvent({type : 'dhAjax:abort', xhr : this.xhr});
      if ( this == DomHandler.pendingRequest ) delete DomHandler.pendingRequest;
    },

    error : function(xhr, status, exception){
      console.error('ajax call fail', xhr);

      this.callbackEvent({
        type : 'dhAjax:fail',
        xhr : xhr,
        status : status
      });
    },

    success : function(response, status){

      if ( response.redirect ) {
        var relative = DomHandler.ajaxCallWillSubmit(response.redirect);

        if ( !relative ) {
          top.location.replace(response.redirect);
        } else {

          delete response.redirect;
          if ( !this.redirectedResponse ) {
            this.redirectedResponse = response;
          } else {
            jQuery.extend(this.redirectedResponse, response);
          }

          this.submit(relative);
        }

        return false;
      }

      if ( typeof this.redirectedResponse == 'object' ) {
        jQuery.extend(response, this.redirectedResponse);
      }
      delete this.redirectedResponse;

      this.callbackEvent({
        type : 'dhAjax:success',
        response : response,
        status : status
      });

      var isCurrent = (this == DomHandler.pendingRequest);
      if (isCurrent && response["dh.update_suffix_hash"]) {
        currentSuffix = this.url.replace(Application.canvasUrlRegex, '').substr(1);
        FB_RequireFeatures(["CanvasUtil"], function(){
          FB.CanvasClient.changeUrlSuffix(currentSuffix);
        });
      }

      if (!forcedRefresh && response["forceRefresh"]) {
        forceRefresh = true;
        window.top.location.href = Application.canvasUrl + "/" + currentSuffix;
        return;
      } else if (forcedRefresh) {
        return;
      }

      $.each(response, function(selector, data){
        if (selector.slice(0,3) == 'dh.' ) return;

        $(selector).each(function(){
          var handler = $(this).data('handler');
          var callback;

          if ( handler && handler.update ) {
            callback = handler.update;

            if ( isCurrent || handler.receiveAllUpdates) {
            } else if (handler.receiveIntermediateUpdates &&
                      (!handler.lastUpdateAt || handler.lastUpdateAt < response['dh.timestamp'])) {
              handler.lastUpdateAt = response['dh.timestamp'];
            } else {
              return; // return from this run if the iterator
            }

          } else {
            callback = DomHandler.elemWasUpdated;
          }

          try {
            callback.call((handler || this), data, isCurrent);
          } catch (e) {
	    log_exception(e);
          }
        });
      });
    },

    complete : function(xhr, status){
      if ( this.redirectedResponse ) return;

      if ( this == DomHandler.pendingRequest ) delete DomHandler.pendingRequest;

      this.callbackEvent({type : 'dhAjax:complete', xhr : xhr, status : status});
    },

    callbackEvent : function(event){
      this.initiatorChain.each(function(){
        $(this).trigger('dhAjax:callbackTriggered');
        if ( ajaxEventDidBubble ) {
          try {
            $(this).trigger(event);
          } catch (e) {
	    log_exception(e);
          }
          ajaxEventDidBubble = false;
          return false; // stop iterating. man, jQuery "each" return api sucks
        } else {
          return true; // keep going.
        }
      });
    }
  };

  var documentIsReady = false;
  $(function(){ documentIsReady = true });

  function elemIsReady(elem){
    if ( documentIsReady ) return true;

    while (elem) {
      if (elem.nextSibling) return true;
      elem = elem.parentNode;
    }

    return false;
  }

  function domSweeper(ctx) {
    $('[data-handler]', ctx).each(function(){
      if ( elemIsReady(this) ) initDomHandler(this);
    });
  }

/*
  var jQuery_fn_domManip = jQuery.fn.domManip;
  var jQuery_fn_html = jQuery.fn.html;

  jQuery.fn.domManip = function( args, table, callback ) {
    var elem = this;
    setTimeout(function(){ domSweeper(elem) }, 0);
    return jQuery_fn_domManip.call(this, args, table, callback);
  };

  jQuery.fn.html = function( value ) {
    var elem = this;
    if ( typeof(value) != "undefined" && typeof(value) == "string" ) setTimeout(function(){ domSweeper(elem) }, 0);
    return jQuery_fn_html.call(this, value);
  };
*/

  jQuery.extend(DomHandler, {

    start : function(base){
      base = base || 'body';

      var sweeperInterval = setInterval(domSweeper, 50);
      $(function(){
        clearInterval(sweeperInterval);
        domSweeper();
      });

      if ( ! DomHandler.ajax_off ) {

        var interval, attached = false;

        var attacher = function(match){
          attached = true;
          match.unbind('click.base'); // this should be unnecessary
          match.bind('click.ajax', handleAnchorClick);
        };

        interval = setInterval(function(){
          var match = $(base);

          if ( match.length > 0 ) {
            clearInterval(interval);
            attacher(match);
          }
        }, 0);

        $(function(){
          if ( !attached ) {
            clearInterval(interval);
            var match = $(base);

            if ( match.length == 0 ) {
              console.error('error attaching ajax click handler: ' + base + ' not found.');
            } else {
              attacher(match);
            }
          }
        });
      }
    },

    get : function(url, initiator){
      var new_url = DomHandler.ajaxCallWillSubmit(url);
      new AjaxRequest(new_url || url, initiator || 'body');
    },

    post : function(url, initiator, data){
      var new_url = DomHandler.ajaxCallWillSubmit(url);
      data = jQuery.extend({'_method': 'POST'}, data || {});
      new AjaxRequest(new_url || url, initiator || 'body', data);
    },

    elemWasUpdated : function(data, isCurrent){
      $(this).html(data);
    },

    ajaxCallWillSubmit : function(url){
			  if (url && (url.search(/^http/) != -1 )) return null;
      return url;
    },

    hashHrefClicked : function(url) { return true }
  });

  jQuery.fn.handler = function() {
    initDomHandler(this);
    return this.data('handler');
  };

  jQuery.dh = DomHandler;

  jQuery.dh.start();

})(jQuery);

