(function( $ ){

  /**
   *  Sets the default locale for all widgets. If nothing is found in window.locale it falls back to this
   */
  var locale = window.locale || 'de_DE';

  /**
   *  Jquery namespace
   * @name jQuery
   * @namespace
   */

  /**
   *  Creates a new widget state object.
   *  This is a helper object to handle widget manipulation.
   *  @constructor Widget State Object
   *  @name widgetState
   *  @param {string} Widget name (like MyLoginWidget)
   *  @param {id} id of the wrapper element
   *  @public
   */
  var widget = function (name, wrapper, $loader, $closer, randId) {
    try {
      var config = gim.getConfigByProperty({
        name: name
      });
    } catch (e) {
      console.log(e);
      return false;
    }
    
    /**
     * Configuration of the widget (from the original widget framework)
     * @memberOf widgetState
     * @type {Object}
     */
    this.config = config;
    
    /**
     * Type name of the widget
     * @memberOf widgetState
     * @type {String}
     */
    this.type = config.type;
    
    /**
     * Name of the widget
     * @memberOf widgetState
     * @type {String}
     */
    this.name = config.name;
    
    /**
     * Current view of the widget. (DO NOT EDIT FROM THE OUTSIDE)
     * @memberOf widgetState
     * @type {String}
     */
    this.view = null;
    
    /**
     * Current params of the widget.
     * @memberOf widgetState
     * @type {String}
     */
    this.params = '';
    
    this.id = wrapper[0].id;
    this.randId = randId;
    /**
     *  Jquery object with the widget wrapper selected
     *  @memberOf widgetState
     *  @type {Object}
     */
    this.jq = wrapper;

    /**
     *  Jquery object with the loading wrapper selected OR an empty jquery object
     *  @memberOf widgetState
     *  @type {Object}
     */
    this.$loader = $loader || $();

    /**
     *  Jquery object with the closing wrapper selected OR an empty jquery object
     *  @memberOf widgetState
     *  @type {Object}
     */
    this.$closer = $closer || $();
  }

  /**
   *  Renders the widget with the given view and parameters.
   *  @name render
   *  @methodOf widgetState
   *  @param {String} view Name of the view to render
   *  @param {String} [params] Parameter string
   *  @public
   */
  widget.prototype.render = function (view, params) {
    this.params = params || this.params;
    this.view = view;
    gim.renderWidget(this.name, view, locale, this.params, this.id);
    return this.showLoader();
  }

  /**
   *  Reload the widget with it's last known state.
   *  @name reload
   *  @methodOf widgetState
   *  @public
   */
  widget.prototype.reload = function () {
    gim.renderWidget(this.name, this.view, locale, this.params, this.id);
    return this.showLoader();
  }

  /**
   *  Change the view of the widget.
   *  @name changeView
   *  @methodOf widgetState
   *  @param {String} view Target view to be used
   *  @public
   */
  widget.prototype.changeView = function (view) {
    this.view = view;
    gim.renderWidget(this.name, view, locale, this.params, this.id);
    return this.showLoader();
  }

  /**
   *  Destroy the widget and all its event listeners.
   *  @name destroy
   *  @methodOf widgetState
   *  @public
   */
  widget.prototype.destroy = function () {
    var parent = this.jq.parent();
    widgetEventHandler.unsubscribe(this.randId);
    this.jq.remove();
    this.$loader.remove();
    this.$closer.remove();
    return parent;
  }

  /**
   *  Show the loading div and hide the widget (if loading was set to to true or a class name).
   *  Attention: This does not automatically hide it again! Widgets that have the showLoader class will hide the loading bar on receiving the widgetDimension event.
   *  @name showLoader
   *  @methodOf widgetState
   *  @public
   */
  widget.prototype.showLoader = function () {
    var isLoading = this.view === 'empty' || ! this.$loader || ! this.$loader.length
    if (isLoading)
      return this;

    this.jq.hide();
    this.$closer.hide();
    this.$loader.show();
    return this;
  }

  /**
   * This helper does a little workaround for comparing views where the widget changes the view internally without communicating with the ajaxhub.
   */
  var compare_same_view = function (e, widgetView) {
    return e.srcView === widgetView || 
      (widgetView === 'start' && (e.srcView === 'preLogin' || e.srcView === 'postLogin' || e.srcView === 'welcome')) ||
      (widgetView === 'login' && e.srcView === 'confirmationRequired') || 
      (e.srcWidget === 'MyEnterContestWidget' && widgetView === 'start') ||
      (e.srcWidget === 'MyPurchaseRewardWidget' && widgetView === 'start') ||
      (e.srcWidget === 'MyRegistrationWidget' && widgetView === 'registerUser' && e.srcView === 'underAgeUser');
  }

  /**
   *  Object that is given to the event listeners as the first argument
   * @name eventObject
   * @class
   */
  /**
   * Name of the widget that triggered the event.
   * @name eventObject#srcWidget
   * @field
   */
  /**
   * Name of the view that the widget has that fired the event.
   * @name eventObject#srcView
   * @field
   */
  /**
   * Type of the widget that the event is going to.
   * @name eventObject#dstWidgetType
   * @field
   */
  /**
   * Name of the view of the widget that the event is going to.
   * @name eventObject#dstWidetView
   * @field
   */

  /**
   * Start of "custom" events
   * @namespace
   */
  var customEvents = {

    /**
     * Check if the destination widget is the one that triggered the listener
     * @private
     */
    selfChange: function (listener) {
      return {
        event: 'view',
        /**
         *  selfChange is fired when the Widget changes itself
         *  The this keyword in the event callback refers to the {@link widget} object
         * @name widgetEvents#selfChange
         * @event
         * @param {Object} e {@link eventObject}
         * @param {String} dstView Name of the new view
         */
        fn: function (e) {
          if (window.breakEvent)
            debugger;
          if (e.srcWidget === listener.widget.name && e.dstView !== e.srcView && compare_same_view(e, listener.widget.view)) {
            return listener.fn.call(listener.widget, e, e.dstView, listener) || true;
          }
          return true;
        }
      }
    },
    /**
     * Check if the destination widget is the one that triggered the listener and the view didn't change
     * @private
     */
    refresh: function (listener) {
      return {
        event: 'view',
        /**
         *  refresh is fired when the Widget changes it's view to the same view it had before
         *  The this keyword in the event callback refers to the {@link widget} object
         * @name widgetEvents#refresh
         * @event
         * @param {Object} e {@link eventObject}
         */
        fn: function (e) {
          if (window.breakEvent)
            debugger;
          if (e.srcWidget === listener.widget.name && e.dstView === listener.widget.view && compare_same_view(e, listener.widget.view)) {
            return listener.fn.call(listener.widget, e, listener) || true;
          }
          return true;
        }
      }
    },
    /**
     * Check if the destination widget is the one that triggered the listener
     * @private
     */
    incomingChange: function (listener) {
      return {
        event: 'change',
        /**
         *  incomingChange is fired when another widget requests this widget to change its view
         *  The this keyword in the event callback refers to the {@link widget} object
         * @name widgetEvents#incomingChange
         * @event
         * @param {Object} e {@link eventObject}
         * @param {String} dstView Name of the widget that triggered the event
         * @param {String} srcName Name of the widget that triggered the event
         * @param {String} srcView View of the widget that triggered the event
         */
        fn: function (e) {
          if (window.breakEvent)
            debugger;
          if (e.dstWidgetType === listener.widget.type && e.srcWidget !== listener.widget.name) {
            return listener.fn.call(listener.widget, e, e.dstView, e.srcWidget, e.srcView, listener) || true;
          }
          return true;
        }
      }
    },
    /**
     * Check if the src widget is the one that triggered the listener
     * @private
     */
    outgoingChange: function (listener) {
      return {
        event: 'change',
        /**
         *  outgoingChange is fired when this widget requests another widget to change its view
         *  The this keyword in the event callback refers to the {@link widget} object
         * @name widgetEvents#outgoingChange
         * @event
         * @param {Object} e {@link eventObject}
         * @param {String} dstName Type of the widget that is requested to change
         * @param {String} dstView View of the widget that is requested to change
         */
        fn: function (e) {
          if (window.breakEvent)
            debugger;
          if (e.srcWidget === listener.widget.name && compare_same_view(e, listener.widget.view) && e.dstWidgetType !== listener.widget.type) {
            return listener.fn.call(listener.widget, e, e.dstWidgetType, e.dstView, listener) || true;
          }
          return true;
        }
      }
    },
    resize: function (listener) {
      return {
        event: 'widgetDimensions',
        /**
         *  resize is fired when this widget is requested to change it's dimensions
         *  This is triggered by the first rendering as well
         *  The this keyword in the event callback refers to the {@link widget} object
         * @name widgetEvents#resize
         * @event
         * @param {Object} e {@link eventObject}
         * @param {Number} width new width in px
         * @param {Number} height new height in px
         */
        fn: function (e) {
          if (window.breakEvent)
            debugger;
          if (e.srcWidget === listener.widget.name && compare_same_view(e, listener.widget.view)) {
            return listener.fn.call(listener.widget, e, e.width, e.height, listener) || true;
          }
          return true;
        }
      }
    },
    cancel: function (listener) {
      return {
        event: 'cancel',
        /**
         *  cancel is fired when the widget has a close button and it is pressed
         *  The this keyword in the event callback refers to the {@link widget} object
         * @name widgetEvents#cancel
         * @event
         * @param {Object} e {@link eventObject}
         */
        fn: function (e) {
          if (window.breakEvent)
            debugger;
          if (e.srcWidget === listener.widget.name && compare_same_view(e, listener.widget.view)) {
            return listener.fn.call(listener.widget, e, listener) || true;
          }
          return true;
        }
      }
    }
  }


  /**
   *  Widget event handler
   *  @constructor Widget Event Handler
   *  @name widgetEvents
   *  @public
   */
  var widgetEvents = function () {
    this.events = {};
  }

  /**
   *  Define a new event listener for all widgets.
   */
  widgetEvents.prototype.listen = function (event, listener) {
    if (!listener || typeof(listener) !== 'object') {
      console.log('widgetEvents.listen() expects the second argument to be an options object');
    }
    // the provided listener function is wrapped so we can call it with the eventhub data and the original listener properties
    var wrapped_listener = function (data) {
      if (window.breakAllEvent)
        debugger;
      return listener.fn.call(listener.widget, data, listener);
    }
    var customEvent;
    // check if there is a custom event with this name, if so map it to the proper wrapper
    if (typeof(customEvents[event]) === 'function') {
      customEvent = customEvents[event](listener);
      event = customEvent.event;
      wrapped_listener = customEvent.fn;
    }
    if (typeof(this.events[event]) === 'undefined') {
      this.events[event] = [];
    }
    this.events[event].push({
      fn: wrapped_listener,
      id: listener.id
    });
  }
  
  widgetEvents.prototype.unsubscribe = function (id, event) {
    var self = this
    , unsub = function (name) {
      var events = self.events[name];
      for (var i = 0, len = events.length; i < len; i++) {
        if ( events[i].id === id ) {
          self.events[name].splice(i, 1);
          len--;
        }
      }
    }
    if (event) {
      unsub(event);
    } else {
      $.each(this.events, function (i, val) {
        unsub(i);
      });
    }
  }

  /**
   *  Trigger the internal event listeners. (not the direct listeners but their wrappers that can check if the event is appropriate)
   */
  widgetEvents.prototype.trigger = function (event, data) {
    if ( ! this.events[event]) {
      return true;
    }
    var events = this.events[event]
    , preventDefault = false;
    /**
     *  This stops the default event handler of the original widget system.
     * @name eventObject#preventDefault
     * @function
     */
    data.preventDefault = function () {
      preventDefault = true;
    }
    for (var i = 0, len = events.length; i < len; i++) {
      if ( events[i].fn(data) === false )
        return false;
    }
    return !preventDefault;
  }

  window.widgetEventHandler = new widgetEvents();

  var overrides = [
    {
      originalName: 'changeWidget', 
      eventName: 'change'
    },
    {
      originalName: 'changeView', 
      eventName: 'view'
    },
    {
      originalName: 'widgetDimensions'
    },
    {
      originalName: 'cancel'
    },
    {
      originalName: 'displayItemDetails'
    },
    {
      originalName: 'changeCatalogWidget'
    },
    {
      originalName: 'popup'
    },
    {
      originalName: 'login', 
      type: 'state'
    },
    {
      originalName: 'logout', 
      type: 'state'
    },
    {
      originalName: 'sessionState', 
      type: 'state'
    },
    {
      originalName: 'loginRequired', 
      type: 'state'
    },
    {
      originalName: 'pointsChanged', 
      type: 'state'
    },
    {
      originalName: 'messageDetails'
    },
    {
      originalName: 'pointsBalance', 
      type: 'state'
    },
    {
      originalName: 'memberInfoChanged', 
      type: 'state'
    },
    {
      originalName: 'profileChanged', 
      type: 'state'
    }
  ];
  
  var overrider = function (override) {
    var oName = override.originalName
    , eName = override.eventName || override.originalName
    , type = override.type || 'ui';
    window['ko_platform_'+type+'_'+oName+'_override'] = function (data) {
      return widgetEventHandler.trigger(eName, data);
    }
  }
  
  for (var i = 0, len = overrides.length, type, oName, eName; i < len; i++) {
    if (overrides.hasOwnProperty(i)) {
      overrider(overrides[i]);
    }
  }
  
  window['mcb_subscribedToTopic_override'] = function (data) {
    return widgetEventHandler.trigger('subscribedToTopic', data);
  }
 
 
  // this hacks into jquery to override hiding of widgets. 
  // we don't do display:none for them and instead put them outside the viewport. 
  // this makes it possible for the widgetdimensions event to return the correct width/height.
  var realHide = jQuery.fn.hide,
      realShow = jQuery.fn.show,
      hideProps = {
        position: 'fixed',
        left: '-100000px',
        top: '-100000px',
        marginLeft: 0,
        marginTop: 0
      };
     
  var getPosition = function ($elem) {
    var coords = $elem.position(),
        position = $elem.css('position');
    return {
      position: position,
      left: coords.left,
      top: coords.top
    };
  }
      
  jQuery.fn.hideByPosition = function () {
    if (this.is(':visible') && ! this.data('oldPosition')) {
      var old = getPosition(this);
      if (old.left+'px' !== hideProps.left) {
        this.data('oldPosition', old);
      }
    }
    return this.css(hideProps);
  }
  
  jQuery.fn.showByPosition = function () {
    var data = this.data('oldPosition');
    return data === null ? this : this.css(data);
  }
  
  jQuery.fn.hide = function () {
    if (this.hasClass('widget_wrapper') && typeof (this.parent().data('widgetState')) !== 'undefined') {
      return this.hideByPosition();
    }
    return realHide.apply(this, arguments);
  }
  
  jQuery.fn.show = function () {
    if (this.hasClass('widget_wrapper') && typeof (this.parent().data('widgetState')) !== 'undefined') {
      return this.showByPosition();
    }
    return realShow.apply(this, arguments);
  }
  
  /**
   * If options is provided this function creates a widget with the given options and returns the jquery object.<br/>
   * If no options are provided this returns the widgetState object of the selected element (if there is one).
   * @name jQuery#widget
   * @function
   * @param {Object} options
   *                This is an object containing all the options needed to properly render and display the widget.<br/>
   *                Possible options are:<br/>
   *                <ul>
   *                  <li><b>name</b>: Name of the widget to be rendered</li>
   *                  <li><b>startView</b>: Initial view that is rendered</li>
   *                  <li><b>params</b> (optional): Parameters for the widget (as you'd pass them to gim.renderWidget)</li>
   *                  <li><b>css</b> (optional): Some styling rules</li>
   *                  <li><b>loading</b> (optional): Whether there should be a loading div.
   *                      <br/>If you set this to a string, the string will be used as the classname for the loading div.
   *                      <br/>If it's not a string but evaluates to true the div will have a class of "widget_loading".</li>
   *                  <li><b>events</b> (optional): Object containing all the event handlers you want to define. {@link widgetEvents}</li>
   *                </ul>
   */
  jQuery.fn.widget = function( options ) {
    if (!options || typeof(options) !== 'object') {
      return this.data('widgetState');
    }
    var events = options.events || []
    , widgetType = gim.getConfigByProperty({
      name: options.name
    }).type;

    if (widgetType === null) {
      throw new Error('$.widget() could not find a widget type with the provided name.');
    }

    return this.each(function () {
      var $this = $(this)
      , randId = 'widget_'+(+new Date())+''+Math.floor(Math.random()*1000)
      , $loadingDiv = null
      , $closer = null
      , $wrapperDiv
      , widgetState;

      $wrapperDiv = $('<div/>', {
        id: randId+'_wrapper',
        'class': 'widget_wrapper'
      });

      if (options.loading) {
        $loadingDiv = $('<div/>', {
          id: randId+'_loading',
          'class': typeof(options.loading) === 'string'
                    ? options.loading
                    : 'widget_loading'
        });
      }

      if (options.closeButton) {
        $closer = $('<a/>', {
          id: randId+'_close',
          'class': typeof(options.closeButton) === 'string'
                    ? 'widget_closer ' + options.closeButton // both here to make assigning the click handler easier
                    : 'widget_closer',
          href: 'javascript:'
        }).hide().text('Schließen');
      }

      $this .append($wrapperDiv)
            .append($loadingDiv)
            .append($closer);


      widgetState = new widget(options.name, $wrapperDiv, $loadingDiv, $closer, randId);
      
      if (!widgetState) // widget failed to initialize
        return false;
      
      $this.data('widgetState', widgetState);

      $.each(events, function (i, val) {
        widgetEventHandler.listen(i, {
          fn: val,
          elem: $wrapperDiv,
          widget: widgetState,
          id: randId
        });
      });
      
      widgetState.render(options.startView || 'start', options.params);
      $wrapperDiv.height(options.startHeight || 50);

      // if a loading div was requested we hide the widget wrapper, show the loading div and subscribe to the resize event to show the widget then.
      if ($loadingDiv && ! options.hide) {
        widgetState.showLoader();
      }
      widgetEventHandler.listen('resize', {
        elem: $wrapperDiv,
        widget: widgetState,
        fn: function (e, width, height) {
          
          $wrapperDiv.height(height);
          
          var showWidget = e.srcView !== 'empty' &&
              ( $loadingDiv && $loadingDiv.is(':visible') );
          
          if ($loadingDiv)
            $loadingDiv.hide();
          
          if (showWidget) {
            if ($closer)
              $closer.show();
            $wrapperDiv.show();
          }
        }
      });

      if (options.css) {
        $wrapperDiv.css(options.css);
      }
      if (options.hide) {
        $wrapperDiv.hide();
        if ($loadingDiv) {
          $loadingDiv.hide();
        }
      }
    });
  };

})( jQuery );



// make sure we have console.xxx for debugging in ie, ff w/out firebug etc.
if (typeof (console) === 'undefined' || typeof (console.log) === 'undefined') {
  debugger; // should probably leave this in here to make the developer aware that this is happening!
  $('<div id="debug_console" style="display:none; position:absolute; top: 0; left: 0; z-index: 10000; width: 100%; height: 100%;"></div>').appendTo('body');
  var $debug_console = $('#debug_console');
  var console = function () {
    this.buffer = [];
  }
  console.prototype.info = console.prototype.warn = console.prototype.error = console.prototype.dir = console.prototype.log = function () {
    var args = Array.prototype.slice.call(arguments)
    , self = this;
    $.each(args, function (i, val) {
      self.buffer.push(val);
      $debug_console.append('<p>'+val[0]+'</p>');
    })
  };
  console.prototype.dump = function () {
    var buffer = this.bufffer;
    this.buffer = [];
    return buffer;
  }
  window.console = new console();
  window.show_console = function () {
    $debug_console.show();
  }
  window.hide_console = function () {
    $debug_console.hide();
  }
}
