/*

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

*/
Role('Siesta.Recorder.Role.CanRecordMouseMove', {

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

    has : {
        recordMouseMove         : true,

        // used by RootCause, position in page coordinates
        cursorPosition          : Joose.I.Array,

        // used by RootCause (probably not needed anymore)
        lastMoveTimestamp       : null,

        // used by RootCause
        lastMouseMoveEvent      : null
    },


    override : {

        initialize : function () {
            this.SUPERARG(arguments)

            this.onBodyMouseMove        = this.safeBind(this.onBodyMouseMove);
            this.onBodyMouseOver        = this.safeBind(this.onBodyMouseOver);
            this.onBodyMouseOut         = this.safeBind(this.onBodyMouseOut);
        },


        onStart : function () {
            this.SUPERARG(arguments);

            var win     = this.window;
            var doc     = win.document;
            var body    = doc.body

            if (this.recordMouseMove) {
                // Need to also observe `mouseover` since it's fired before `mousemove` in case there is a crash
                // in `mousemove` handler we need to know the exact cursor position
                body.addEventListener('mouseover', this.onBodyMouseOver, true);
                body.addEventListener('mouseout', this.onBodyMouseOut, true);
                body.addEventListener('mousemove', this.onBodyMouseMove, true);
            }
        },


        onStop : function () {
            this.SUPERARG(arguments);

            var win     = this.window;
            var doc     = win.document;
            var body    = doc.body

            if (this.recordMouseMove) {
                body.removeEventListener('mouseover', this.onBodyMouseOver, true);
                body.removeEventListener('mouseout', this.onBodyMouseOut, true);
                body.removeEventListener('mousemove', this.onBodyMouseMove, true);

                this.lastMouseMoveEvent = null
            }
        }
    },

    methods : {

        saveMouseMoveData : function (event) {
            var me                  = this

            me.lastMoveTimestamp    = new Date() - 0

            me.cursorPosition[ 0 ]  = me.viewportXtoPageX(event.clientX, me.window)
            me.cursorPosition[ 1 ]  = me.viewportYtoPageY(event.clientY, me.window)

            me.lastMouseMoveEvent   = event
        },


        onBodyMouseMove        : function (e) {
            // Skip test playback events and mouse moves in frames
            if ((this.ignoreSynthetic && e.synthetic) || e.target.ownerDocument !== this.window.document) return;

            this.saveMouseMoveData(e)

            this.processBodyMouseMove(e)
        },


        processBodyMouseMove : function (e) {
        },


        onBodyMouseOver : function (e) {
            // Skip test playback events and mouse moves in frames
            if ((this.ignoreSynthetic && e.synthetic) || e.target.ownerDocument !== this.window.document) return;

            this.saveMouseMoveData(e)

            this.processBodyMouseOver(e)
        },


        processBodyMouseOver : function (e) {
        },


        onBodyMouseOut : function (e) {
            // Skip test playback events and mouse moves in frames
            if ((this.ignoreSynthetic && e.synthetic) || e.target.ownerDocument !== this.window.document) return;

            this.saveMouseMoveData(e)

            this.processBodyMouseOut(e)
        },


        processBodyMouseOut : function (e) {
        },


        targetToPathPoint : function (target) {
            var pathPoint   = [ target.getTargetAsQueryString() ]

            if (target.getTarget().offset) pathPoint.push.apply(pathPoint, target.getTarget().offset)

            pathPoint.xy    = target.getTargetByType('xy').target

            return pathPoint
        },


        addPathPoint : function (pathPoints, newPathPoint) {
            // early exit, in case new path point is query-based
            if (newPathPoint.length === 3) {
                pathPoints.push(newPathPoint)
                return
            }

            var lastPathPoint       = pathPoints[ pathPoints.length - 1 ]

            // if new path point is a absolute point (which will be the case for the `useXY` arg
            // of `addMoveCursorAction` enabled then covert it to relative
            if (newPathPoint.length === 1 && this.typeOf(newPathPoint[ 0 ]) == 'Array') {
                var relativePoint   = [
                    newPathPoint[ 0 ][ 0 ] - lastPathPoint.xy[ 0 ],
                    newPathPoint[ 0 ][ 1 ] - lastPathPoint.xy[ 1 ]
                ]

                // do nothing
                if (relativePoint[ 0 ] === 0 && relativePoint[ 1 ] === 0) return

                relativePoint.xy    = newPathPoint[ 0 ]

                pathPoints.push(relativePoint)
            } else
                pathPoints.push(newPathPoint)
        },


        // this method merges several consequent calls to it to `moveCursorAlongPath` action
        addMoveCursorAction : function (event, targetOverride, useXY) {
            if (!(event instanceof Siesta.Recorder.Event)) event = Siesta.Recorder.Event.fromDomEvent(event)

            var lastAction      = this.getLastAction()

            var isMouseMove     = lastAction && (lastAction.action == 'moveCursorTo' || lastAction.action == 'moveCursorAlongPath')

            if (!lastAction || !isMouseMove || !this.recordMouseMovePath) {

                // defensive coding because of unknown: `lastAction.target` is undefined exception
                if (lastAction && lastAction.hasTarget() && lastAction.target) {
                    var xy      = lastAction.target.getTargetByType('xy')

                    if (xy) {
                        xy      = xy.target

                        // do nothing if we already have an action with the same target point
                        // this might happen during our own tests, where we use `drag` command, which
                        // issues "mouseover" for initial and last points
                        if (event.x == xy[ 0 ] && event.y == xy[ 1 ]) return
                    }
                }

                var targetOptions   = this.getPossibleTargets(event, true, targetOverride, useXY)

                this.addAction({
                    action          : 'moveCursorTo',

                    target          : targetOptions,

                    sourceEvent     : event,
                    options         : event.options
                })
            } else {
                var target      = new Siesta.Recorder.Target({
                    targets         : this.getPossibleTargets(event, true, targetOverride, useXY)
                })

                if (lastAction.action == 'moveCursorTo') {
                    lastAction.action   = 'moveCursorAlongPath'

                    lastAction.value    = [ this.targetToPathPoint(lastAction.target) ]

                    lastAction.target   = null
                }

                this.addPathPoint(lastAction.value, this.targetToPathPoint(target))

                this.fireEvent('actionupdate', lastAction)
            }
        }
    }
    // eof `override`
});