/*

Siesta 5.1.0
Copyright(c) 2009-2018 Bryntum AB
https://bryntum.com/contact
https://bryntum.com/products/siesta/license

*/
Ext.define('Siesta.Project.Browser.UI.ComponentInspector', {
    extend : 'Ext.util.Observable',

    inspectedComponent      : null,
    inspectedComponentXType : null,
    boxIndicatorEl          : null,
    active                  : false,
    window                  : null,
    trackMouse              : true,
    showSelectorText        : true,
    bufferTime              : 30,
    injectIntoTestFrame     : false,

    getIndicatorEl : function () {
        return this.boxIndicatorEl;
    },

    getExt : function () {
        return this.window.Ext;
    },

    start : function (window, containerEl) {
        window = window || this.window;

        if (!window) throw 'Must provide a window context for the inspector';

        this.window = window;

        var _Ext = this.getExt();
        var me   = this;
        var wrap = this.injectIntoTestFrame ? window.document.body : (containerEl || window.document.body);
        var boxSizingStyle = 'box-sizing: border-box;-moz-box-sizing: border-box;-ms-box-sizing: border-box;-webkit-box-sizing: border-box;';

        me.boxIndicatorEl = me.boxIndicatorEl || Ext.fly(wrap).createChild({
            cls      : 'target-inspector-box',
            style    : 'position : absolute;left:0;top:0;pointer-events : none;z-index : 100001; border : 2px solid red;transition-property : transform,border-color;transition-duration : 0.3s;',
            children : [
                {
                    tag      : 'div',
                    style    : 'border:1px solid;border-color:inherit;width : 13px; height : 13px;position : absolute;margin-top : -8px;margin-left : -8px;border-radius:19px;transition-property : transform;transition-duration : 0.3s;' + boxSizingStyle,
                    cls      : 'target-inspector-coordinate',
                    html     : '<div style="border-left: 1px solid;border-color:inherit;position: absolute;top: -3px;left: 5px;height: 6px;' + boxSizingStyle +'"></div>' +
                               '<div style="border-left: 1px solid;border-color:inherit;position: absolute;bottom: -3px;left: 5px;height: 6px;' + boxSizingStyle +'"></div>' +
                               '<div style="border-top: 1px solid;border-color:inherit;position: absolute;top: 5px;left: -3px;width: 6px;' + boxSizingStyle +'"></div>' +
                               '<div style="border-top: 1px solid;border-color:inherit;position: absolute;top: 5px;right: -3px;width: 6px;' + boxSizingStyle +'"></div>'
                }
            ].concat(this.showSelectorText ? {
                    tag    : 'a',
                    cls    : 'target-inspector-label',
                    target : '_blank'
                } : [])
        });

        if (this.trackMouse && _Ext && _Ext.getBody) {
            this.toggleMouseMoveListener(true);

            _Ext.getBody().on('click', this.onInspectionClick, { me : this });
        }

        this.fireEvent('start', this);

        this.active = true;
    },


    stop : function (suppressEvent) {
        if (!this.active) return;

        this.active = false;

        var _Ext            = this.getExt();

        Ext.destroy(this.boxIndicatorEl);
        this.boxIndicatorEl = null;

        if (!suppressEvent) {
            this.fireEvent('stop', this);
        }

        if (_Ext && _Ext.getBody) {
            this.toggleMouseMoveListener(false);
            _Ext.getBody().un('click', this.onInspectionClick, { me : this });
        }

        this.inspectedComponent = this.inspectedComponentXType = null;
    },


    // Listen for mousemove in the frame and any direct iframe children too
    toggleMouseMoveListener : function (enabled) {
        var _Ext = this.getExt();

        if (!_Ext) return;

        var frames = _Ext.getBody().select('iframe');
        var fn     = enabled ? 'on' : 'un';

        //                                                  Avoid using "this" directly due to Touch incompatibilities with Ext
        _Ext.getBody()[fn]('mousemove', this.onMouseMove, { me : this }, { buffer : this.bufferTime });

        for (var i = 0; i < frames.getCount(); i++) {
            var innerExt = frames.item(i).dom.contentWindow.Ext;

            //                                                                                     Avoid using "this" directly due to Touch incompatibilities with Ext
            innerExt && innerExt.getBody && innerExt.getBody()[fn]('mousemove', this.onMouseMove, { me : this }, { buffer : this.bufferTime });
        }
    },

    onInspectionClick : function (e, t) {
        if (!this.boxIndicatorEl) return;

        // Avoid using "this" directly due to Touch incompatibilities with Ext
        var me = this.me;

        me.toggleMouseMoveListener(false);

        // If user clicks on a non-component, or clicking outside currently selected component - we abort
        if (!me.inspectedComponent || me.findComponentByTarget(t) !== me.inspectedComponent) {
            me.stop();
        } else {
            me.fireEvent('targetselected', me, me.inspectedComponent, me.inspectedComponentXType);
        }
    },


    onMouseMove : function (e, t) {
        //Have to avoid using "this" directly due to Touch incompatibilities with Ext
        var me = this.me;

        if (!me.boxIndicatorEl) return;

        var cmp = me.findComponentByTarget(t);

        if (cmp) {
            if (cmp === me.inspectedComponent) return;

            var xtype = me.resolveComponentXtype(cmp);

            me.inspectedComponent      = cmp;
            me.inspectedComponentXType = xtype;

            me.highlightTarget(cmp.el.dom);
            me.updateHighlightContent(cmp, xtype);

            me.fireEvent('targethover', me, me.inspectedComponent, me.inspectedComponentXType);
        }
    },


    resolveComponentXtype : function (cmp) {
        var xtype = (cmp.getXType && cmp.getXType()) || cmp.xtype;

        // If the found component doesn't have an own xtype, look up the superclass chain to find one
        if (!xtype) {
            var cls = cmp;
            for (var i = 0; i < 10 && !xtype; i++) {
                cls   = cmp.superclass;
                xtype = cls.xtype;
            }
        }

        return xtype;
    },


    updateHighlightContent : function (cmp, xtype) {
        var html;

        var link = {
            tag  : 'a',
            cls  : 'target-inspector-label',
            href : '#'
        };

        if (typeof cmp === 'string') {
            html = cmp;
        } else if (Ext.ClassManager) {

            // If recorder is visible, let's add some targeting suggestions
            var recorderPanel = Ext.ComponentQuery.query('recorderpanel')[0];
            var targetWindow  = cmp.el.dom.ownerDocument.defaultView;
            var pageExtHasCQ  = targetWindow && targetWindow.Ext && targetWindow.Ext.ComponentQuery;

            if (recorderPanel && recorderPanel.isVisible()) {
                var cq;

                // Make sure this is an Ext JS page and the recorder used is the Ext JS recorder (not the generic one)
                if (pageExtHasCQ && recorderPanel.recorder.extractor.findComponentQueryFor) {
                    cq = recorderPanel.recorder.extractor.findComponentQueryFor(cmp);
                    cq = (cq && cq.query) || xtype;
                }
                html = '>>' + (cq || xtype);
            } else {
                var clsName = this.findExtAncestorClassName(cmp);

                if (clsName) {
                    var docsPath = Siesta.Resource('Siesta.Project.Browser.UI.DomContainer', 'docsUrlText');
                    var framework;

                    if (Ext.versions.touch) {
                        framework = 'touch';
                    } else {
                        framework = 'extjs';
                    }

                    link.target = '_blank';
                    link.href   = Ext.String.format(docsPath, framework, clsName);
                    link.title  = Siesta.Resource('Siesta.Project.Browser.UI.DomContainer', 'viewDocsText') + clsName;
                }

                html = xtype;
            }
        }

        if (html) {
            link.html  = html;
            var linkEl = this.boxIndicatorEl.down('.target-inspector-label', true);

            linkEl.href      = link.href;
            linkEl.title     = link.title;
            linkEl.innerHTML = html;
        }
    },


    highlightTarget : function (node, content, point) {
        var boxStyle       = this.boxIndicatorEl.dom.style;

        if (!this.active) {
            this.start();
        }

        if (node) {
            var offsets = this.getOffsets(node);
            var left    = (Ext.fly(node).getX() - 2 + offsets[0]);
            var top     = (Ext.fly(node).getY() - 2 + offsets[1]);
            var width   = ((Ext.fly(node).getWidth() || (parseInt(node.style.width.substring(0, node.style.width.length - 2), 10))) + 2);
            var height  = ((Ext.fly(node).getHeight() || (parseInt(node.style.height.substring(0, node.style.height.length - 2), 10))) + 2);

            if (this.injectIntoTestFrame) {
                left += this.window.document.body.scrollLeft;
                top  += this.window.document.body.scrollTop;
            }

            var translateStyle = bowser.opera ? ('translate(' + left + 'px,' + top + 'px)') :
                ('translate3d(' + left + 'px,' + top + 'px, 0)');

            // Regular getWidth/getHeight doesn't work if another iframe is on the page
            boxStyle.setProperty('transform', translateStyle)
            boxStyle.width     = width + 'px';
            boxStyle.height    = height + 'px';
            boxStyle['border-color']   = 'red';

            if (this.showSelectorText && content) {
                this.updateHighlightContent(content);
            }
        } else {
            boxStyle['border-color']   = '#ccc';
        }

        var crosshair = this.boxIndicatorEl.down('.target-inspector-coordinate', true);

        if (point) {
            translateStyle  = bowser.opera ? ('translate(' + point[0] + 'px,' + point[1] + 'px)') :
                ('translate3d(' + point[0] + 'px, ' + point[1] + 'px, 0)');

            crosshair.style.setProperty('display', 'block');
            crosshair.style.setProperty('transform', translateStyle)
        } else {
            crosshair.style.setProperty('display', 'none');
        }
    },


    findComponentByTarget : function (target) {
        var Ext     = this.getExt();
        var testDoc = this.window.document;

        // Handle potentially having another Ext copy loaded in another frame
        if (target.ownerDocument !== testDoc) {
            var innerFrame = (target.ownerDocument.parentWindow || target.ownerDocument.defaultView).frameElement;
            Ext            = innerFrame.contentWindow.Ext;
        }

        var cmp

        while (!cmp && target && target.nodeName !== 'BODY') {
            cmp    = Ext.getCmp(target.id);
            target = target.parentNode;
        }

        return cmp;
    },


    getOffsets : function (node) {
        var targetDoc = this.window.document;
        var offsets   = [0, 0]

        if (node.ownerDocument !== targetDoc) {
            var innerFrame = (node.ownerDocument.parentWindow || node.ownerDocument.defaultView).frameElement;

            offsets = Ext.fly(innerFrame).getXY();
            offsets[0] -= node.ownerDocument.body.scrollLeft;
            offsets[1] -= node.ownerDocument.body.scrollTop;
        }

        return offsets;
    },


    findExtAncestorClassName : function (cmp) {
        while (cmp) {
            var name = Ext.ClassManager.getName(cmp);

            if (name.match(/^Ext./)) return name;

            cmp = cmp.superclass;
        }

        return '';
    }
});