/*

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

*/
Role('Siesta.Test.Simulate.Event', {

    does        : [ JooseX.Observable ],

    requires : [
        '$',
        'typeOf',
        'createTextEvent',
        'createMouseEvent',
        'createKeyboardEvent'
    ],

    has : {
        bowser              : function () { return bowser },
        browser             : function () { return bowser },

        actionDelay         : 100,
        afterActionDelay    : 100,

        /**
         * @cfg {String} simulateEventsWith
         *
         * This option is IE9-strict mode (and probably above) specific. It specifies, which events simulation function Siesta should use.
         * The choice is between 'dispatchEvent' (W3C standard) and 'fireEvent' (MS interface) - both are available in IE9 strict mode
         * and both activates different event listeners. See this blog post for detailed explanations:
         * <http://www.digitalenginesoftware.com/blog/archives/76-DOM-Event-Model-Compatibility-or-Why-fireEvent-Doesnt-Trigger-addEventListener.html>
         *
         * Valid values are "dispatchEvent" and "fireEvent".
         *
         * The framework specific adapters choose the most appropriate value automatically (unless explicitly configured).
         */
        simulateEventsWith  : {
            is   : 'rw',
            lazy : function () { return 'dispatchEvent' }
        }
    },

    methods : {

        normalizeEventName : function (eventName) {
            return eventName
        },


        /**
         * This method will simulate an event triggered by the passed element. If no coordinates are supplied in the options object, the center of the element
         * will be used.
         * @param {Siesta.Test.ActionTarget} el
         * @param {String} type The type of event (e.g. 'mouseover', 'click', 'keypress')
         * @param {Object} the options for the event. See http://developer.mozilla.org/en/DOM/event for reference.
         */
        simulateEvent : function (el, type, options) {
            var global  = this.global;
            options     = options || {};

            if (this.typeOf(el) === 'Array') {
                if (el.length == 0) el = this.currentPosition.slice()

                if (!('clientX' in options)) {
                    options.clientX = el[ 0 ];
                }

                if (!('clientY' in options)) {
                    options.clientY = el[ 1 ];
                }
            }

            el          = this.test.normalizeElement(el);
            type        = this.normalizeEventName(type)

            var evt     = this.createEvent(type, options, el);

            if (evt) {
                this.maintainScrollPositionDuring(function () {
                    evt.synthetic = true;

                    this.mimicBrowserBehaviorBefore(evt, type, el);

                    this.dispatchEvent(el, type, evt);

                    !this.isEventPrevented(evt) && this.mimicBrowserBehaviorAfter(evt, type, el);
                })
            }

            this.fireEvent('eventsimulated', evt, el, type, options)

            return evt;
        },


        createEvent : function (type, options, el) {
            var event

            if (/^textinput$/i.test(type)) {
                event = this.createTextEvent(type, options, el);
            } else if (/^mouse(over|out|down|up|move|enter|leave)|contextmenu|wheel|(dbl)?click$/.test(type) || /^(ms)?pointer/i.test(type)) {
                event = this.createMouseEvent(type, options, el);
            } else if (/^key(up|down|press)$/.test(type)) {
                event = this.createKeyboardEvent(type, options, el);
            } /*else if (/^touch/.test(type)) {
             return this.createTouchEvent(type, options, el);
             }*/
            else if (/^change$|^input$|^submit/.test(type)) {
                event = this.createGenericEvent(type, options, el);
            }
            else
                event = this.createHtmlEvent(type, options, el);

            // IE>=9 somehow reports that "defaultPrevented" property of the event object is `false`
            // even that "preventDefault()" has been called on the object
            // more over, immediately after call to "preventDefault()" the property is updated
            // but down in stack it is replaced with "false" again somehow
            // we setup our own, additional property, indicating that event has been prevented
            if (event && bowser.msie && bowser.version >= 9) {
                var prev = event.preventDefault

                event.preventDefault = function () {
                    arguments.callee.$prevented = true;
                    this.returnValue = false

                    return prev && prev.apply(this, arguments)
                }
            }

            return event
        },


        isEventPrevented : function (event) {
            // our custom property - takes highest priority
            if (event.preventDefault && this.typeOf(event.preventDefault.$prevented) == 'Boolean') return event.preventDefault.$prevented

            // W3C standards property
            if (this.typeOf(event.defaultPrevented) == 'Boolean') return event.defaultPrevented

            return event.returnValue === false
        },


        createGenericEvent : function (type, options, el) {
            var doc                 = el.ownerDocument;

            if (doc.createEvent && this.getSimulateEventsWith() == 'dispatchEvent') {
                var evt             = doc.createEvent("Events");

                evt.initEvent(type, true, true);

                return evt;
            } else if (doc.createEventObject) {
                var event           = doc.createEventObject()

                event.srcElement    = el

                event.bubbles       = options.bubbles
                event.cancelBubble  = !options.bubbles
                event.type          = type

                return event
            }
        },


        createHtmlEvent : function (type, options, el) {
            var doc = el.ownerDocument;

            if (doc.createEvent && this.getSimulateEventsWith() == 'dispatchEvent') {
                var evt = doc.createEvent("HTMLEvents");
                evt.initEvent(type, false, false);
                return evt;
            } else if (doc.createEventObject) {
                return doc.createEventObject();
            }
        },


        dispatchEvent : function (el, type, evt) {

            // use W3C standard when available and allowed by "simulateEventsWith" option
            if (el.dispatchEvent && this.getSimulateEventsWith() == 'dispatchEvent') {
                el.dispatchEvent(evt);
            } else if (el.fireEvent) {
                // IE 6,7,8 can't dispatch many events cleanly - throws exceptions
                try {
                    // this is the serios nominant to the best-IE-bug-ever prize and it's IE7 specific
                    // accessing the "scrollLeft" property on document or body triggers a synchronous(!) "resize" event on the window
                    // ExtJS uses a singleton for Ext.EventObj and its "target" property gets overwritten with "null"
                    // thus consequent event handlers fails
                    // doing an access to that property to cache it
                    var doc  = this.global.document.documentElement;
                    var body = this.global.document.body;

                    var xxx = doc && doc.scrollLeft || body && body.scrollLeft || 0;

                    el.fireEvent('on' + type.toLowerCase(), evt);
                } catch (e) {
                }

                // in IE, the "fireEvent" does not bubble the "change" event
                // we try to bubble it manually (to fix the TaskBoard2.x "subtasks" tests, but then
                // the target el is not set correctly and it goes too deep into Ext sources, so commenting for now
//                if (type == 'change') {
//                    if (el != doc && el != body && el.parentElement) this.dispatchEvent(el.parentElement, type, evt)
//                }
            } else
                throw "Can't dispatch event: " + type

            return evt;
        },


        mimicBrowserBehaviorBefore : function (event, type, target) {
        },


        mimicBrowserBehaviorAfter : function (event, type, target) {
            var tagName = target.tagName.toLowerCase();

            switch (type) {
                case 'mousedown':
                    // it seems selection is actually cleared in "pointerup" (mouseup)
                    if (this.isTextInput(target)) {
                        this.mimicClearTextSelection(target);
                    }
                    break;
                case 'click':
                    if (
                        bowser.msie && bowser.version < 11 &&
                        tagName === 'a' &&
                        target.getAttribute("href")
                    ) {
                        this.mimicHashUpdate(target);
                    } else if (tagName === 'option' && !target.getAttribute('disabled')) {
                        this.mimicOptionSelect(target);
                    }
                    break;
                case 'dblclick':
                    if (this.isTextInput(target)) {
                        this.mimicDblClickTextSelection(target);
                    }
                    break;
                case 'keydown':
                    var KeyCodes = Siesta.Test.UserAgent.KeyCodes().keys

                    if (event.keyCode === KeyCodes.BACKSPACE) {
                        this.mimicHistoryChangeAfterBackspace(event, target);
                    }
                    break;
            }
        },

        // IE9+
        // Breaks IE9 with Ext JS 5.1.0, tested in .540_extjs_type.t.js?5.1.0
        mimicClearTextSelection : function (target) {
            var extVersion = this.global.Ext && this.global.Ext.versions;
            var isExtJS51  = extVersion && extVersion.extjs && extVersion.extjs.equals('5.1.0.107');

            if (!bowser.msie || !isExtJS51 || (bowser.version > 9)) {
                this.test.selectText(target, target.value.length - 1, target.value.length);

                this.test.setCaretPosition(target, target.value.length);
            }
        },


        mimicDblClickTextSelection : function (target) {
            this.test.selectText(target);
        },

        // After a click action in old IE, change location hash manually
        mimicHashUpdate : function (el) {
            var href = el.getAttribute("href").match(/#(.*)/);

            if (href) {
                this.global.location.hash = href[1];
            }
        },


        mimicHistoryChangeAfterBackspace : function (event, target) {
            // Chrome+Safari doesn't trigger page navigation (as of Chrome52)
            if (bowser.webkit || bowser.blink) return;

            var doc      = target.ownerDocument;

            if (!target.isContentEditable && doc.designMode.toLowerCase() !== "on" && !this.isTextInput(target)) {
                var elWindow = target.ownerDocument.defaultView || target.ownerDocument.parentWindow;

                if (event.shiftKey) {
                    this.mimicNextHistory(elWindow);
                } else {
                    this.mimicPreviousHistory(elWindow);
                }
            }
        },


        mimicPreviousHistory : function (global) {
            global.history.back();
        },


        mimicNextHistory : function (global) {
            global.history.forward();
        },


        mimicOptionSelect : function (optionNode) {
            var select   = this.$(optionNode).closest('select')[0];
            var oldValue = select.value;

            select.value = optionNode.value;

            if (oldValue !== optionNode.value) {
                this.simulateEvent(select, "change");
            }
        }
    }
});