/**
 * A specialized tooltip class for tooltips that can be specified in markup and automatically
 * managed by the global {@link Ext.tip.QuickTipManager} instance. See the QuickTipManager
 * documentation for additional usage details and examples.
 *
 *      @example     
 *      Ext.tip.QuickTipManager.init(); // Instantiate the QuickTipManager 
 *
 *      Ext.create('Ext.Button', {
 *
 *          renderTo: Ext.getBody(),
 *          text: 'My Button',
 *          listeners: {
 *
 *              afterrender: function(me) {
 *
 *                  // Register the new tip with an element's ID
 *                  Ext.tip.QuickTipManager.register({
 *                      target: me.getId(), // Target button's ID
 *                      title : 'My Tooltip',  // QuickTip Header
 *                      text  : 'My Button has a QuickTip' // Tip content  
 *                  });
 *
 *              },
 *              destroy: function(me) {
 *                  Ext.tip.QuickTipManager.unregister(me.getId());
 *              }
 *          }
 *      });
 *
 */
Ext.define('Ext.tip.QuickTip', {
    extend: 'Ext.tip.ToolTip',
    alias: 'widget.quicktip',
    alternateClassName: 'Ext.QuickTip',
 
    /**
     * @cfg {String/HTMLElement/Ext.dom.Element} target
     * The target HTMLElement, {@link Ext.dom.Element} or id to associate with this Quicktip.
     *
     * Defaults to the document.
     */
 
    /**
     * @cfg {Boolean} interceptTitles
     * `true` to automatically use the element's DOM title value if available.
     */
    interceptTitles: false,
 
    /**
     * @cfg {String/Ext.panel.Title} title
     * The title text to be used to display in the Tip header.  May be a string 
     * (including HTML tags) or an {@link Ext.panel.Title} config object.
     */
    title: ' ',
 
    /**
     * @cfg checkNestedDelegates
     * @inheritdoc
     */
    checkNestedDelegates: true,
 
    /**
     * @private
     */
    tagConfig: {
        namespace: 'data-',
        attribute: 'qtip',
        width: 'qwidth',
        target: 'target',
        title: 'qtitle',
        hide: 'hide',
        cls: 'qclass',
        align: 'qalign',
        anchor: 'anchor',
        showDelay: 'qshowDelay',
        hideAction: 'hideAction',
        anchorTarget: 'anchorTarget'
    },
 
    isQuickTip: true,
 
    /**
     * @cfg shrinkWrapDock
     * @inheritdoc
     */
    shrinkWrapDock: true,
 
    initComponent: function() {
        var me = this;
 
        // delegate selector is a function which detects presence
        // of attributes which provide QuickTip text.
        me.delegate = me.delegate.bind(me);
 
        me.target = me.target || Ext.getDoc();
        me.targets = me.targets || {};
 
        me.header = me.header || {};
        me.header.focusableContainer = false;
 
        me.callParent();
    },
 
    setTagConfig: function(cfg) {
        this.tagConfig = Ext.apply({}, cfg);
 
        // Let attr get recomputed
        delete this.tagConfig.attr;
    },
 
    /**
     * @cfg text
     * @inheritdoc Ext.tip.ToolTip#cfg-html
     */
    text: null,
 
    /**
     * @cfg html
     * @hide
     * -- hidden for Ext.tip.QuickTip - see #cfg-text
     */
 
    /**
     * Configures a new quick tip instance and assigns it to a target element.
     *
     * For example usage, see the {@link Ext.tip.QuickTipManager} class header.
     *
     * @param {Object} config The config object with the following properties:
     * @param config.target (required) The target HTMLElement, {@link Ext.dom.Element} or 
     * id to associate with this Quicktip.  See {@link Ext.tip.QuickTip#target}.
     * @param config.text Tip body content.  See {@link Ext.tip.QuickTip#text}.
     * @param config.title Tip header.  See {@link Ext.tip.QuickTip#title}.
     * @param config.autoHide False to prevent the tip from automatically hiding on 
     * mouseleave.  See {@link Ext.tip.QuickTip#autoHide}.
     * @param config.cls An optional extra CSS class that will be added to the tip.  See 
     * {@link Ext.tip.QuickTip#cls}.
     * @param config.dismissDelay Delay in milliseconds before the tooltip automatically 
     * hides (overrides singleton value).  See {@link Ext.tip.QuickTip#dismissDelay}.
     * @param config.width Tip width in pixels.  See {@link Ext.tip.QuickTip#width}.
     */
    register: function(config) {
        var configs = Ext.isArray(config) ? config : arguments,
            i = 0,
            len = configs.length,
            target, j, targetLen;
 
        for (; i < len; i++) {
            config = configs[i];
            target = config.target;
 
            if (target) {
                if (Ext.isArray(target)) {
                    for (= 0, targetLen = target.length; j < targetLen; j++) {
                        this.targets[Ext.id(target[j])] = config;
                    }
                }
                else {
                    this.targets[Ext.id(target)] = config;
                }
            }
        }
    },
 
    /**
     * Removes this quick tip from its element and destroys it.
     * @param {String/HTMLElement/Ext.dom.Element} el The element from which the quick tip
     * is to be removed or ID of the element.
     */
    unregister: function(el) {
        delete this.targets[Ext.id(el)];
    },
 
    /**
     * Hides a visible tip or cancels an impending show for a particular element.
     * @param {String/HTMLElement/Ext.dom.Element} el The element that is the target of
     * the tip or ID of the element.
     */
    cancelShow: function(el) {
        var me = this,
            currentTarget = me.currentTarget;
 
        el = Ext.getDom(el);
 
        if (me.isVisible()) {
            if (currentTarget.dom === el) {
                me.hide();
            }
        }
        else if (currentTarget.dom === el) {
            me.clearTimer('show');
        }
    },
 
    delegate: function(target) {
        var me = this,
            cfg = me.tagConfig,
            attr = cfg.attr || (cfg.attr = cfg.namespace + cfg.attribute),
            registeredTarget = me.targets[target.id],
            text;
 
        // We can now only activate on elements which have the required attributes
        text = target.getAttribute(attr) || (me.interceptTitles && target.title) ||
                (registeredTarget && registeredTarget.text);
 
        return !!text;
    },
 
    /**
     * @private
     * Reads the tip text from the target.
     */
    getTipText: function(target) {
        var titleText = target.title,
            cfg = this.tagConfig,
            attr = cfg.attr || (cfg.attr = cfg.namespace + cfg.attribute);
 
        if (this.interceptTitles && titleText) {
            target.setAttribute(attr, titleText);
            target.removeAttribute('title');
 
            return titleText;
        }
        else {
            return target.getAttribute(attr);
        }
    },
 
    onTargetOver: function(event) {
        var me = this,
            currentTarget = me.currentTarget,
            target = event.target,
            targets, registeredTarget, key;
 
        // If the over target is not an HTMLElement, or is the <html> or the <body>, then return
        if (!target || target.nodeType !== 1 || target === document.documentElement ||
            target === document.body) {
            return;
        }
 
        me.pointerEvent = event;
        targets = me.targets;
 
        // Loop through registered targets seeing if we are over one.
        for (key in targets) {
            if (targets.hasOwnProperty(key)) {
                registeredTarget = targets[key];
 
                target = Ext.getDom(registeredTarget.target);
 
                // If we moved over a registered target from outside of it, activate it.
                if (target && Ext.fly(target).contains(event.target) &&
                    !Ext.fly(target).contains(event.relatedTarget)) {
                    currentTarget.attach(target);
                    me.activeTarget = registeredTarget;
                    registeredTarget.el = currentTarget;
                    me.anchor = registeredTarget.anchor;
                    me.activateTarget();
 
                    return;
                }
            }
        }
 
        // We found no registered targets, now continue as a regular ToolTip, and
        // see if we are over any of our delegated targets.
        me.callParent([event]);
    },
 
    handleTargetOver: function(target, event) {
        var me = this,
            currentTarget = me.currentTarget,
            cfg = me.tagConfig,
            ns = cfg.namespace,
            tipText = me.getTipText(target, event),
            autoHide;
 
        if (tipText) {
 
            autoHide = currentTarget.getAttribute(ns + cfg.hide);
 
            me.activeTarget = {
                el: currentTarget,
                text: tipText,
                width: +currentTarget.getAttribute(ns + cfg.width) || null,
                autoHide: autoHide !== "user" && autoHide !== 'false',
                title: currentTarget.getAttribute(ns + cfg.title),
                cls: currentTarget.getAttribute(ns + cfg.cls),
                align: currentTarget.getAttribute(ns + cfg.align),
                showDelay: currentTarget.getAttribute(ns + cfg.showDelay),
                hideAction: currentTarget.getAttribute(ns + cfg.hideAction),
                alignTarget: currentTarget.getAttribute(ns + cfg.anchorTarget)
            };
 
            // If we were not configured with an anchor,
            // allow it to be set by the target's properties
            if (!me.initialConfig.hasOwnProperty('anchor')) {
                me.anchor = currentTarget.getAttribute(ns + cfg.anchor);
            }
 
            // If we are anchored, and not configured with an anchorTarget,
            // anchor to the target element, or whatever its 'data-anchortarget' points to
            if (me.anchor && !me.initialConfig.hasOwnProperty('anchorTarget')) {
                me.alignTarget = me.activeTarget.alignTarget || target;
            }
 
            me.activateTarget();
        }
    },
 
    activateTarget: function() {
        var me = this,
            activeTarget = me.activeTarget,
            delay = activeTarget.showDelay,
            hideAction = activeTarget.hideAction;
 
        // If moved from target to target rapidly, the hide delay will not
        // have fired, so just update content and alignment.
        if (me.isVisible()) {
            me.updateContent();
            me.realignToTarget();
        }
        else {
            if (activeTarget.showDelay) {
                delay = me.showDelay;
                me.showDelay = parseInt(activeTarget.showDelay, 10);
            }
 
            me.delayShow();
 
            if (activeTarget.showDelay) {
                me.showDelay = delay;
            }
 
            if (!(hideAction = activeTarget.hideAction)) {
                delete me.hideAction;
            }
            else {
                me.hideAction = hideAction;
            }
        }
    },
 
    getAnchorAlign: function() {
        var active = this.activeTarget;
 
        return (active && active.align) || this.callParent();
    },
 
    getAlignRegion: function() {
        var me = this,
            activeTarget = me.activeTarget,
            currentTargetDom = me.currentTarget.dom,
            result;
 
        // If we are anchored, and not configured with an anchorTarget,
        // align to the target element, or whatever its 'data-anchortarget' points to
        if (activeTarget && activeTarget.alignTarget && me.anchor &&
            !me.initialConfig.hasOwnProperty('anchorTarget')) {
            me.currentTarget.attach(Ext.getDom(activeTarget.alignTarget));
        }
 
        // Anchor to the target when have an align config or an anchor config
        me.anchorToTarget = !!(activeTarget.align || me.anchor);
        result = me.callParent();
 
        // Return currentTarget to correctness for pointer event processing
        me.currentTarget.attach(currentTargetDom);
 
        return result;
    },
 
    /**
     * @private
     */
    handleTargetOut: function(e) {
        var me = this,
            active = me.activeTarget,
            autoHide = me.autoHide,
            hideDelay = me.hideDelay;
 
        if (active && autoHide !== false) {
            me.autoHide = true;
 
            if (active.hideDelay) {
                me.hideDelay = parseInt(active.hideDelay, 10);
            }
 
            me.callParent([e]);
            me.autoHide = autoHide;
            me.hideDelay = hideDelay;
        }
    },
 
    targetTextEmpty: function() {
        var me = this,
            target = me.activeTarget,
            cfg = me.tagConfig,
            el, text;
 
        if (target) {
            el = target.el;
 
            if (el) {
                text = el.getAttribute(cfg.namespace + cfg.attribute);
 
                // Note that the quicktip could also have been registered with the QuickTipManager.
                // If this was the case, then we don't want to veto showing it.
                // Simply do a lookup in the registered targets collection.
                if (!text && !me.targets[Ext.id(target.el.dom)]) {
                    return true;
                }
            }
        }
 
        return false;
    },
 
    show: function() {
        var me = this,
            fromDelay = me.fromDelayShow;
 
        // We're coming from a delayed show, so check whether
        // the attribute has been removed before we show it
        if (fromDelay && me.targetTextEmpty()) {
            me.activeTarget = null;
            me.currentTarget.detach();
 
            return;
        }
 
        me.callParent(arguments);
    },
 
    /**
     * @method beforeShow
     * @inheritdoc Ext.tip.Tip#method-beforeShow
     */
    beforeShow: function() {
        this.updateContent();
        this.callParent(arguments);
    },
 
    /**
     * @private
     */
    updateContent: function() {
        var me = this,
            target = me.activeTarget,
            header = me.header,
            dismiss, cls;
 
        if (target) {
            me.suspendLayouts();
 
            if (target.title) {
                me.setTitle(target.title);
                header.show();
            }
            else if (header) {
                header.hide();
            }
 
            me.update(target.text);
            me.autoHide = target.autoHide;
            dismiss = target.dismissDelay;
 
            me.dismissDelay = Ext.isNumber(dismiss) ? dismiss : me.dismissDelay;
 
            cls = me.lastCls;
 
            if (cls) {
                me.removeCls(cls);
                delete me.lastCls;
            }
 
            cls = target.cls;
 
            if (cls) {
                me.addCls(cls);
                me.lastCls = cls;
            }
 
            me.setWidth(target.width);
 
            me.align = target.align;
            me.resumeLayouts(true);
        }
    },
 
    /**
     * @method hide
     * @inheritdoc
     */
    hide: function() {
        this.activeTarget = null;
        this.callParent();
    }
});