/**
 * @version 2.1
 * @versionDevelopment 2.1.0
 * Copyright Coca-Cola Company
 * This files contains utility functions that help market sites to load widgets both as inline and popup widgets.
 * This also helps in handling topics.
 */

/**
 * Create gim namespace
 */
var gim = function(){

    /**
     * Widget object that will store instances of each widget created using AddWidget() constructor function
     * @public
     */
    var widget = {};

    /**
     * Hub Client (Listner) object, which basically listen to each and every topic and pass it on to handleTopics() function
     * Also point to window.hubClient (used by Widget Explorer)
     * @public
     */
    var hubClient;

    /**
     * Wrapper CSS classes (defined in HTML itself) will be preserved in this object
     * to avoid getting overwritten by custom classes
     * @private
     */
    var _wrapperHtmlCss = {};

    /**
     * Topic Queue will stores last topic that is published
     * Used for duplicate topic handling
     * @private
     */
    var _topicQueue = [];

    /**
     * Lister Object hold list of all widget listerners events
     * @private
     */
    var _listeners = {};

    /**
     * List of valid events
     * @private
     */
    var _events = {
        'onWidgetStart' : true,
        'onWidgetEnd' : true,
        'onWidgetConn' : true,
        'onWidgetClose' : true
    };

    /**
     * Default Settings in case any property missing from settings.js
     * Also, widget will fall back to default settings if settings.js file is missing
     * @private
     */
    var _defaultSetting = {
        hubConnectionTimeout : 60000,
        locale : 'en_US',
        inputFormat : 'html',
        outputFormat : 'html',
        isInline : true,

        minWidgetHeight : 150,
        minWidgetWidth : 200,

        outerOverlayWrapper : {
            'id':'widgetOuterOverlayWrapper',
            'width': 300,
            'height': 150
        }
    };

    /**
     * Unique ID value is added in various place to create unique ID/widget
     * @private
     */
    var _uniId = 1;

    /**
     * Enable/Disable logger function
     * @private
     */
    var _debug = true;

    /**
     * Create Managed Hub object
     * Define Callback methods for Managed Hub connection
     */

    /**
     * Callback for publish requests. return true will approve all publish requests.
     * @params topic, data, publishContainer, subscribeContainer
     */
    var onMHPublish = function() {
        return true;
    };

    /**
     * Callback for subscribe requests. return true will approve all subscribe requests.
     * @params topic, container
     */
    var onMHSubscribe = function() {
        return true;
    };

    /**
     * Callback for unsubscribe requests. return true will approve all unsubscribe requests.
     * @param topic, container
     */
    var onMHUnsubscribe = function() {
        return true;
    };

    /**
     * Callback for security alerts
     * @param source, alertType
     */
    var onMHSecurityAlert = function() {
        return true;
    };

    /** Initializes Agency Hub */
    var manageAgencyHub = new OpenAjax.hub.ManagedHub({
        onPublish:       onMHPublish,
        onSubscribe:     onMHSubscribe,
        onUnsubscribe:   onMHUnsubscribe,
        onSecurityAlert: onMHSecurityAlert
    });

    /** Create Listner inline Hub object *******************************************/

    /* Listner container callback functions */
    var onListnerConnect = function(container) {};
    var onListnerDisconnect = function(container) {};
    var onListnerSecurityAlert = function(source, alertType) {};

    /**
     * Callback method that will redirect all topics to handleopics method
     */
    var onListnerHubConnect = function(hubClient, success, error ) {
        if (success) {
            hubClient.subscribe('**', handleTopics);
        }
    };

    /**
     * Create inline container client & Hub and subscribe to all topics
     * This connection actually works as a central listner for all topics
     */
    var init = function() {

        // Check to make sure we don't cause duplicate Hub error
        if(typeof hubClient === 'undefined'){
            // Create inline container client
            var listnerContainer = new OpenAjax.hub.InlineContainer(manageAgencyHub , 'listnerClient', {
                Container: {
                    onConnect:       onListnerConnect,
                    onDisconnect:    onListnerDisconnect,
                    onSecurityAlert: onListnerSecurityAlert
                }
            });

            // Create inline hub client
            hubClient = new OpenAjax.hub.InlineHubClient({
                HubClient: {
                    onSecurityAlert: onListnerSecurityAlert
                },
                InlineHubClient: {
                    container: listnerContainer
                }
            });
            // Connect to the ManagedHub
            hubClient.connect( onListnerHubConnect );
            // Store as global object for backward compatibility
            window.hubClient = hubClient;
        }

        // Re-write self so it can be initialized only once
        gim.init = function(){};
    };

    /**
     * This will render a new widget in current window. renderWidget() always call removeWidget
     * to make sure there is no duplicate instances of same widget.
     * @param {string} name Widget name
     * @param {string} view Widget View name
     * @param {string} locale Language in which widget to render default to 'en_US'
     * @param {string} param Additional query parameter that needs to be appended to a widget while rendering
     * @param {string} wrapper Wrapper <div> ID in which widget needs to be rendered
     * @public
     *  */
    var renderWidget = function(name, view, locale, param, wrapper){
        logger.info(name + '_' + view + ' [START]', arguments);

        /**
         * Perform validation for mandatory parameters
         */
        if(!isSet(name) || !isSet(view)){
            logger.warn('Render Widget Failed - name or view missing', arguments);
            return false;
        }

        /**
         * Increment the ID to keep it unique
         */
        _uniId++;

        /**
         * Generate Unique Widget ID
         */
        var widgetId = _returnWidgetId(name, view);

        /**
         * Perform cleanup, remove any object which is no longer required
         */
        var _wrapper = getWrapper(name, view, wrapper, false);
        for(var key in gim.widget){
            if(isSet(gim.widget[key].setting.wrapper.id) && gim.widget[key].setting.wrapper.id == _wrapper){
                removeWidget(_returnWidgetId(gim.widget[key].name, gim.widget[key].view), false);
            }
        }

        /**
         * Remove widget if it already exist, this is done to make sure, duplicated instances are not created for same widget
         */
        removeWidget(widgetId, false);

        /**
         * Create a new widget object and add it to widget object
         */
        widget[widgetId] = new AddWidget({
            'id' : widgetId,
            'name': name,
            'view' : view,
            'locale' : locale,
            'param' : param,
            'wrapper' : wrapper,
            'defaultSetting' : _defaultSetting
        });

        /**
         * If AddWidget failed, remove current widget from gim.widget
         */
        if(!isSet(widget[widgetId].container) ||  widget[widgetId].container === null){
            removeWidget(widgetId, false);
            return false;
        }

        return true;
    };

    /********************************************************************************************************
     * ADD WIDGET CONSTRUCTOR FUNCTION START
     ********************************************************************************************************/

    /**
     * This is the primary function/class that will create widget object
     * @param {object} args Arguments
     * - {string} args.id Unique widget ID
     * - {string} args.name Widget name
     * - {string} args.view Widget View name
     * - {string} args.locale Language in which widget to render default to 'en_US'
     * - {string} args.param Additional query parameter that needs to be appended to a widget while rendering
     * - {string} args.wrapper Wrapper <div> ID in which widget needs to be rendered
     * @public
     */
    var AddWidget = function(args){

        /*
         * Set widget defaults
         */
        this.id = args.id;
        this.name = args.name;
        this.view = args.view;

        /**
         * Get the setting values from settings.js
         */
        this.setting = getSetting(this.name, this.view);

        this.setting.locale = args.locale || window.widgetLocale || this.setting.locale || args.defaultSetting.locale;

        this.setting.parameters = args.param || this.setting.parameters;

        /* Default value for connection timeout (milliseconds) */
        if(!isSet(this.setting.hubConnectionTimeout)){
            this.setting.hubConnectionTimeout = args.defaultSetting.hubConnectionTimeout;
        }

        if(!isSet(this.setting.inputFormat)){
            this.setting.inputFormat = args.defaultSetting.inputFormat;
        }

        if(!isSet(this.setting.outputFormat)){
            this.setting.outputFormat = args.defaultSetting.outputFormat;
        }

        if(!isSet(this.setting.isInline)){
            this.setting.isInline = args.defaultSetting.isInline;
        }

        /**
         * Dispatch onWidgetStart event
         */
        dispatchEvent.call(this, 'onWidgetStart', {
            'eventType' : 'onWidgetStart',
            'widget':{
                'id' : this.id,
                'name' : this.name,
                'view' : this.view
            },
            'setting' : this.setting
        });

        /*
         * Check widget URL, if it exists, do re-direct
         */
        if(isSet(this.setting.url)){
            window.location.href = this.setting.url;
            return true;
        }

        /*
         * Get wrapper element
         */
        this.setting.wrapper = getWrapper(this.name, this.view, args.wrapper);

        this.setting.wrapperBGIframe = getWrapperBGIframe.apply(this);

        /**
         * Set the widget wrapper properties/look and feel
         */
        readyWrapper.apply(this);

        /**
         * Get widget URI from configurables
         */
        if((this.url = getWidgetURL.apply(this)) === null){
            logger.warn('Widget url is null.');
            return false;
        }

        /**
         * Callback function for widget connect
         */
        this.onWidgetConnect = function (container) {

            /**
             * Get Widget Object
             */
            var _widget = getWidgetByClientId(container.getClientID());

            showHideOverlay(false, 'in', container.getIframe().parentNode);

            /**
             * Dispatch onWidgetConn event
             */
            dispatchEvent.call(_widget, 'onWidgetConn', {
                'eventType' : 'onWidgetConn',
                'widget':{
                    'id' : _widget.id,
                    'name' : _widget.name,
                    'view' : _widget.view
                },
                'setting' : _widget.setting,
                'container' : _widget.container
            });

            logger.info(_widget.id + ' [END]');
        };

        /**
         * Callback function for widget disconnects
         */
        this.onWidgetDisconnect = function(container) {
            return true;
        };

        /**
         * Callback function to handle client-side security alerts
         */
        this.onWidgetSecurityAlert = function(container, alertType) {
            showHideOverlay(false, 'in', container.getIframe().parentNode);

            handleSecurityAlert({
                'container' : container,
                'alertType' : alertType,
                'widget' : getWidgetByClientId(container.getClientID())
            });
        };

        try{
            this.container = new OpenAjax.hub.IframeContainer(manageAgencyHub , 'widgetID' + _uniId, {
                Container: {
                    onConnect:          this.onWidgetConnect,
                    onDisconnect:       this.onWidgetDisconnect,
                    onSecurityAlert:    this.onWidgetSecurityAlert
                },
                IframeContainer: {
                    // DOM element that is parent of this container:
                    parent:             this.setting.wrapper,
                    // Container's iframe loads the following URL:
                    uri:                this.url,
                    // Tunnel URL required by IframeHubClient. This particular tunnel URL
                    // is the one that corresponds to release/all/OpenAjaxManagedHub-all.js:
                    tunnelURI:          window.tunnelURI,
                    // If the client at uri does not establish a connection with this
                    // container in the given time (in milliseconds), the onSecurityAlert callback is
                    // called with a LoadTimeout error code.
                    timeout:            this.setting.hubConnectionTimeout,
                    // Container's iframe will have these CSS styles:
                    iframeAttrs:        isSet(this.setting.iframeAttributes)? this.setting.iframeAttributes : window.iframeAttributes
                }
            });

        }
        catch(e){
            logger.warn('Widget Rendering failed with error >> ' + e.message);
            return false;
        }

        /**
         * Set focus on the widget iframe, so that any focus on field within iframe will start work
         */
        this.container.getIframe().contentWindow.focus();

        /**
         * Dispatch onWidgetEnd event
         */
        dispatchEvent.call(this, 'onWidgetEnd', {
            'eventType' : 'onWidgetEnd',
            'widget':{
                'id' : this.id,
                'name' : this.name,
                'view' : this.view
            },
            'setting' : this.setting,
            'container' : this.container
        });

        return true;
    };

    /**
     * This will reset the wrapper width and height as per the parameters
     * @param {object} size New Dimensions
     * - {string} height New Width
     * - {string} h New Height
     * - {string} unit Unit Type
     */
    AddWidget.prototype.resizeWrapper = function(size){
        /**
         * Perform Validation
         */
        if(!isObject(size)){
            return false;
        }

        size.unit = size.unit || 'px';

        /**
         * get BG iframe increment value
         */
        var incBGIframe = (isSet(this.setting.wrapperIe6IframeFix) && isSet(this.setting.wrapperIe6IframeFix.inc))? parseInt(this.setting.wrapperIe6IframeFix.inc, 10) : 0;

        if(!isEmpty(size.width)){
            /**
             * Check if the width is passed with unit or without unit
             */
            size.width = isNaN(size.width)? size.width : parseInt(size.width, 10) + size.unit;
            this.setting.wrapper.style.width = size.width;

            if(!isEmpty(this.setting.wrapperBGIframe)){
                /**
                 * Get unit type
                 */
                size.unit = !isEmpty(size.width.replace(/[0-9]/g, ''))? size.width.replace(/[0-9]/g, '') : size.unit;
                this.setting.wrapperBGIframe.style.width = parseInt(size.width, 10) + incBGIframe + size.unit;
            }
        }

        if(!isEmpty(size.height)){
            /**
             * Check if the width is passed with unit or without unit
             */
            size.height = isNaN(size.height)? size.height : parseInt(size.height, 10) + size.unit;
            this.setting.wrapper.style.height = size.height;

            if(!isEmpty(this.setting.wrapperBGIframe)){
                /**
                 * Get unit type
                 */
                size.unit = !isEmpty(size.height.replace(/[0-9]/g, ''))? size.height.replace(/[0-9]/g, '') : size.unit;
                this.setting.wrapperBGIframe.style.height = parseInt(size.height, 10) + incBGIframe + size.unit;
            }
        }

        return true;
    };

    AddWidget.prototype.resizeDimension = function(w, h){
        if(this.setting.isInline === false && isSet(this.setting.resizeWidgetDimensions) && this.setting.resizeWidgetDimensions !== false){
            if(!isEmpty(w)){
                w = (isSet(this.setting.resizeWidgetDimensions.width)) ? w + this.setting.resizeWidgetDimensions.width : w;
            }
            if(!isEmpty(h)){
                h = (isSet(this.setting.resizeWidgetDimensions.height)) ? h + this.setting.resizeWidgetDimensions.height: h;
            }
            this.resizeWrapper({
                'width':w,
                'height':h
            });
        }
    };

    /**
     * This will reload the widget iframe
     */
    AddWidget.prototype.reload = function(){
        try{
            this.container.getIframe().contentWindow.location = updateUrlParam(this.container.getIframe().src, {
                'ieFix': Math.floor(Math.random()*101)
            });
        }
        catch(e){
            logger.warn('Iframe reload failed with error >> ' + e.message);
        }
    };

    /**
     * This will reposition widget to supplied top/left
     * @param {ovbect} pos Position object with top/left/unit properties
     */
    AddWidget.prototype.repositionWrapper = function(pos){
        /**
         * Check for popup widget
         */
        if(this.setting.isInline === false){
            /**
             * Set teh default unit to px
             */
            pos.unit = pos.unit || 'px';

            /**
             * Set top position
             */
            if(isSet(pos.top)){

                pos.top = isNaN(pos.top)? pos.top : parseInt(pos.top, 10) + pos.unit;

                this.setting.wrapper.style.top = pos.top;

                if(!isEmpty(this.setting.wrapperBGIframe)){
                    this.setting.wrapperBGIframe.style.top = pos.top;
                }
            }

            /**
             * Set left position
             */
            if(isSet(pos.left)){

                pos.left = isNaN(pos.left)? pos.left : parseInt(pos.left, 10) + pos.unit;

                this.setting.wrapper.style.left = pos.left;

                if(!isEmpty(this.setting.wrapperBGIframe)){
                    this.setting.wrapperBGIframe.style.left = pos.left;
                }
            }
        }
    }

    /**
     * This will reposition widget to center
     */
    AddWidget.prototype.reposition = function(){

        /**
         * Check if the current widget is a popup widget
         */
        if(this.setting.isInline === false){

            /**
             * Check if the repositionWidget property is true
             */
            if(isSet(this.setting.repositionWidget) && this.setting.repositionWidget === true){
                this.repositionWrapper(repositionElement({
                    'p':window.document,
                    'c':this.setting.wrapper
                }));
            }

            /**
             * Check for IE6 Browser + Overlay wrapper exist
             * If found, change its height and width to fixed pixel
             */
            if (/MSIE 6/i.test(navigator.userAgent) && document.getElementById(gim.defaultSetting.outerOverlayWrapper.id) !== null) {
                var eF = document.getElementById(gim.defaultSetting.outerOverlayWrapper.id);
                eF.style.width = document.documentElement.clientWidth;
                eF.style.height = getDimension.call(window.document,'Height');
            }
        }
    };

    /********************************************************************************************************
     * ADD WIDGET CONSTRUCTOR FUNCTION END
     ********************************************************************************************************/

    /*
     * This is HubConnect callback method, which will handle all topics
     * - It can be overridden by defining handleTopics_override function
     * - Specific topic handling functionality can be overridden by defining
     *   [topic]_override function (e.g. ko_platform_ui_changeWidget__override)
     * @param {string} topic Topic name
     * @param {object} data Topic data
     * @param {boolean} recCall Check if this is a recursive call or not
     */
    var handleTopics = function(topic, data, recCall) {
        logger.log('[TOPIC]', topic, data, recCall);

        /** Stores current widget config data from settings.js */
        var srcWidgetConfig = getSetting(data.srcWidget, data.srcView);

        /**
         * Check for mapping, if exist, change mapped data value
         */
        if(!isEmpty(srcWidgetConfig) && isSet(srcWidgetConfig.mapping)){
            for(var key in data){
                if(isSet(srcWidgetConfig.mapping[key]) && isSet(srcWidgetConfig.mapping[key][data[key]])){
                    logger.warn('[Override] Topic Data', topic, key, data);
                    data[key] = srcWidgetConfig.mapping[key][data[key]];
                }
            }
        }

        // Check and execute if handleTopics_override function exist
        if(typeof handleTopics_override === 'function'){
            logger.warn('[Override] handleTopics', topic, data);
            handleTopics_override(topic, data);
            return true;
        }

        // Check and execute if topic specific function exist
        if(typeof window[(topic.replace(/\./g, '_') + '_override')] === 'function'){
            logger.warn('[Override] Topic - ' + topic, data);
            if(!window[(topic.replace(/\./g, '_') + '_override')](data, topic)){
                return true;
            };
        }

        /**
         * Check if the current topic is duplicate and checkDupTopic is NOT false
         */
        if(typeof _topicQueue[0] !== 'undefined' && _topicQueue[0] === topic){

            if(typeof window.widget_default_config !== 'undefined' && typeof window.widget_default_config.dupTopic === 'boolean' && window.widget_default_config.dupTopic !== true){
                return true;

            }else if(typeof window.widget_default_config !== 'undefined' && typeof window.widget_default_config.dupTopic === 'function'){
                /**
                 * Execute callback function and check for return,
                 * if return true, exit handleTopics else continue normal execution
                 */
                if(!window.widget_default_config.dupTopic(data, topic)){
                    return true;
                }

            /**
                 * Check if callback function exist for duplicate topic
                 */
            }else if(typeof window.widget_default_config !== 'undefined' && typeof window.widget_default_config.dupTopic === 'object' && typeof window.widget_default_config.dupTopic[topic] === 'function'){
                /**
                 * Execute callback function and check for return,
                 * if return true, exit handleTopics else continue normal execution
                 */
                if(!window.widget_default_config.dupTopic[topic](data, topic)){
                    return true;
                }

            /**
                 * If no function is defined, but value is true, continue handleTopic execution
                 */
            }else if(typeof window.widget_default_config !== 'undefined' && (typeof window.widget_default_config.dupTopic === 'object' && typeof window.widget_default_config.dupTopic[topic] !== 'undefined')){
                return true;
            }
        }
        _topicQueue[0] = topic;

        /** Stores current widget name */
        var widgetName;

        /** Stores current widget change URL */
        var widgetUrl = appendWidgetUrl(data);

        /**
         * Close source widget for certain topics, if it is a popup widget
         */
        if(topic == 'ko.platform.ui.changeWidget' || topic == 'ko.platform.ui.displayItemDetails' || (isSet(window.widget_default_config) && isSet(window.widget_default_config.onTopicWidgetClose) && isSet(window.widget_default_config.onTopicWidgetClose[topic]))){
            /**
             * Get Source Widget
             */
            var _widget = getWidget({
                'name' : data.srcWidget,
                'view' : data.srcView
            });

            /**
             * Check if source widget is available and is popup
             */
            if(!isEmpty(_widget) && _widget.setting.isInline === false){
                /**
                 * Close Widget
                 */
                closeWidget({
                    'name' : _widget.name,
                    'view' : _widget.view,
                    'remWrp' : true
                });
            }
        }

        // Action on each topic
        if(topic == 'ko.platform.ui.changeWidget') {
            // Check if widget URL change is required
            if(widgetUrl !== null && widgetUrl !== false){
                window.location = widgetUrl;
                return true;
            }

            widgetName = getConfigByProperty({
                'srcWidget' : data.srcWidget,
                'type' : data.dstWidgetType
            }, 'type').name;

            renderWidget(widgetName, data.dstView);

        } else if (topic == 'ko.platform.state.login') {

            /**
             * get Login widget
             */
            var _widget = getWidget({
                'name' : data.srcWidget,
                'view' : data.srcView
            });

            /**
             * If login widget is popup, close it
             */
            if(!isEmpty(_widget) && _widget.setting.isInline === false){
                closeWidget({
                    'name' : _widget.name,
                    'view' : _widget.view,
                    'remWrp' : true
                });
            }

            /**
             * Refresh Widgets
             */
            var _refresh = {};
            _refresh[getConfigByProperty({
                'type' : 'ProfileWidget'
            }).name] = true;
            for(var key in gim.widget){
                if(isSet(_refresh[gim.widget[key].name]) && gim.widget[key].setting.isInline === true){
                    gim.widget[key].reload();
                }
            }
			
        } else if (topic == 'ko.platform.state.loginRequired') {
            renderWidget(getConfigByProperty({
                'type' : 'LoginWidget'
            }).name, 'login');
			
        } else if (topic == 'ko.platform.state.logout') {
            renderWidget(data.srcWidget, 'start');

        } else if (topic == 'ko.platform.ui.changeView') {
            renderWidget(data.srcWidget, data.dstView);

        } else if(topic == 'ko.platform.ui.popup' || topic == 'ko.platform.ui.osso') {
            // Check if widget URL change is required
            if(widgetUrl !== null && widgetUrl !== false){
                window.location = widgetUrl;
                return true;
            }

            widgetName = getConfigByProperty({
                'srcWidget' : data.srcWidget,
                'type' : data.dstWidgetType
            }, 'type').name;

            isSet(data.locale)? renderWidget(widgetName, data.dstView, data.locale, 'url=' + data.url) : renderWidget(widgetName, data.dstView, null, 'url=' + data.url);

        } else if (topic == 'ko.platform.ui.redirect') {
            window.open(data.url);

        } else if(topic == 'ko.platform.ui.changeCatalogWidget') {
            // Check if widget URL change is required
            if(widgetUrl !== null && widgetUrl !== false){
                window.location = widgetUrl;
                return true;
            }

            widgetName = getConfigByProperty({
                'type' : data.dstWidgetType
            }).name;

            if(!isEmpty(data.ticket)) {
                renderWidget(widgetName, data.dstView, null, 'ticket=' + data.ticket);
            } else {
                renderWidget(widgetName, data.dstView, null, 'prizeId=' + data.itemId + '&showPrizeInfo=true');
            }

        } else if(topic == 'ko.platform.ui.displayItemDetails') {
            // Check if widget URL change is required
            if(widgetUrl !== null && widgetUrl !== false){
                window.location = widgetUrl;
                return true;
            }

            widgetName = getConfigByProperty({
                'type' : data.dstWidgetType
            }).name;
            renderWidget(widgetName, data.dstView, null, 'itemId=' + data.itemId);

        } else if(topic == 'ko.platform.ui.widgetDimensions') {
            var _widget = getWidget({
                'name' : data.srcWidget,
                'view' : data.srcView
            });

            /**
             * If widget exist with the name and view return by topic, do resizing and repositioning
             * ELSE
             * Loop through all widget, do resizing and repositioning for all the widgets
             * where name = data.srcWidget and the widget is a popup widget
             *
             * This is done to fix issue when the widget view is changed internally at server side
             */
            if(!isEmpty(_widget)){
                _widget.resizeDimension(data.width, data.height);
                _widget.reposition();
            }else{
                for(var key in gim.widget){
                    if(gim.widget[key].name === data.srcWidget && gim.widget[key].setting.isInline === false){
                        gim.widget[key].resizeDimension(data.width, data.height);
                        gim.widget[key].reposition();
                    }
                }
            }

        } else if(topic == 'ko.platform.ui.cancel') {
            var _widget = getWidget({
                'name' : data.srcWidget,
                'view' : data.srcView
            });

            /**
             * If widget exist with the name and view close the widget
             * ELSE
             * Loop through all widget, close all the widgets
             * where name = data.srcWidget and the widget is a popup widget
             *
             * This is done to fix issue when the widget view is changed internally at server side
             */
            if(!isEmpty(_widget)){
                closeWidget({
                    'name' : _widget.name,
                    'view' : _widget.view,
                    'remWrp' : true
                });
            }else{
                for(var key in gim.widget){
                    if(gim.widget[key].name === data.srcWidget && gim.widget[key].setting.isInline === false){
                        closeWidget({
                            'name' : data.srcWidget,
                            'remWrp' : true
                        });
                    }
                }
            }

        } else if(topic == 'ko.platform.ui.displayListItems') {
            // Check if widget URL change is required
            if(widgetUrl !== null && widgetUrl !== false){
                window.location = widgetUrl;
                return true;
            }
            widgetName = getConfigByProperty({
                'type' : data.dstWidgetType
            }).name;
            renderWidget(widgetName, data.dstView, null, 'categoryId=' + data.categoryId + '&subCategoryId=' + data.subCategoryId + '&subSubCategoryId=' + data.subSubCategoryId + '&searchText=' + data.searchText  + '&typeOfStructure=' + data.typeOfStructure);

        } else if(topic == 'ko.core.app.enable.cookies') {
            /**
             * Check for callback function
             */
            if(isSet(window.cookieFired) && window.cookieFired === true){
                /**
                 * Execute callback function and check for return,
                 */
                if(isSet(window.widget_default_config) && typeof window.widget_default_config.dupTopic === 'object' && typeof window.widget_default_config.dupTopic[topic] === 'function'){
                    window.widget_default_config.dupTopic[topic](data, topic)
                }

            }else{
                var cookieDisableMsg = (isSet(window.widget_default_config) && isSet(window.widget_default_config.cookieDisableMsg))? window.widget_default_config.cookieDisableMsg : "Cookies are Disabled: \nWe're sorry, but we are unable to process your request \nbecause cookies are currently disabled in your web browser, \nor you have Internet security software that is preventing \nthem from reaching us.!";
                alert(cookieDisableMsg);
            }

            /**
             * Create cookieFired flag
             */
            window.cookieFired = true;

        } else if(topic == 'ko.platform.state.pointsChanged') {
		if(isSet(data.srcWidgetType) && data.srcWidgetType === 'OutboundSSOWidget'){
                        closeWidget({
                            'name' : getConfigByProperty({
			                'type' : data.srcWidgetType
       				}).name,
                            'remWrp' : true
                        });
		}

        } else if(topic == 'ko.core.app.load.failure') {
            logger.warn('Widget failed to load from server.', data);

        } else {
            logger.warn('[TOPIC] ELSE', topic, data);
            //alert('Publishing Topic ELSE: Topic - ' + topic + ' Data - ' + JSON.stringify(data));
            return false;
        }

        return true;
    };

    /**
     * This will remove widget including its instance from gim.widget object as well as wrapper
     * @param {string} id Widget ID (name + '_' + view)
     * @param {boolean} remWrp If true, it will set diaplay of wrapper element to none
     */
    var removeWidget = function(id, remWrp){

        if(typeof widget[id] !== 'undefined'){
            try{
                /**
                 * Remove the instance from Managed Hub as well as its container
                 */
                manageAgencyHub.removeContainer(widget[id].container);
            }catch(e){
            //logger.log('Oops! Remove widget failed with error >> ' + e.message)
            }

            /**
             * Hide wrapper and empty content
             */
            if(!isSet(remWrp) || remWrp !== false){

                /**
                 * Remove any style and class attribute if defined
                 */
                widget[id].setting.wrapper.setAttribute ('style', '');
                widget[id].setting.wrapper.style.cssText = '';
                widget[id].setting.wrapper.className = '';

                /**
                 * Hide the Wrapper Div and cleanup
                 */
                widget[id].setting.wrapper.style.display = 'none';
                widget[id].setting.wrapper.innerHTML = '';
            }

            /**
             * In case of popup widget:
             * 1. Remove overlay
             * 2. Remove BG Iframe
             */
            if(widget[id].setting.isInline !== true){
                showHideOverlay(false, 'out');
                removeWrapperBGIframe();
            }

            /**
             * Restore the original CSS class if defined
             */
            if(typeof _wrapperHtmlCss[widget[id].setting.wrapper.id] !== 'undefined'){
                widget[id].setting.wrapper.className = _wrapperHtmlCss[widget[id].setting.wrapper.id];
            }

            /**
             * Dispatch onWidgetClose event
             */
            dispatchEvent.call(widget[id], 'onWidgetClose', {
                'eventType' : 'onWidgetClose',
                'widget':{
                    'id' : widget[id].id,
                    'name' : widget[id].name,
                    'view' : widget[id].view
                },
                'setting' : widget[id].setting
            });

            /**
             * Delete widget from global gim.widget array
             */
            delete(widget[id]);

            return true;
        }
        return false;
    };

    /**
     * This function can remove widget from gim.widget object by Wrapper ID OR by Name+View
     * @param {object} args Argument object which can have following properties: Name, View, Wrapper ID
     * @public
     */
    var closeWidget = function(args){

        if(isSet(args.name) && isSet(args.view)){
            removeWidget(_returnWidgetId(args.name, args.view), args.remWrp);

        }else if(isSet(args.name)){
            for(var key in gim.widget){
                if(isSet(gim.widget[key].name) && gim.widget[key].name == args.name){
                    removeWidget(_returnWidgetId(gim.widget[key].name, gim.widget[key].view), args.remWrp);
                }
            }

        }else if(isSet(args.wrapper)){
            for(var key in gim.widget){
                if(isSet(gim.widget[key].setting.wrapper.id) && gim.widget[key].setting.wrapper.id == args.wrapper){
                    removeWidget(_returnWidgetId(gim.widget[key].name, gim.widget[key].view), args.remWrp);
                }
            }

        /**
             * For backword compatibility, where onCloseClick() is called with wrapper ID
             */
        }else if(typeof args === 'string'){
            closeWidget({
                'wrapper' : args,
                'remWrp' : true
            });
        }
    };

    /**
     * This will return wrapper element to display widget
     * @param {string} name Widget Name
     * @param {string} view Widget View
     * @param {string} fallback This will override setting wrapper value
     * @param {boolean} createEl If true, it will create <div> element if it does not exist
     * @return {object} <div> element
     */
    var getWrapper = function(name, view, override, createEl){
        var setting = getSetting(name, view);
        var def = (isSet(setting.isInline) && setting.isInline === true)? 'inlineWidget' : 'popupWidget';
        var wrapper = override || setting.wrapper || setting.divId || def;

        if(!isSet(createEl) || createEl === true){
            // Check Wrapper existence, and create if required
            if(document.getElementById(wrapper) === null){
                var wrpEl = document.createElement('div');
                wrpEl.id = wrapper;
                wrpEl.style.display = 'none';
                wrpEl.className = def;
                document.body.appendChild(wrpEl);
            }
            return document.getElementById(wrapper);
        }
        return wrapper;
    };

    /**
     * Add popup widget iframe BG for IE6/7 for select z-index bug
     */
    var getWrapperBGIframe = function(){
        if(this.setting.isInline === false && isSet(this.setting.wrapperIe6IframeFix) && (/MSIE 6/i.test(navigator.userAgent))){
            /**
             * Get BG iframe element
             */
            var bgIframeEl = (document.getElementById('widgetBgIframe') === null)? document.createElement('iframe') : document.getElementById('widgetBgIframe');

            /**
             * Add default style properties
             */
            bgIframeEl.id = 'widgetBgIframe';
            bgIframeEl.src = 'javascript:"<html></html>";';
            bgIframeEl.style.position = 'absolute';
            bgIframeEl.style.border = 'none';
            bgIframeEl.style.display = 'block';
            bgIframeEl.style.zIndex = '95';
            bgIframeEl.className = 'widgetBgIframe';

            /**
             * Override defaults with user supplied properties
             */
            if(typeof this.setting.wrapperIe6IframeFix === 'object'){
                for(var key in this.setting.wrapperIe6IframeFix){
                    if(key !== ('id' || 'inc')){
                        bgIframeEl.style[key] = this.setting.wrapperIe6IframeFix[key];
                    }
                }
            }

            /**
             * Append BG iframe element to document
             */
            if(document.getElementById('widgetBgIframe') === null){
                document.body.appendChild(bgIframeEl);
            }

            return document.getElementById(bgIframeEl.id);
        }
    };

    /**
     * Remove popup widget BG iframe
     */
    var removeWrapperBGIframe = function(){
        if(document.getElementById('widgetBgIframe') !== null){
            document.body.removeChild(document.getElementById('widgetBgIframe'));
        }
    }

    /**
     * This is a decorator function, that will apply various CSS classes, and other styles like height/width etc. to widget wrapper
     */
    var readyWrapper = function(){

        /**
         * If class is already defined in HTML, store it for future reference
         */
        if(typeof _wrapperHtmlCss[this.setting.wrapper.id] === 'undefined'){
            _wrapperHtmlCss[this.setting.wrapper.id] = this.setting.wrapper.className;
        }

        this.setting.wrapper.innerHTML = '';

        /**
         * Call override function if exist
         */
        if(typeof window.readyWrapper_override === 'function'){
            if(!window.readyWrapper_override.apply(this)){
                return true;
            }

        }else if (this.setting.isInline === true) {
            if(typeof window.loadInlineWidget_override === 'function'){
                if(!window.loadInlineWidget_override(this)){
                    return true;
                }
            }

        }else if(typeof window.loadPopupWidget_override === 'function') {
            if(!window.loadPopupWidget_override(this)){
                return true;
            }
        }

        /**
         * Add required classes
         */
        var _class = _wrapperHtmlCss[this.setting.wrapper.id] + ' ';
        _class += (typeof this.setting.isInline !== 'undefined' && this.setting.isInline !== true)? 'popupWidget' : 'inlineWidget';
        _class += ' ' + this.name + ' ' + this.name + '_' + this.view;
        _class += (typeof this.setting.wrapperCSSClass !== 'undefined')? ' ' + this.setting.wrapperCSSClass : '';
        this.setting.wrapper.className = _class;

        /**
         * Resize Wrapper
         */
        this.resizeWrapper({
            'width':this.setting.width,
            'height':this.setting.height
        });

        /**
         * Check if the widget is popup widget
         */
        if(this.setting.isInline === false){
            var widgetCloseId = 'linkToClosepopUp' + _uniId;
            this.setting.wrapper.innerHTML = "<div id='closeButton' class='closeWidget'><a href='#' id='"+widgetCloseId+"' class='"+ this.setting.closeButtonClassName +"'><img src='" + this.setting.closeImageSrc + "' id='closeIconImage' /></a></div><div id='popupIFrame'></div>";
            this.repositionWrapper({
                'top':this.setting.top,
                'left':this.setting.left
            });

            /**
             * Add listner for widget close
             */
            var widgetObj = this;
            addListener(document.getElementById(widgetCloseId), 'click', function(){
                removeWidget(widgetObj.id);
            });

        }

        this.setting.wrapper.style.display = 'block';

        /**
         * If widget wrapper does not have any height/width either by JS or by CSS,
         * set a min height/width to avoid empty display in IE6
         * This will also fix auto repositionWidget widget to center
         */
        if(isSet(this.setting.isInline) && this.setting.isInline !== true && getDimension.call(this.setting.wrapper, 'Width') < _defaultSetting.minWidgetWidth){
            this.setting.wrapper.style.width = _defaultSetting.minWidgetWidth + 'px';
        }
        if(isSet(this.setting.isInline) && this.setting.isInline !== true && getDimension.call(this.setting.wrapper, 'Height') < _defaultSetting.minWidgetHeight){
            this.setting.wrapper.style.height = _defaultSetting.minWidgetHeight + 'px';
        }

        /**
         * If repositionWidget property is true, move the wiget to center of the page
         */
        if(isSet(this.setting.isInline) && this.setting.isInline !== true && isSet(this.setting.repositionWidget) && this.setting.repositionWidget === true){
            this.repositionWrapper(repositionElement({
                'p':window.document,
                'c':this.setting.wrapper
            }));
        }

        // Show Overlay
        showHideOverlay.call(this, true, 'out', document.body);
        showHideOverlay.call(this, true, 'in', this.setting.wrapper);

        return true;
    };

    /**
     * Creates widget URL
     */
    var getWidgetURL = function(){
        var widget = getConfigByName(this.name);

        // Check if widget config exists
        if(widget === null){
            return widget;
        }

        var url = widget.url;
        url += widget.configKey + '/';
        url += this.view + '/';
        url += this.setting.inputFormat +  '/';
        url += this.setting.outputFormat;

        url += '?';

        if (typeof this.setting.locale !== 'undefined'){
            url += 'locale=' + this.setting.locale + '&';
        }

        if (typeof this.setting.parameters !== 'undefined' && this.setting.parameters !== null){
            url += this.setting.parameters + '&';
        }

        return url;
    };

    var getConfigByName = function(name) {
        return getConfigByProperty({
            'name' : name
        });
    };

    var getConfigByType = function(type) {
        return getConfigByProperty({
            'type' : type
        });
    };

    /**
     * Return widget config (from configurables.js) based on properties passed as an object literal
     * @param {object} args Parameters passed as object literal e.g. {'type':'mytype', 'name':'myname'}
     * @param {string} fallback Fallback property in case multi-property check failed
     * @return {object}
     * @public
     */
    var getConfigByProperty = function(args, fallback) {
        if(typeof window.widgetData !== 'undefined' && typeof window.widgetData.widgets !== 'undefined' && typeof args === 'object'){
            var widgets = window.widgetData.widgets;
            for(var i = 0, j = widgets.length; i < j; i++) {
                var k = true;
                for(var prop in args){
                    if(widgets[i].widget[prop] !== args[prop]) {
                        k = false;
                        break;
                    }
                }
                if(k === true) {
                    return widgets[i].widget;
                }
            }

            /**
             * If widget config not found with defined parameters, do fallback
             */
            if(isSet(fallback) && isSet(args[fallback])){
                logger.warn('Widget Config Not Found. Falling back to ' + fallback, args[fallback]);
                var _args = {};
                _args[fallback] = args[fallback];
                return getConfigByProperty(_args);
            }
        }
        logger.warn('Widget Config Not Found!');
        return null;
    };

    /**
     * Loads the settings literal object from page scope
     * @private
     */
    var getSetting = function(name, view) {
        // Return from cache if exist
        if(typeof widget[name + '_' + view] !== 'undefined' && typeof widget[name + '_' + view].setting !== 'undefined'){
            return widget[name + '_' + view].setting;
        }

        var mergedConfig = {};
        var defaultConfig = window.widget_default_config || {};
        var widgetConfig = window['widget_' + name + '_config'];
        var widgetViewConfig = window['widget_' + name + '_' + view + '_config'];

        if (typeof defaultConfig !== 'undefined') {
            mergedConfig = extend(mergedConfig, defaultConfig);
        }

        if (typeof widgetConfig !== 'undefined') {
            mergedConfig = extend(mergedConfig, widgetConfig);
        }

        if (typeof widgetViewConfig !== 'undefined') {
            mergedConfig = extend(mergedConfig, widgetViewConfig);
        }

        return mergedConfig;
    };

    /**
     * Return widget object by its container client ID passed to OpenAjax
     * @param {string} id Container Client ID
     * @return {object} Widget Object
     */
    var getWidgetByClientId = function(id) {
        for(var key in gim.widget){
            if(typeof gim.widget[key].container !== 'undefined' && gim.widget[key].container.getClientID() === id){
                return gim.widget[key];
            }
        }
        return null;
    };

    /**
     * Return widget object by widget name
     * @param {string} name Container Client ID
     * @return {object} Widget Object
     */
    var getWidgetByName = function(name) {
        for(var key in gim.widget){
            if(typeof gim.widget[key].name !== 'undefined' && gim.widget[key].name === name){
                return gim.widget[key];
            }
        }
        return null;
    };

    /**
     * Return widget object by various properties
     * @return {object} Widget Object
     */
    var getWidget = function(args) {
        for(var key in gim.widget){
            var r = true;
            for(var prop in args){
                if(gim.widget[key][prop] !== args[prop]) {
                    r = false;
                    break;
                }
            }
            if(r){
                return gim.widget[key];
            }
        }
        return null;
    };

    /**
     *
     */
    var _returnWidgetId = function(name, view){
        return name + '_' + view;
    };

    /**
     * Append default URL querystring with values from data based on array defined
     * as property of widget_default_config object in settings.js
     * @private
     */
    var appendWidgetUrl = function(data){
        /** Get current widget config data from settings.js */
        var srcWidgetConfig = getSetting(data.srcWidget, data.srcView);
        /** URL value if exist */
        var locationUrl = srcWidgetConfig[data.dstWidgetType+'Url'];

        if(typeof locationUrl !== 'undefined' && locationUrl !== null){
            locationUrl += '?';
            if(typeof srcWidgetConfig.urlParam !== 'undefined'){
                for(var i = 0, j = srcWidgetConfig.urlParam.length; i < j; i++){
                    if(typeof data[srcWidgetConfig.urlParam[i]] !== 'undefined'){
                        locationUrl += '&'+ srcWidgetConfig.urlParam[i] + '=' + data[srcWidgetConfig.urlParam[i]];
                    }
                }
            }
            return locationUrl;
        }
        return null;
    };

    /** GIM COMMON CALLBACK FUNCTION *********************************************************************/

    /** Handle security alerts */
    var handleSecurityAlert = function(args) {

        // Check and execute if handleSecurityAlert_override function exist
        try {
            if(typeof handleSecurityAlert_override === 'function'){
                handleSecurityAlert_override(args);
                return;
            }

            // Check and execute if alert specific function exist
            if(typeof window[(args.alertType.replace(/\./g, '_') + '_override')] === 'function'){
                window[(args.alertType.replace(/\./g, '_') + '_override')](args);
                return;
            }
        }
        catch (e) {}

        // Handle various security alert
        switch (args.alertType) {
            case 'OpenAjax.hub.SecurityAlert.LoadTimeout':
                /**
                 * Callback function, If the client at params.IframeContainer.uri does not establish
                 * a connection with the container in the given time
                 */
                try{
                    if(typeof widgetConnectionTimeout === 'function'){
                        widgetConnectionTimeout(args);
                        return;
                    }
                }catch(e){}
                break;
            case 'OpenAjax.hub.SecurityAlert.FramePhish':
                break;
            case 'OpenAjax.hub.SecurityAlert.ForgedMsg':
                break;
            default:
                break;
        }
    };

    var handleConnect = function(args){
        // Check and execute if handleConnect_override function exist
        try {
            if(typeof handleConnect_override === 'function'){
                handleConnect_override(args);
                return;
            }
        }
        catch (e) {}
    };

    var handleDisconnect = function(args){
        // Check and execute if handleDisconnect_override function exist
        try {
            if(typeof handleDisconnect_override === 'function'){
                handleDisconnect_override(args);
                return;
            }
        }
        catch (e) {}
    };

    /** HELPER FUNCTIONS ***************************************************************************/

    /**
     * Global method to "extend" objects
     * @private
     */
    var extend = function (obj, extObj) {
        if (arguments.length > 2) {
            for (var i = 1, j = arguments.length; i < j; i++) {
                extend(obj, arguments[i]);
            }
        } else {
            for (var key in extObj) {
                obj[key] = extObj[key];
            }
        }
        return obj;
    };

    /**
     * This function create a overlay on widget onload and remove it once Hub is connected
     * @param {boolean} show If show is true, it will create an overlay, otherwise remove it
     * @param {string} type Type value (in/out) define the type of overlay to create
     * @param {object} wrp Widget wrapper element
     * @return {object} Overlay element
     */
    var showHideOverlay = function(show, type, wrapper){

        if(show === true){
            /**
             * Perform Validation
             */
            if(!isSet(type) || (this.setting.isInline === true && type !== 'in') || typeof this.setting.overlay !== 'object' || !isSet(this.setting.overlay[type])){
                return false;
            }

            /*
             * Check if any exisiting overlay element exist, if Not, create a new <div> element
             */
            var _id = (type === 'in')? this.setting.wrapper.id + '_overlayWrapper' : _defaultSetting.outerOverlayWrapper.id;
            var eF = (document.getElementById(_id) !== null)? document.getElementById(_id) : document.createElement('div');

            /**
             * Add attributes and apply style
             */
            eF.id = _id;
            eF.className = type + 'OverlayWrapper';
            applyStyle(eF, this.setting.overlay[type].style, {
                'height':'100%',
                'width':'100%'
            })

            /**
             * Append overlay element to document
             */
            if(document.getElementById(_id) === null){
                wrapper.appendChild(eF);
            }

            /**
             * In case of IN overlay, display message and loading icon
             */
            if(type === 'in'){
                eF.innerHTML = "<div id='" + this.setting.wrapper.id + "_overlayMsg" +"' class='overlayMsg'><p>" + (this.setting.overlay[type].msg || 'Loading...') + "</p><img src='" + (this.setting.overlay[type].loadImg || 'ajax-loader.gif') + "' alt=''/></div>";
                var _overlayMsg = document.getElementById(this.setting.wrapper.id + "_overlayMsg");
                _overlayMsg.style.top = ((parseInt((this.setting.wrapper.offsetHeight - _overlayMsg.offsetHeight)/2) > 0)? parseInt((this.setting.wrapper.offsetHeight - _overlayMsg.offsetHeight)/2) : 0) + 'px';
                _overlayMsg.style.left = parseInt(((this.setting.wrapper.offsetWidth - _overlayMsg.offsetWidth)/2)) + 'px';
            }

            /**
             * Check for IE6 Browser and use fixed height/width
             */

            if (/MSIE 6/i.test(navigator.userAgent)) {
                eF.style.width = (type === 'out')? document.documentElement.clientWidth : '100%';
                eF.style.height = (type === 'out')? getDimension.call(window.document,'Height') : this.setting.wrapper.offsetHeight;
            }

            /**
             * Check for IE6/7 Browser and add iframe Bg for <select> overlapping fix
             */
            if(type === 'out' && (/MSIE 6/i.test(navigator.userAgent))){

                /**
                 * Get ID for Bg iframe
                 */
                _id = _defaultSetting.outerOverlayWrapper.id + '_bgIframe';

                /**
                 * Get BG Overlay iframe element
                 */
                var eBgIf = (document.getElementById(_id) === null)? document.createElement('iframe') : document.getElementById(_id);

                /**
                 * Add attributes and apply style
                 */
                eBgIf.id = _id;
                eBgIf.className = 'overlayBgIframe';
                eBgIf.src = 'javascript:"<html></html>";';
                eBgIf.setAttribute('allowtransparency', 'true');
                eBgIf.style.filter='progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)';
                applyStyle(eBgIf, null, {
                    'position':'absolute',
                    'display':'block',
                    'top':'0px',
                    'left':'0px',
                    'height':getDimension.call(window.document,'Height'),
                    'width':'100%'
                })

                /**
                 * Append BG iframe element to document
                 */
                if(document.getElementById(_id) === null){
                    document.body.appendChild(eBgIf);
                }
            }

            return eF;

        }else{
            /**
             * Get overlay element
             */
            var _obj = (type === 'in')? document.getElementById(wrapper.id + '_overlayWrapper') : document.getElementById(_defaultSetting.outerOverlayWrapper.id);

            /**
             * If overlay element found, remove it from the document
             */
            if(!isEmpty(_obj)){
                (type === 'in')? wrapper.removeChild(_obj) : document.body.removeChild(_obj);
            }

            /**
             * Check for BG iframe element and if found, remove it from the document
             */
            if(type === 'out' && document.getElementById(_defaultSetting.outerOverlayWrapper.id + '_bgIframe') !== null){
                document.body.removeChild(document.getElementById(_defaultSetting.outerOverlayWrapper.id + '_bgIframe'));
            }

            return true;
        }
    };

    var repositionElement = function(args){
        var _r = {};
        args.minHeight = args.minHeight || 0;
        args.minWidth = args.minWidth || 0;
        _r.top = (args.p.nodeType === 9)?
        ((parseInt((document.documentElement.clientHeight - Math.max(args.c.offsetHeight, args.minHeight))/2) > 0)? parseInt((document.documentElement.clientHeight - Math.max(args.c.offsetHeight, args.minHeight))/2) : 0)
        : '';
        _r.left = (args.p.nodeType === 9)?
        ((document.documentElement.clientWidth - Math.max(args.c.offsetWidth, args.minWidth))/2)
        : '';
        return _r;
    };


    /**
     * This will add/remove any query string parameter in the URL
     * @param {string} url This should be full URI starting from http
     * @param {object} param Function will loop thorugh each property in this object and update/add query param as required
     *                 To remove any query string param, set its value to null
     * @return {string} url Modified url
     */
    function updateUrlParam(url, param){
        var urlparts = url.split('?');
        if (urlparts.length >= 2) {
            // Existing parameters array
            var paramArray = urlparts[1].split(/[&;]/g);
            for(var newParam in param){
                for(var i = (paramArray.length - 1); i >= 0; i--){
                    if (paramArray[i].lastIndexOf(encodeURIComponent(newParam), 0) !== -1){
                        (param[newParam] === null)? paramArray.splice(i, 1) : paramArray[i] = newParam + '=' + param[newParam];
                        break;
                    }else{
                        paramArray.push(newParam + '=' + param[newParam]);
                        break;
                    }
                }
            }
            url = urlparts[0]+'?'+paramArray.join('&');
        }
        return url;
    };

    var dispatchEvent = function(eType, args){
        if(_events[eType] && _listeners[eType]){
            var listeners = _listeners[eType];
            for(var l = 0; listeners && l < listeners.length; l++){
                var listener = listeners[l];
                if((listener.name === null && listener.view === null) || (this.name === listener.name && listener.view === null) || (this.name === listener.name && this.view === listener.view)){
                    listener.callback.call(this, args);
                }
            }
        }
    };

    var addWidgetListener = function(eType, cb, name, view){
        if(typeof _events[eType] !== 'undefined'){
            if(!_listeners[eType]){
                _listeners[eType]=[];
            }
            _listeners[eType].push({
                'name' : name || null,
                'view' : view || null,
                'callback' : cb
            });
        }
    };

    var removeEventListener = function(eType, cb, name, view){
        var listeners = _listeners[eType];
        for(var l = 0; listeners && l < listeners.length; l++){
            if(listeners[l].name === name && listeners[l].view === view && listeners[l].callback === cb) {
                listeners.splice(l,1);
            }
        }
    };

    /**
     * Add event to an element
     * @param {object} el Element on which event needs to be added
     * @param {string} ev Event name
     * @param {function} cb Callback function
     * @param {boolean} cp if true, will enable capture event instead of bubble
     */
    var addListener = function(el, ev, cb, cp){
        if(!el){
            return false;
        }
        cp = cp || false;
        if(typeof window.addEventListener === 'function') {
            el.addEventListener(ev, cb, cp);
        }else if(typeof document.attachEvent === 'function'){
            el.attachEvent('on' + ev, cb);
        }else{
            el['on' + ev] = cb;
        }
        return true;
    };

    /**
     * Remove event from an element
     * @param {object} el Element on which event needs to be removed
     * @param {string} ev Event name
     * @param {function} cb Callback function
     * @param {boolean} cp if true, will enable capture event instead of bubble
     */
    var removeListener = function(el, ev, cb, cp){
        if(!el){
            return false;
        }
        cp = cp || false;
        if(typeof window.removeEventListener === 'function') {
            el.removeEventListener(ev, cb, cp);
        }else if(typeof document.detachEvent === 'function'){
            el.detachEvent('on' + ev, cb);
        }else{
            el['on' + ev] = null;
        }
        return true;
    };

    /**
     * Apply style on element passed as parameter, will fallback to default, if style does not exist
     * @param {object} el Element
     * @param {object} style Style object literal
     * @param {object} def Default object literal
     * @return {object} Element
     */
    var applyStyle = function(el, style, def){
        /**
         * Validate parameters
         */
        if(typeof el !== 'object' || (!isSet(style) && !isSet(def))){
            return false;
        }
        /**
         * Override default with supplied value
         */
        if(isSet(def) && typeof def === 'object'){
            style = (isSet(style) && typeof style === 'object')? extend(def, style) : def;
        }
        for(var key in style){
            el.style[key] = style[key];
        }
        return el;
    }

    /**
     * This will check if the passed parameter is defined or not
     */
    var isSet = function(v){
        return (typeof v === 'undefined')? false : true;
    };

    /**
     * This will check if the passed parameter is empty or not
     * will return true if parameter is:
     * - undefined
     * - null
     * - false
     * - ''
     * - 0
     */
    var isEmpty = function(v){
        return ((typeof v === 'undefined') || (v === null) || (v === false) || (v === '') || (v === 0))? true : false;
    };

    /**
     * Check if the passed parameter is an object
     */
    var isObject = function(v){
        return (typeof v === 'object' && v !== null)? true : false;
    };

    /**
     * Return the dimension of the element passed as parameter
     */
    var getDimension = function(v){
        return (this.nodeType === 9)? Math.max(
            this.documentElement['client' + v],
            this.body['scroll' + v], this.documentElement['scroll' + v],
            this.body['offset' + v], this.documentElement['offset' + v]
            ) : Math.max(
            this['offset' + v], this.style[v.toLowerCase()]
            );
    };

    /**
     *  This function will user (console.log/alert) to display any number of parameters passed
     *  as arguments, it will also loop through object to provide more meaningful information.
     *  @param {boolean} enableAlert Enable/disable alert on IE
     */
    var logger = {
        'log' : function(){},
        'info' : function(){},
        'warn' : function(){},
        'error' : function(){}
    };
    if(typeof _debug !== 'undefined' && _debug === true){
        if (typeof window.console !== 'undefined'){
            logger.log = (typeof window.console.log !== 'undefined')? function(){
                window.console.log(arguments);
            } : function(){};
            logger.info = (typeof window.console.info !== 'undefined')? function(){
                window.console.info(arguments);
            } : function(){};
            logger.warn = (typeof window.console.warn !== 'undefined')? function(){
                window.console.warn(arguments);
            } : function(){};
            logger.error = (typeof window.console.error !== 'undefined')? function(){
                window.console.error(arguments);
            } : function(){};
        }
    }

    return{
        hubClient:hubClient,
        widget:widget,
        defaultSetting : _defaultSetting,
        init:init,
        getConfigByProperty:getConfigByProperty,
        renderWidget: renderWidget,
        closeWidget: closeWidget,
        getWidgetByName: getWidgetByName,
        getWidgetByClientId: getWidgetByClientId,
        addWidgetListener : addWidgetListener,
        removeEventListener : removeEventListener,

        // Depricated - Only for backward compatibility
        onCloseClick : closeWidget
    }
}();


