/*

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

*/
Role('Siesta.Project.Browser.Automation', {

    does        : [
        Siesta.Util.Role.CanStyleOutput,
        Siesta.Util.Role.CanFormatStrings
    ],

    has : {
        outputLog                       : Joose.I.Array,

        currentTestTimeout              : null,

        lastActivity                    : null,
        exitStatus                      : null,

        activeTestAutomationId          : null,

        testResults                     : Joose.I.Array,

        streamAssertions                : false,

        eventLog                        : Joose.I.Array,

        enableCodeCoverage              : false,

        manuallyProcessCoverageResults  : true,

        codeCoverageResults             : Joose.I.Array
    },


    override : {

        setup : function () {
            if (this.isAutomated) {
                this.autoLaunchTests    = false
                this.forceDOMVisible    = true

                this.speedRun           = true
                this.forcedRunCore      = 'sequential'
                this.transparentEx      = false
                this.keepNLastResults   = 0

                this.waitForTimeout     = this.waitForTimeout * 3
                this.defaultTimeout     = this.defaultTimeout * 3
                this.isReadyTimeout     = this.isReadyTimeout * 3

                this.lastActivity       = new Date()

                var me                  = this

                if (typeof window != 'undefined')
                    window.onerror          = function (message, url, lineNumber, col, error) {
                        me.warn("[ERROR] message : " + message)
                        me.warn("[ERROR] url     : " + url)
                        me.warn("[ERROR] line    : " + lineNumber)

                        if (col) me.warn("[ERROR] col     : " + col)
                        if (error && error.stack) me.warn("[ERROR] stack   : " + error.stack)
                    }

                this.on('maxtimeoutchanged', function (event, timeout) {
                    me.onTestMaxTimeoutChanged(event.source, timeout)
                })

                this.on('focuslost', function (event) {
                    me.onTestFocusLost(event.source)
                })
            }

            this.SUPERARG(arguments)
        }
    },


    after : {

        markMissingFile : function (desc) {
            this.warn(Siesta.Resource('Siesta.Role.ConsoleReporter', 'missingFileText').replace("{URL}", desc.url))

            if (this.isAutomated) {
                this.lastActivity           = new Date()

                var result                  = {
                    automationElementId : desc.automationElementId,
                    url                 : desc.url,
                    ERROR               : "Can't open test file: " + desc.url
                }

                if (this.streamAssertions)
                    this.eventLog.push({
                        isResult        : true,
                        data            : result
                    })
                else
                    this.testResults.push(result)
            }
        },


        onTestSuiteStart : function (descriptors, contenManager, launchState) {
            // if (this.isAutomated && this.initialContentManagerState) contenManager.setState(this.initialContentManagerState)
        },


        onTestSuiteEnd : function (descriptors, contenManager, launchState) {
            if (this.isAutomated && !launchState.needToStop) this.exit()
        },


        onTestStart : function (test) {
            if (this.isAutomated && this.isTestActionActual(test)) {
                this.currentTestTimeout     = test.getMaximalTimeout() * 2

                this.activeTestAutomationId = test.automationElementId

                this.lastActivity           = new Date()

                if (this.streamAssertions)
                    if (!test.parent)
                        this.eventLog.push({
                            isUpdate        : true,
                            data            : test.getResults().toJSON()
                        })
            }
        },


        onTestEnd : function (test) {
            if (this.isAutomated && this.isTestActionActual(test)) {
                this.lastActivity           = new Date()

                this.activeTestAutomationId = null

                if (this.streamAssertions)
                    this.eventLog.push({
                        isResult        : true,
                        data            : test.getResults().toJSON()
                    })
                else
                    this.testResults.push(test.getResults().toJSON())

                if (this.enableCodeCoverage && this.manuallyProcessCoverageResults) {
                    var result      = test.global.__coverage__

                    result && this.codeCoverageResults.push(result)
                }
            }
        },


        onTestUpdate : function (test, result, parentResult) {
            if (this.isAutomated && this.isTestActionActual(test)) {
                this.lastActivity           = new Date()

                if (this.streamAssertions)
                    if (!(result.isWaitFor && !result.completed) && !(result instanceof Siesta.Result.Summary)) {
                        this.eventLog.push({
                            isUpdate        : true,
                            data            : result.toJSON()
                        })

                        // in "breakOnFail" case the failed assertion will switch "launchState" to "needToStop"
                        // and "isTestActionActual" will return false for any following updates/results,
                        // so, adding total test result manually
                        if (this.breakOnFail && ((result instanceof Siesta.Result.Assertion) && !result.isPassed()))
                            this.eventLog.push({
                                isResult        : true,
                                data            : test.getResults().toJSON()
                            })
                    }
            }
        }
    },


    methods : {

        isTestActionActual : function (test) {
            if (test.launchId != this.currentLaunchId) return false

            var launchState   = this.launches[ this.currentLaunchId ]

            return launchState ? !launchState.needToStop : false
        },


        onTestFocusLost : function (test) {
            if (this.isAutomated) {
                test.warn(Siesta.Resource('Siesta.Test.Browser').get('focusLostWarningLauncher', { url : test.url }))

                this.stopCurrentLaunch()

                this.exit('focus_lost')
            }
        },


        onTestMaxTimeoutChanged : function (test, timeout) {
            if (this.isAutomated) this.currentTestTimeout = timeout * 2
        },


        filterDescriptors : function (includeTests, excludeTests, descriptors) {
            includeTests        = includeTests ? new RegExp(includeTests) : null
            excludeTests        = excludeTests ? new RegExp(excludeTests) : null

            var filtered        = []

            if (includeTests || excludeTests) {
                Joose.A.each(this.flattenDescriptors(descriptors || this.descriptors), function (desc) {
                    if (includeTests && !includeTests.test(desc.url)) return
                    if (excludeTests && excludeTests.test(desc.url)) return

                    filtered.push(desc)
                })
            } else
                filtered        = this.flattenDescriptors(descriptors || this.descriptors)

            return filtered
        },


        getLastActivity : function () {
            return this.lastActivity - 0
        },


        log : function (text) {
            if (this.isAutomated) {
                if (this.streamAssertions)
                    this.eventLog.push({
                        isLog       : true,
                        data        : text
                    })
                else
                    this.outputLog.push(text)
            }
        },


        exit : function (status) {
            this.exitStatus = status || 'all_processed'
        },


        getAutomationState : function () {
            var launchState   = this.launches[ this.currentLaunchId ]

            var state   = {
                activeTestAutomationId  : this.activeTestAutomationId,
                activityTimeout         : this.currentTestTimeout,
                lastActivity            : this.lastActivity - 0,
                testResults             : this.flushTestResults(),
                log                     : this.flushLog(),
                exitStatus              : this.exitStatus,
                commands                : this.flushAutomationCommands && this.flushAutomationCommands(),
                // launchState is deleted upon launch completion
                notLaunched             : launchState ? launchState.notLaunchedByAutomationId : {}
            }

            if (this.streamAssertions) {
                state.eventLog          = this.flushEventLog()
            }

            if (this.enableCodeCoverage) {
                state.coverageResult    = this.flushCoverageResults()
            }

            return state
        },


        flushLog : function () {
            var outputLog       = this.outputLog

            if (outputLog.length) {
                this.outputLog  = []

                return outputLog
            }
        },


        flushCoverageResults : function () {
            var results         = this.codeCoverageResults

            if (results.length) {
                this.codeCoverageResults    = []

                return results
            }
        },


        flushTestResults : function () {
            var testResults         = this.testResults

            if (testResults.length) {
                this.testResults    = []

                return testResults
            }
        },


        flushEventLog : function () {
            var eventLog        = this.eventLog

            if (eventLog.length) {
                this.eventLog   = []

                return eventLog
            }
        },


        cascadeStructureLeafOnly : function (func, list) {
            var me      = this

            Joose.A.each(list, function (desc) {
                if (desc.group) {
                    me.cascadeStructureLeafOnly(func, desc.items)
                } else
                    func(desc)
            })
        },


        getDescriptorStructure : function (desc, visibleById) {
            var me      = this

            if (visibleById && desc.id != '__ROOT__' && !visibleById[ desc.id ]) return null

            if (desc.group) {
                var items       = []

                Joose.A.each(desc.items, function (desc) {
                    var res     = me.getDescriptorStructure(desc, visibleById)

                    if (res) items.push(res)
                })

                return {
                    id      : desc.id,
                    group   : desc.group || desc.title || desc.name,
                    items   : items
                }
            } else {
                return desc.id
            }
        },


        getConfigInfo : function (includeTest, excludeTests, filterValue, projectConfig) {
            var me                  = this
            var visibleById

            // need to configure project first, because some command line options (like `simulation`) overrides
            // default project config and affect the result of this method
            if (projectConfig) this.configure(projectConfig)

            if (filterValue) {
                var filterer        = new Siesta.Util.TreeStoreFilterer({
                    idProp          : 'id',
                    childNodesProp  : 'items',
                    parentNodeProp  : 'parent',
                    isLeaf          : function (node) { return !node.group }
                })

                var res             = filterer.parseFilterValue(filterValue)

                var testFilterRegexps   = res.testFilterRegexps
                var groupFilterRegexps  = res.groupFilterRegexps

                var getTitle        = function (node) {
                    // do not consider a test suite title as a "root group", otherwise, for the suite title "Test suite"
                    // the --filter="ui>" will match all top-level tests
                    if (node == me)
                        return ''
                    else
                        return node.group || node.title || node.name
                }

                visibleById         = filterer.collectNodes({
                    id              : '__ROOT__',
                    group           : '__ROOT__',
                    items           : this.descriptors
                }, {
                    filter          : function (node) {
                        return filterer.checkCommonFilter(node, getTitle, testFilterRegexps, groupFilterRegexps)
                    }
                })
            }

            var structure           = this.getDescriptorStructure(
                { id : '__ROOT__', group : '__ROOT__', items : this.descriptors },
                visibleById
            )

            var list

            if (filterValue) {
                list                = []

                this.cascadeStructureLeafOnly(function (id) { list.push(me.getDescById(id)) }, structure.items)
            } else
                list                = this.descriptors

            var filteredFlattenList = this.filterDescriptors(includeTest, excludeTests, list)

            return {
                // system info
                VERSION         : Siesta.meta.VERSION,
                title           : this.title,
                structure       : structure,
                descriptors     : this.sortDescriptors(filteredFlattenList, 'sequential', true),

                // environment
                hostName        : typeof location != 'undefined' ? location.host : require('os').hostname(),
                userAgent       : typeof navigator != 'undefined' ? navigator.userAgent : 'NodeJS/' + process.version,
                platform        : typeof navigator != 'undefined' ? navigator.platform : process.platform,

                hasNativeSimulation         : this.hasDescriptorWithNativeEventsSimulation(filteredFlattenList),

                // options
                breakOnFail                 : this.breakOnFail,
                screenshotCompareConfig     : this.screenshotCompareConfig
            }
        },


        // chunk task - [ { descId : descId, automationElementId : elId }, ... ]
        launchAutomatedTests : function (task, options) {
            if (!this.setupDone) {
                this.on('setupdone', function () { this.launchAutomatedTests(task, options) })

                return
            }

            if (options.projectConfig) this.configure(options.projectConfig)

            this.enableCodeCoverage             = Boolean(options.enableCodeCoverage)
            this.streamAssertions               = Boolean(options.streamAssertions)
            this.showCursor                     = Boolean(options.showCursor)

            if (options.pause != null) this.pauseBetweenTests   = options.pause
            if (options.restartOnBlur != null) this.restartOnBlur   = options.restartOnBlur

            var me              = this

            var descriptors     = Joose.A.map(task, function (el) {
                var desc                    = me.getScriptDescriptor(el.descId)

                desc.automationElementId    = el.automationElementId

                return desc
            })

            this.launch(descriptors)
        },


        warn : function (text) {
            this.log({
                text        : text,
                isWarning   : true
            })

            if (typeof window != 'undefined')
                window.console && console.warn(text)

            if (this.viewport) {
                Ext.toast({
                    cls     : 'tr-warn-toast',
                    title   : 'Warning',
                    html    : text.replace(/\n/g, '<br>'),
                    align   : 't',
                    autoCloseDelay : 3000
                })
            }
        },


        getExitCode : function () {
            return this.allPassed() ? 0 : 1
        }

    }
})
//eof Siesta.Project.Browser.Automation

if (!Siesta.Project.NodeJS)
    Siesta.Project.Browser.meta.extend({
        does : [
            Siesta.Project.Browser.Automation,
            Siesta.Project.Browser.Automation.Selenium,
            Siesta.Project.Browser.Automation.Puppeteer
        ]
    })
else
    if (Siesta.Project.NodeJS)
        Siesta.Project.NodeJS.meta.extend({
            does : [
                Siesta.Project.Browser.Automation,
                Siesta.Project.Browser.Automation.NodeJS
            ]
        })