/*

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

*/
Role('Siesta.Util.Role.Dom', {

    does        : [
        Siesta.Util.Role.CanCalculatePageScroll
    ],

    has : {
        doesNotIncludeMarginInBodyOffset : false
    },

    methods : {

        isCrossOriginWindow : function (win) {
            try {
                var doc     = win.document;
            } catch (e) {
                return true
            }

            // Safari doesn't throw exception when trying to access x-domain frames
            return !doc
        },


        closest : function (elem, selector, maxLevels) {
            maxLevels = maxLevels || Number.MAX_VALUE;

            var docEl = elem.ownerDocument.documentElement;

            // Get closest match
            for (var i = 0; i < maxLevels && elem && elem !== docEl; elem = elem.parentNode) {
                if (Siesta.Sizzle.matchesSelector(elem, selector)) {
                    return elem;
                }

                i++;
            }

            return false;
        },


        contains : function (parentEl, childEl) {
            if (!parentEl) return false

            if (parentEl.contains)  return parentEl.contains(childEl)

            // SVG elements in IE does not have "contains" method
            if (parentEl.compareDocumentPosition)
                return parentEl === childEl || Boolean(parentEl.compareDocumentPosition(childEl) & 16)

            throw new Error("Can't determine `contains` status")
        },


        matches : function (node, selector) {
            return Siesta.Sizzle.matchesSelector(node, selector);
        },


        // returns { left : Number, top : Number } object in page coordinates
        offset : function (elem) {
            if (!elem) return null

            var doc     = elem.ownerDocument
            if (!doc) return null

            if (elem === doc.body) return this.bodyOffset(elem);

            var box = this.getBoundingClientRect(elem)

            var win     = doc.defaultView || doc.parentWindow

            return box ? {
                left    : this.viewportXtoPageX(Math.floor(box.left), win),
                top     : this.viewportYtoPageY(Math.floor(box.top), win)
            } : {
                left    : 0,
                top     : 0
            }
        },


        bodyOffset: function (body) {
            var top     = body.offsetTop,
                left    = body.offsetLeft;

            this.initializeOffset();

            if (this.doesNotIncludeMarginInBodyOffset) {
                var style = getComputedStyle(body);

                top     += parseFloat(style.marginTop) || 0;
                left    += parseFloat(style.marginLeft) || 0;
            }

            return { top: top, left: left };
        },


        initializeOffset: function () {
            var body        = document.body,
                container   = document.createElement("div"),
                bodyMarginTop = parseFloat(getComputedStyle(body).marginTop) || 0,
                html        = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";

            var styles      = { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" };

            for (var o in styles) {
                container.style[ o ] = styles[ o ];
            }

            container.innerHTML     = html;

            body.insertBefore(container, body.firstChild);

            var innerDiv            = container.firstChild;
            var checkDiv            = innerDiv.firstChild;
            var td                  = innerDiv.nextSibling.firstChild.firstChild;

            checkDiv.style.position = "fixed";
            checkDiv.style.top      = "20px";
            checkDiv.style.position = checkDiv.style.top = "";

            innerDiv.style.overflow = "hidden";
            innerDiv.style.position = "relative";

            this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);

            body.removeChild(container);

            this.initializeOffset   = function () {};
        },


        getElementWidth : function (el) {
            return this.getBoundingClientRect(el).width;
        },


        getElementHeight : function (el) {
            return this.getBoundingClientRect(el).height;
        },


        getWindowSize : function (win) {
            var doc         = win.document

            return {
                width       : win.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth,
                height      : win.innerHeight || doc.documentElement.clientHeight || doc.body.clientHeight
            }
        },


        isPointWithinElement : function (x, y, el) {
            var rect = this.getBoundingClientRect(el);

            return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
        },


        isElementReachableAt : function (el, pageX, pageY, allowChild) {
            allowChild      = allowChild !== false

            var doc         = el.ownerDocument
            var win         = doc.defaultView || doc.parentWindow
            var foundEl     = doc.elementFromPoint(this.pageXtoViewportX(pageX, win), this.pageYtoViewportY(pageY, win))

            return foundEl && (foundEl === el || allowChild && this.contains(el, foundEl))
        },


        isElementReachableAtCenter : function (el, allowChild) {
            allowChild      = allowChild !== false

            var offsets     = this.offset(el);

            return this.isElementReachableAt(
                el,
                offsets.left + (this.getElementWidth(el) / 2),
                offsets.top + (this.getElementHeight(el) / 2),
                allowChild
            );
        },

        // patched version to support SVG in IE11/Edge
        getBoundingClientRect : function (el) {
            var svgEl = el.ownerSVGElement;

            if (svgEl && (bowser.msie || bowser.msedge)) {
                var elBox   = el.getBBox(),
                    svgRect = svgEl.getBoundingClientRect(),
                    left    = svgRect.left + elBox.x,
                    top     = svgRect.top + elBox.y,
                    right   = left + elBox.width,
                    bottom  = top + elBox.height;

                return {
                    x      : left,
                    y      : top,
                    left   : left,
                    top    : top,
                    bottom : bottom,
                    height : elBox.height,
                    width  : elBox.width
                };
            } else {
                return el.getBoundingClientRect();
            }
        },


        nodeIsUnloaded : function (el) {
            try {
                // throws if accessed when element belonged to an iframe that's no longer in DOM
                el && el.tagName

                var doc = el.ownerDocument
                var win = doc && (doc.defaultView || doc.parentWindow)

                return !Boolean(win)
            } catch (e) {
                // exception here probably means the "lastOverEl" is from freed context (unloaded page)
                // access to such elements throws exceptions in IE
                el          = null

                return true
            }
        },


        nodeIsOrphan : function (el) {
            var doc = el.ownerDocument

            return !doc || !doc.body || !el.parentNode || !(el === doc.body || $.contains(doc.body, el));
        },


        getNodeParents : function (node) {
            var doc   = node.ownerDocument;
            var nodes = [];

            for (; node && node.parentNode; node = node.parentNode) {
                nodes.unshift(node);
            }

            return nodes;
        },


        getCommonAncestor : function (node1, node2) {
            var parents1 = this.getNodeParents(node1);
            var parents2 = this.getNodeParents(node2);

            // Make sure both nodes are part of same DOM tree
            if (parents1[ 0 ] != parents2[ 0 ]) return null;

            for (var i = 0; i < parents1.length; i++) {
                if (parents1[ i ] !== parents2[ i ]) {
                    return parents1[ i - 1 ];
                }
            }
        }
    }
})