(function(){

    var DomReady = window.DomReady = {};

    // Everything that has to do with properly supporting our document ready event. Brought over from the most awesome jQuery.

    var userAgent = navigator.userAgent.toLowerCase();

    // Figure out what browser is being used
    var browser = {
        version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
        safari: /webkit/.test(userAgent),
        opera: /opera/.test(userAgent),
        msie: (/msie/.test(userAgent)) && (!/opera/.test( userAgent )),
        mozilla: (/mozilla/.test(userAgent)) && (!/(compatible|webkit)/.test(userAgent))
    };

    var readyBound = false;
    var isReady = false;
    var readyList = [];

    // Handle when the DOM is ready
    function domReady() {
        // Make sure that the DOM is not already loaded
        if(!isReady) {
            // Remember that the DOM is ready
            isReady = true;

            if(readyList) {
                for(var fn = 0; fn < readyList.length; fn++) {
                    readyList[fn].call(window, []);
                }

                readyList = [];
            }
        }
    };

    // From Simon Willison. A safe way to fire onload w/o screwing up everyone else.
    function addLoadEvent(func) {
        var oldonload = window.onload;
        if (typeof window.onload != 'function') {
            window.onload = func;
        } else {
            window.onload = function() {
                if (oldonload) {
                    oldonload();
                }
                func();
            }
        }
    };

    // does the heavy work of working through the browsers idiosyncracies (let's call them that) to hook onload.
    function bindReady() {
        if(readyBound) {
            return;
        }

        readyBound = true;

        // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
        if (document.addEventListener && !browser.opera) {
            // Use the handy event callback
            document.addEventListener("DOMContentLoaded", domReady, false);
        }

        // If IE is used and is not in a frame
        // Continually check to see if the document is ready
        if (browser.msie && window == top) (function(){
            if (isReady) return;
            try {
                // If IE is used, use the trick by Diego Perini
                // http://javascript.nwbox.com/IEContentLoaded/
                document.documentElement.doScroll("left");
            } catch(error) {
                setTimeout(arguments.callee, 0);
                return;
            }
            // and execute any waiting functions
            domReady();
        })();

        if(browser.opera) {
            document.addEventListener( "DOMContentLoaded", function () {
                if (isReady) return;
                for (var i = 0; i < document.styleSheets.length; i++)
                    if (document.styleSheets[i].disabled) {
                        setTimeout( arguments.callee, 0 );
                        return;
                    }
                // and execute any waiting functions
                domReady();
            }, false);
        }

        if(browser.safari) {
            var numStyles;
            (function(){
                if (isReady) return;
                if (document.readyState != "loaded" && document.readyState != "complete") {
                    setTimeout( arguments.callee, 0 );
                    return;
                }
                if (numStyles === undefined) {
                    var links = document.getElementsByTagName("link");
                    for (var i=0; i < links.length; i++) {
                        if(links[i].getAttribute('rel') == 'stylesheet') {
                            numStyles++;
                        }
                    }
                    var styles = document.getElementsByTagName("style");
                    numStyles += styles.length;
                }
                if (document.styleSheets.length != numStyles) {
                    setTimeout( arguments.callee, 0 );
                    return;
                }

                // and execute any waiting functions
                domReady();
            })();
        }

        // A fallback to window.onload, that will always work
        addLoadEvent(domReady);
    };

    // This is the public function that people can use to hook up ready.
    DomReady.ready = function(fn, args) {
        // Attach the listeners
        bindReady();

        // If the DOM is already ready
        if (isReady) {
            // Execute the function immediately
            fn.call(window, []);
        } else {
            // Add the function to the wait list
            readyList.push( function() {
                return fn.call(window, []);
            } );
        }
    };

    bindReady();

})();

window.DomReady.ready(gim.init);

