/*

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

*/
/**
@class Siesta.Test.UserAgent.Touch

This is a mixin, providing the touch events simulation functionality.
*/
Role('Siesta.Test.UserAgent.Touch', {

    requires        : [
        'normalizeElement'
    ],

    has: {
        notSupportedWarned  : false
    },


    methods: {

        checkTouchEventsSupport : function () {
            var supports        = Siesta.Project.Browser.FeatureSupport().supports

            var root            = this.getRootTest()

            if (!supports.TouchEvents && !supports.PointerEvents && !supports.MSPointerEvents && !root.notSupportedWarned) {
                root.notSupportedWarned = true

                this.warn("Touch events are not supported by browser. For Chrome, you can enable them, by launching it with: --args --touch-events")
            }
        },

        /**
         * This method taps the passed target, which can be of several different types, see {@link Siesta.Test.ActionTarget}
         *
         * @param {Siesta.Test.ActionTarget} target Target for this action
         * @param {Function} callback (optional) A function to call after action.
         * @param {Object} scope (optional) The scope for the callback
         * @param {Object} options (optional) Any options that will be used when simulating the event. For information about possible
         * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent>
         * @param {Array} offset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or
         * ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge.
         */
        tap : function (target, callback, scope, options, offset, performTargetCheck) {
            this.checkTouchEventsSupport()

            target      = target || this.getCursorPagePosition()

            if (performTargetCheck !== false && callback) {
                this.waitForTargetAndSyncMousePosition(
                    target, offset, this.tap, [ target, callback, scope, options, offset, false ]
                );
                return;
            }

            var context     = this.getNormalizedTopElementInfo(target, true, 'tap', offset);

            if (!context) {
                callback && callback.call(scope || this);

                return;
            }

            this.runPromiseAsync(
                this.simulator.simulateTap(context, options),
                'tap',
                callback,
                scope
            )
        },


        /**
         * This method double taps the passed target, which can be of several different types, see {@link Siesta.Test.ActionTarget}
         *
         * @param {Siesta.Test.ActionTarget} target Target for this action
         * @param {Function} callback (optional) A function to call after action.
         * @param {Object} scope (optional) The scope for the callback
         * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible
         * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent>
         * @param {Array} offset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge.
         */
        doubleTap : function (target, callback, scope, options, offset, performTargetCheck) {
            this.checkTouchEventsSupport()

            target      = target || this.getCursorPagePosition()

            if (performTargetCheck !== false && callback) {
                this.waitForTargetAndSyncMousePosition(
                    target, offset, this.doubleTap, [ target, callback, scope, options, offset, false ]
                );
                return;
            }

            var context = this.getNormalizedTopElementInfo(target, true, 'doubleTap', offset);

            if (!context) {
                callback && callback.call(scope || this);

                return;
            }

            this.runPromiseAsync(
                this.simulator.simulateDoubleTap(context, options),
                'doubleTap',
                callback,
                scope
            )
        },


        // backward-compat with SenchaTouch class, which used to have all lower-cased method
        longpress : function () {
            return this.longPress.apply(this, arguments)
        },


        /**
         * This performs a long press on the passed target, which can be of several different types, see {@link Siesta.Test.ActionTarget}
         *
         * @param {Siesta.Test.ActionTarget} target Target for this action
         * @param {Function} callback (optional) A function to call after action.
         * @param {Object} scope (optional) The scope for the callback
         * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible
         * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent>
         * @param {Array} offset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge.
         */
        longPress : function (target, callback, scope, options, offset, performTargetCheck) {
            this.checkTouchEventsSupport()

            target      = target || this.getCursorPagePosition()

            if (performTargetCheck !== false && callback) {
                this.waitForTargetAndSyncMousePosition(
                    target, offset, this.longPress, [ target, callback, scope, options, offset, false ]
                );
                return;
            }

            var context = this.getNormalizedTopElementInfo(target, true, 'longPress', offset);

            if (!context) {
                callback && callback.call(scope || this);

                return;
            }

            this.runPromiseAsync(
                this.simulator.simulateLongPress(context, options),
                'longPress',
                callback,
                scope
            )
        },


        /**
         * This method performs a pinch between the two specified points. It draws a line between the specified points and then moves 2 touches along that line,
         * so that the final distance between the touches becomes `scale * original distance`.
         *
         * This method can be called either in the full form with 2 different targets:
         *

    t.pinch("#grid > .col1", "#grid > .col2", 3, function () { ... })

         * or, in the short form, where the 2nd target argument is omitted:
         *

    t.pinch("#grid > .col1", 3, function () { ... })

         * In the latter form, `target2` is considered to be the same as `target1`.
         *
         * If `target1` and `target2` are the same, and no offsets are provided, offsets are set to the following values:
         *

    offset1     = [ '25%', '50%' ]
    offset2     = [ '75%', '50%' ]

         *
         *
         * @param {Siesta.Test.ActionTarget} target1 First point for pinch
         * @param {Siesta.Test.ActionTarget} target2 Second point for pinch. Can be omitted, in this case both points will belong to `target1`
         * @param {Number} scale The multiplier for a final distance between the points
         * @param {Function} callback A function to call after the pinch has completed
         * @param {Object} scope A scope for the `callback`
         * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible
         * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent>
         * @param {Array} offset1 An X,Y offset relative to the target1. Example: [20, 20] for 20px or ["50%", "100%-2"]
         * for the point in the center horizontally and 2px from the bottom edge.
         * @param {Array} offset2 An X,Y offset relative to the target1. Example: [20, 20] for 20px or ["50%", "100%-2"]
         * for the point in the center horizontally and 2px from the bottom edge.
         */
        pinch : function (target1, target2, scale, callback, scope, options, offset1, offset2) {
            this.checkTouchEventsSupport()

            var me          = this;

            if (this.typeOf(target2) == 'Number') {
                offset2     = offset1
                offset1     = options
                options     = scope
                scope       = callback
                callback    = scale
                scale       = target2
                target2     = target1
            }

            if (target2 == null) target2 = target1

            if (target1 == target2 && !offset1 && !offset2) {
                offset1     = [ '25%', '50%' ]
                offset2     = [ '75%', '50%' ]
            }

            var context1    = this.getNormalizedTopElementInfo(target1, true, 'pinch: target1', offset1);
            var context2    = this.getNormalizedTopElementInfo(target2, true, 'pinch: target2', offset2);

            if (!context1 || !context2) {
                var R  = Siesta.Resource('Siesta.Test.Browser');

                this.waitFor({
                    method          : function () {
                        var el1     = me.normalizeElement(target1, true)
                        var el2     = me.normalizeElement(target2, true)

                        return el1 && me.elementIsTop(el1, true, offset) && el2 && me.elementIsTop(el2, true, offset)
                    },
                    callback        : function () {
                        me.pinch(target1, target2, scope, callback, scope, options, offset1, offset2)
                    },
                    assertionName   : 'waitForTarget',
                    description     : ' ' + R.get('target') + ' "' + target1 + '" and "' + target2 + '" ' + R.get('toAppear')
                });

                return
            }

            this.runPromiseAsync(
                this.simulator.simulatePinch(context1, context2, options),
                'pinch',
                callback,
                scope
            )
        },


        /**
         * This method will simulate a drag and drop operation between either two points or two DOM elements.
         *
         * @param {Siesta.Test.ActionTarget} source {@link Siesta.Test.ActionTarget} value for the drag starting point
         * @param {Siesta.Test.ActionTarget} target {@link Siesta.Test.ActionTarget} value for the drag end point
         * @param {Function} callback A function to call after the drag operation is completed.
         * @param {Object} scope (optional) the scope for the callback
         * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible
         * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent>
         * @param {Boolean} dragOnly true to skip the mouseup and not finish the drop operation.
         * @param {Array} sourceOffset (optional) An X,Y offset relative to the source. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge.
         * @param {Array} targetOffset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge.
         */
        touchDragTo : function (source, target, callback, scope, options, dragOnly, sourceOffset, targetOffset) {
            var me          = this
            var context1    = this.getNormalizedTopElementInfo(source, true, 'touchDragTo: source', sourceOffset);
            var context2    = this.getNormalizedTopElementInfo(target, true, 'touchDragTo: target', targetOffset);

            if (!context1 || !context2) {
                var R  = Siesta.Resource('Siesta.Test.Browser');

                this.waitFor({
                    method          : function () {
                        var el1     = me.normalizeElement(source, true)
                        var el2     = me.normalizeElement(target, true)

                        return el1 && me.elementIsTop(el1, true, sourceOffset) && el2 && me.elementIsTop(el2, true, targetOffset)
                    },
                    callback        : function () {
                        me.touchDragTo(source, target, callback, scope, options, dragOnly, sourceOffset, targetOffset)
                    },
                    assertionName   : 'waitForTarget',
                    description     : ' ' + R.get('target') + ' "' + source + '" and "' + target + '" ' + R.get('toAppear')
                });

                return
            }

            this.runPromiseAsync(
                this.simulator.simulateTouchDrag(context1.localXY, context2.localXY, options, dragOnly),
                'touchDragTo',
                callback,
                scope
            )
        },


        /**
         * This method will simulate a drag and drop operation from a point (or DOM element) and move by a delta.
         *
         * @param {Siesta.Test.ActionTarget} source {@link Siesta.Test.ActionTarget} value as the drag starting point
         * @param {Array} delta The amount to drag from the source coordinate, expressed as [ x, y ]. E.g. [ 50, 10 ] will drag 50px to the right and 10px down.
         * @param {Function} callback A function to call after the drag operation is completed.
         * @param {Object} scope (optional) the scope for the callback
         * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible
         * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent>
         * @param {Boolean} dragOnly true to skip the mouseup and not finish the drop operation.
         * @param {Array} offset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge.
         */
        touchDragBy : function (source, delta, callback, scope, options, dragOnly, offset) {
            var me      = this;
            var context = this.getNormalizedTopElementInfo(source, true, 'touchDragBy', offset);

            if (!context) {
                this.waitForTarget(source, function() {
                    this.touchDragBy(source, delta, callback, scope, options, dragOnly, offset)
                }, this, null, offset)

                return
            }

            var sourceXY        = context.globalXY;
            var targetXY        = [ sourceXY[ 0 ] + delta[ 0 ], sourceXY[ 1 ] + delta[ 1 ] ];

            this.runPromiseAsync(
                this.simulator.simulateTouchDrag(sourceXY, targetXY, options, dragOnly),
                'touchDragBy',
                callback,
                scope
            )
        },


        /**
         * This method will simulate a swipe operation between either two points or on a single DOM element.
         *
         * @param {Siesta.Test.ActionTarget} target Target for this action
         * @param {String} direction Either 'left', 'right', 'up' or 'down'
         * @param {Function} callback A function to call after the swing operation is completed
         * @param {Object} scope (optional) the scope for the callback
         * @param {Object} options (optional) Any options that will be used when simulating the event. For information about possible
         * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent>
         */
        swipe : function (target, direction, callback, scope, options, performTargetCheck) {
            this.checkTouchEventsSupport()

            target      = target || this.getCursorPagePosition()

            if (performTargetCheck !== false && callback) {
                this.waitForTargetAndSyncMousePosition(
                    target, null, this.swipe, [ target, direction, callback, scope, options, false ]
                );
                return;
            }

            var context = this.getNormalizedTopElementInfo(target, true, 'swipe');

            if (!context) {
                callback && callback.call(scope || this);

                return;
            }

            var Ext     = this.Ext()
            var R       = Siesta.Resource('Siesta.Test.SenchaTouch')

            var box     = Ext.fly(context.el).getBox(),
                x       = box.x,
                y       = box.y,
                width   = box.width,
                height  = box.height,
                centerX = x + width / 2,
                centerY = y + height / 2,
                start,
                end,
                edgeCoef    = 0.1

            // Since this method accepts elements as target, we need to assure that we swipe at least about 150px
            // using Math.max below etc
            switch (direction) {
                case 'u':
                case 'up':
                    start       = [ centerX, y + height * (1 - edgeCoef) ];
                    end         = [ centerX, y + height * edgeCoef ];

                    end[ 1 ]    = Math.min(start[ 1 ] - 100, end[ 1 ]);
                break;

                case 'd':
                case 'down':
                    start       = [ centerX, y + height * edgeCoef ];
                    end         = [ centerX, y + height * (1 - edgeCoef) ];

                    end[ 1 ]    = Math.max(start[ 1 ] + 100, end[ 1 ]);
                break;

                case 'r':
                case 'right':
                    start       = [ x + width * edgeCoef, centerY ];
                    end         = [ x + width * (1 - edgeCoef), centerY ];

                    end[ 0 ]    = Math.max(start[ 0 ] + 100, end[ 0 ]);
                break;

                case 'l':
                case 'left':
                    start       = [ x + width * (1 - edgeCoef), centerY ];
                    end         = [ x + width * edgeCoef, centerY ];

                    end[ 0 ]    = Math.min(start[ 0 ] - 100, end[ 0 ]);
                break;

                default:
                    throw R.get('invalidSwipeDir') + ': ' + direction;
            }

            this.touchDragTo(start, end, callback, scope, options);
        }
    }
});