/*

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

*/
// workaround for: http://www.sencha.com/forum/showthread.php?299660-5.1.0.107-Exception-thrown-when-opening-any-example-in-Chrome-touch-simulation-mode&p=1094459#post1094459

// this condition seems to point that Chrome is opened in device simulation mode, w/o touch events enabled from command line
// seems "TouchEvents" + "!Touch" confuses Ext and exception is thrown
if (Ext.supports && Ext.supports.TouchEvents && !Ext.supports.Touch) {
    // w/o these, UI throws exceptions
    Ext.supports.Touch          = false
    Ext.supports.touchScroll    = false
}
// eof workaround


Ext.define('Siesta.Project.Browser.UI.Viewport', {

    extend : 'Ext.container.Viewport',

    mixins     : [
        'Siesta.Project.Browser.UI.CanFillAssertionsStore'
    ],
    controller : 'viewport',

    requires        : [
        'Siesta.Project.Browser.UI.ViewportController',
        'Ext.state.LocalStorageProvider',
        'Ext.state.CookieProvider',

        'ExtX.Reference.Slot',

        'Siesta.Project.Browser.UI.TestGrid',
        'Siesta.Project.Browser.UI.ResultPanel',

        'Siesta.Project.Browser.Model.AssertionTreeStore',
        'Siesta.Project.Browser.Model.FilterableTreeStore',
        'Siesta.Project.Browser.Model.TestFile',
        'Siesta.Project.Browser.Model.Assertion',

        'Siesta.Project.Browser.UI.VersionUpdateButton',

        'Siesta.Project.Browser.UI.Override',
        // requiring "Ext.Img" for it to be in the "siesta-all.js" bundle
        // otherwise it is included into "siesta-coverage-all.js" bundle and when using "siesta-no-ext-all.js" setup
        // Ext.Img is double included
        'Ext.Img'
    ],

    title               : null,

    project             : null,

    // need to set stateful properties before `initComponent`
    stateful            : false,
    layout              : 'border',

    // stateful
    selection           : null,
    filter              : null,
    filterGroups        : false,
    // eof stateful

    testsStore          : null,

    contextMenu         : null,
    enableVersionCheck  : true,

    collapsedNodes      : null,

    showSizeControls    : false,

    expectedExtJSVersion : '6.0.1.250',

    viewportSizes : [
        [640, 480],
        [800, 600],
        [1024, 768],
        [1920, 1080],
        [2048, 1536]
    ],

    idGenSequence       : 1,

    isReadOnlyReport    : false,
    dataUrl             : 'report-data.json',

    autoLaunch          : false,


    initComponent : function () {
        var project

        if (this.isReadOnlyReport) {
            project         = new Siesta.Project.Browser()
            project.configure({
                stateful        : false
            })
            this.project    = project
        } else
            project         = this.project;

        Ext.getBody().addCls('siesta')

        Ext.getBody().on('keydown', this.onBodyKeyDown, this)
        Ext.setGlyphFontFamily('FontAwesome');

        Ext.state.Manager.setProvider(Ext.supports.LocalStorage ? new Ext.state.LocalStorageProvider() : new Ext.state.CookieProvider())

        this.selection      = {}

        // config given during viewport instantiation
        var filter          = this.filter

        if (project.stateful) this.applyState(this.loadState())

        // config given during viewport instantiation - comes from query string ?filter= overrides everything
        if (filter) {
            this.filter         = filter
            this.filterGroups   = false
        }

        var data = this.buildTreeData({
            id    : 'root',
            group : 'test suite' + this.title,
            items : project.descriptors
        }).children;

        var testsStore = this.testsStore = new Siesta.Project.Browser.Model.FilterableTreeStore({
            model : 'Siesta.Project.Browser.Model.TestFile',

            sortOnLoad : false,

            root : {
                expanded : true,

                children : data
            },

            proxy : 'memory',

            listeners : {
                nodecollapse : this.saveState,
                nodeexpand   : this.saveState,

                scope : this
            }
        })

        Ext.apply(this, {
            slots           : true,

            items : [
                {
                    region              : 'west',
                    xtype               : 'testgrid',
                    store               : testsStore,

                    stateful            : project.stateful,

                    slot                : 'filesTree',
                    id                  : project.id.replace(/\W/g, '_') + '-testTree',

                    showSizeControls    : this.showSizeControls,
                    viewportSizes       : this.viewportSizes,
                    stateConfig         : this.getState(),
                    project             : this.project,
                    isReadOnlyReport    : this.isReadOnlyReport,

                    animate             : !Ext.isIE,
                    split               : {
                        size    : 7
                    },

                    filter              : this.filter,
                    filterGroups        : this.filterGroups,

                    listeners           : {
                        viewready   : this.onFilterApplied,
                        scope       : this
                    }
                },
                {
                    xtype          : 'resultpanel',
                    region         : 'center',
                    slot           : 'resultPanel',
                    cls            : 'resultPanel-panel',

                    viewDOM                 : this.getOption('viewDOM'),
                    scaleToFit              : this.getOption('scaleToFit'),
                    scaleToFitMode          : this.getOption('scaleToFitMode'),

                    id                      : project.id.replace(/\W/g, '_') + '-resultpanel',
                    project                 : project,
                    recorderConfig          : project.recorderConfig,

                    isReadOnlyReport        : this.isReadOnlyReport,

                    maintainViewportSize    : project.maintainViewportSize,
                    domContainerRegion      : project.domContainerRegion,

                    listeners       : {
                        newjsonreport   : this.onNewJsonReport,
                        scope           : this
                    }
                }
            ]
            // eof main content area
        })

        this.callParent()

        this.slots.filesTree.store.on({
            'filter-set'   : this.saveState,
            'filter-clear' : this.saveState,

            scope : this
        })

        this.on('statechange', this.saveState, this)

        project.on('testendbubbling', this.onEveryTestEnd, this)
        project.on('testsuitelaunch', this.onTestSuiteLaunch, this)
        project.on('assertiondiscard', this.onAssertionDiscarded, this)

        if (window.location.href.match('^file:///') && !(this.isReadOnlyReport && Ext.browser.is('Firefox'))) {
            var R = Siesta.Resource('Siesta.Project.Browser.UI.Viewport');

            Ext.Msg.alert(R.get('httpWarningTitle'), R.get('httpWarningDesc'))
        } else
            if (this.isReadOnlyReport) this.fetchJsonReport()
    },


    onNewJsonReport : function (field, report) {
        this.loadJsonReport(report)
    },


    buildTreeData : function (descriptor) {
        var data            = {
            id         : descriptor.id,

            title      : descriptor.group || descriptor.title || descriptor.name,
            descriptor : descriptor,

            // HACK, bypass Ext JS cloning
            nodeType   : 1,
            cloneNode  : function () { return this; }
            // EOF HACK
        }

        data.tooltip        = descriptor.desc || data.title

        var me              = this
        var prevId          = data.id
        var collapsedNodes  = this.collapsedNodes || {}

        if (descriptor.group) {

            var children = []

            Ext.each(descriptor.items, function (desc) {
                children.push(me.buildTreeData(desc))
            })

            Ext.apply(data, {
                expanded : (collapsedNodes[prevId] != null || descriptor.expanded === false) ? false : true,
                // || false is required for TreeView - it checks that "checked" field contains Boolean
                checked  : me.selection.hasOwnProperty(prevId) || false,

                folderStatus : 'yellow',

                children : children,
                leaf     : false
            })

        } else {
            Ext.apply(data, {
                url         : descriptor.url,

                leaf        : true,
                // || false is required for TreeView - it checks that "checked" field contains Boolean
                checked     : me.selection.hasOwnProperty(prevId) || false,

                passCount   : 0,
                failCount   : 0,

                time        : 0,

                assertionsStore : new Siesta.Project.Browser.Model.AssertionTreeStore({
                    //autoDestroy : true,
                    model : 'Siesta.Project.Browser.Model.Assertion',

                    proxy : 'memory',

                    root : {
                        id       : '__ROOT__',
                        expanded : true
                    }
                })
            })
        }

        return data
    },


    onBodyKeyDown : function (e) {
        if (e.ctrlKey) {
            var project     = this.project

            if (e.getKey() == Ext.event.Event[ project.rerunHotKey ]) {
                this.runTest();
                e.preventDefault();
            }

            if (e.getKey() == Ext.event.Event[ project.observerModeHotKey ]) {
                var mode        = !project.observerMode

                var menuItem    = this.down('#observerModeMenuItem')

                menuItem.setChecked(mode)

                menuItem.fireEvent('click', menuItem)

                e.preventDefault();
            }
        }
    },


    buildTreeDataFromJSONSReportNode : function (reportNode) {
        var data = {
            id          : this.idGenSequence++,
            title       : reportNode.group || reportNode.name || reportNode.url.replace(/(?:.*\/)?([^/]+)$/, '$1'),
            descriptor  : reportNode,

            // HACK, bypass Ext JS cloning
            nodeType    : 1,
            cloneNode   : function () {
                return this;
            }
        }

        var me              = this

        if (reportNode.group) {
            var children = []

            Ext.each(reportNode.items, function (reportNode) {
                children.push(me.buildTreeDataFromJSONSReportNode(reportNode))
            })

            Ext.apply(data, {
                expanded        : true,
                // disable checkbox selection
                checked         : null,
                leaf            : false,

                folderStatus    : 'yellow',

                children        : children
            })

        } else {
            var parentTest      = new Siesta.Test.Browser({
                project     : this.project,

                url         : reportNode.url,
                name        : reportNode.name,

                startDate   : new Date(reportNode.startDate)
            })

            Ext.apply(data, {
                url             : reportNode.url,

                leaf            : true,
                // disable checkbox selection
                checked         : null,

                time            : reportNode.endDate - reportNode.startDate,

                test            : parentTest,

                assertionsStore : new Siesta.Project.Browser.Model.AssertionTreeStore({
                    model : 'Siesta.Project.Browser.Model.Assertion',

                    proxy : 'memory',

                    root : {
                        id          : '__ROOT__',
                        expanded    : true,

                        children    : this.buildAssertionDataFromJSONSReportNode(reportNode.assertions, parentTest)
                    }
                })
            })

            parentTest.endDate  = new Date(reportNode.endDate)

            Ext.apply(data, {
                passCount       : parentTest.getPassCount(),
                failCount       : parentTest.getFailCount(),
                todoPassCount   : parentTest.getTodoPassCount(),
                todoFailCount   : parentTest.getTodoFailCount()
            })
        }

        return data
    },


    buildAssertionDataFromJSONSReportNode : function (assertions, parentTest) {
        var me      = this

        var res     = Joose.A.map(assertions, function (assertionNode) {
            var isSubTest       = assertionNode.type == 'Siesta.Result.SubTest'
            var result

            var data            = {
                id                  : me.idGenSequence++,

                leaf                : !isSubTest,
                expanded            : isSubTest && (assertionNode.bddSpecType != 'it' || !assertionNode.passed)
            }

            if (isSubTest) {
                var subTest     = new Siesta.Test.Browser({
                    trait       : Siesta.Test.Sub,
                    name        : assertionNode.name,

                    isTodo      : assertionNode.isTodo,

                    startDate   : new Date(assertionNode.startDate),

                    url         : parentTest.url,
                    parent      : parentTest,
                    specType    : assertionNode.bddSpecType
                })

                data.children   = me.buildAssertionDataFromJSONSReportNode(assertionNode.assertions, subTest)

                subTest.endDate = new Date(assertionNode.endDate)

                result          = subTest.getResults()
            } else {
                var resultCls   = Joose.S.strToClass(assertionNode.type)

                result          = new resultCls(assertionNode)
            }

            parentTest.addResult(result)

            data.result         = result

            return data
        })

        if (!parentTest.parent) {
            var summary     = new Siesta.Result.Summary({
                isFailed            : parentTest.isFailed(),
                description         : parentTest.getSummaryMessage()
            })

            parentTest.addResult(summary)

            res.push({
                id                  : this.idGenSequence++,

                result              : summary,

                loaded              : true,
                leaf                : true,
                expanded            : false
            })
        }

        return res
    },


    loadJsonReport : function (report) {
        this.title          = report.testSuiteName || ''

        this.testsStore.setRootNode(this.buildTreeDataFromJSONSReportNode({
            group       : 'TOP',
            items       : report.testCases
        }))

        this.testsStore.getRootNode().cascadeBy(function (node) {
            if (node.parentNode && !node.isLeaf()) node.updateFolderStatus()
        })

        this.updateStatusIndicator();
    },


    fetchJsonReport : function () {
        var me      = this

        var loadingMask = new Ext.LoadMask({
            target : this,
            msg    : Siesta.Resource('Siesta.Project.Browser.UI.CoverageReport', 'loadingText')
        });

        loadingMask.show()

//        use this code for testing
//        setTimeout(function () {
//            loadingMask.hide()
//
//            me.loadHtmlReport(me.htmlReport)
//        }, 2000)

        Ext.Ajax.request({
            url         : this.dataUrl,

            success : function (response) {
                loadingMask.hide()
                me.loadJsonReport(Ext.JSON.decode(response.responseText))
            },

            failure : function () {
                loadingMask.hide()
                Ext.Msg.show({
                    title   : Siesta.Resource('Siesta.Project.Browser.UI.CoverageReport', 'loadingErrorText'),
                    msg     : Siesta.Resource('Siesta.Project.Browser.UI.CoverageReport', 'loadingErrorMessageText') + me.dataUrl,
                    buttons : Ext.MessageBox.OK,
                    icon    : Ext.MessageBox.ERROR
                })
            }
        })
    },


    onFilterApplied : function () {
        if (this.autoLaunch) {
            this.runAll()
        } else
            if (this.getOption('autoRun')) {
                var checked = this.getChecked()

                // either launch the suite for checked tests or for all
                this.project.launch(checked.length && checked || this.project.descriptors)
            }
    },


    setNodeChecked : function (testFile, checked, doNotCascade, skipSave) {
        var me = this
        var id = testFile.getId()

        if (checked)
            this.selection[id] = 1
        else
            delete this.selection[id]


        testFile.set('checked', checked)

        // when unchecking the node - uncheck the parent node (folder) as well
        if (!checked && testFile.parentNode) me.setNodeChecked(testFile.parentNode, false, true, true)

        // only cascade for folders and when `doNotCascade` is false
        if (!testFile.isLeaf() && !doNotCascade) Ext.each(testFile.childNodes, function (childNode) {
            me.setNodeChecked(childNode, checked, false, true)
        })

        if (!skipSave) this.saveState()
    },


    forEachTestFile : function (func, scope) {
        scope           = scope || this
        var testsStore  = this.testsStore
        var root        = testsStore.getRootNode()

        root.cascadeBy(function (node) {
            var isFilteredIn        = testsStore.isNodeFilteredIn(node)

            if (node != root && isFilteredIn) func.call(scope, node)

            // do not cascade for child nodes if parent is not filtered in - faster tree traversing
            return isFilteredIn
        })
    },


    getChecked : function () {
        var descriptors = []

        this.forEachTestFile(function (testFileRecord) {
            if (testFileRecord.get('checked') && testFileRecord.isLeaf()) descriptors.push(testFileRecord.get('descriptor'))
        })

        return descriptors
    },


    runChecked : function () {
        var checked = this.getChecked();

        if (checked.length > 0) {
            this.project.launch(this.getChecked())
        }
    },


    runFailed : function () {
        var descriptors = []

        this.forEachTestFile(function (testFileRecord) {
            var test = testFileRecord.get('test')

            if (test && test.isFailed()) descriptors.push(testFileRecord.get('descriptor'))
        })

        if (descriptors.length > 0) {
            this.project.launch(descriptors)
        }
    },


    runAll : function () {
        var allDesc = []

        this.forEachTestFile(function (testFile) {
            if (testFile.isLeaf()) allDesc.push(testFile.get('descriptor'))
        })

        if (allDesc.length > 0) {
            this.project.launch(allDesc)
        }
    },


    // this method is never called currently, because there's no corresponding button in the test grid bottom bar
    stopSuite : function (button) {
        button.disable()

        this.project.stopCurrentLaunch();

        setTimeout(function () {
            button.enable()
        }, 1000)
    },


    onTestSuiteStop : function (sourceTest) {
        this.testsStore.forEach(function (testFileRecord) {
            if (testFileRecord.get('isStarting')) {
                testFileRecord.set('isStarting', false);
            }
        });

        if (sourceTest) {
            var testRecord = this.testsStore.getNodeById(sourceTest.url)

            this.slots.filesTree.getSelectionModel().select(testRecord);
        }
    },


    // looks less nice than setting it only after preload for some reason
    onBeforeScopePreload : function (scopeProvider, url) {
        var testRecord = this.testsStore.getNodeById(url)
    },


    isTestRunningVisible : function (test) {
        // return false for test's running in popups (not iframes), since we can't show any visual accompaniment for them
        if (!(test.scopeProvider instanceof Scope.Provider.IFrame)) return false;

        // if there is a "forced to be on top" test then we only need to compare the tests instances
        if (this.project.testOfForcedIFrame) {
            return this.project.testOfForcedIFrame.isFromTheSameGeneration(test)
        }

        // otherwise the only possibly visible test is the one of the current assertion grid
        var resultPanel = this.slots.resultPanel;

        // if resultPanel has no testRecord it hasn't yet been assigned a test record
        if (!resultPanel.test || !resultPanel.test.isFromTheSameGeneration(test)) {
            return false;
        }

        // now we know that visible assertion grid is from our test and there is no "forced on top" test
        // we only need to check visibility (collapsed / expanded of the right panel
        return resultPanel.isFrameVisible()
    },


    resetDescriptors  : function (descriptors) {
        var testsStore = this.testsStore;

        Joose.A.each(this.project.flattenDescriptors(descriptors), function (descriptor) {
            var testRecord = testsStore.getNodeById(descriptor.id);

            testRecord.get('assertionsStore').removeAll(true)
            testRecord.reject();
            // || false is required for TreeView - it checks that "checked" field contains Boolean
            testRecord.set('checked', this.selection.hasOwnProperty(descriptor.id) || false)
        }, this);
    },


    // method is called when test suite (any several tests) starts - before caching the script contents
    // at this point we don't know yet about missing test files
    onTestSuiteStart  : function (descriptors) {
        Ext.getBody().addCls('testsuite-running');

        var filesTree       = this.slots.filesTree
        var selModel        = filesTree.getSelectionModel()
        var prevSelection   = selModel.getLastSelected()
        var testsStore      = this.testsStore

        this.resetDescriptors(descriptors);

        // restore the selection after data reload
        if (prevSelection) selModel.select(testsStore.getNodeById(prevSelection.getId()))
    },


    // method is called when test suite (any several tests) launches - after the caching the script contents
    // has completed and 1st test is about to start
    // at this point we know about missing files and `desc.isMissing` property is set
    onTestSuiteLaunch : function (event, descriptors) {
        var testsStore = this.testsStore

        var updated = {}

        Joose.A.each(this.project.flattenDescriptors(descriptors), function (descriptor) {
            var testRecord = testsStore.getNodeById(descriptor.id)

            testRecord.set({
                isMissing  : descriptor.isMissing,
                isStarting : true
            })

            var groupNode = testRecord.parentNode

            if (groupNode && !updated[groupNode.getId()]) {
                // trying hard to prevent extra updates
                for (var node = groupNode; node; node = node.parentNode) updated[node.getId()] = true

                groupNode.updateFolderStatus()
            }
        })
    },


    onTestSuiteEnd : function (descriptors) {
        Ext.getBody().removeCls('testsuite-running');

        this.updateStatusIndicator();

        // Without this the keyboard hotkey won't work (since one of the frames will steal focus likely)
        if (Ext.isChrome) {
            window.focus();
        } else {
            document.body.tabIndex = -1;
            document.body.focus();
        }
    },


    onTestStart        : function (test) {
        var testRecord = this.testsStore.getNodeById(test.url)

        if (!this.isTestUpdateActual(test, testRecord)) return

        testRecord.beginEdit()

        // will trigger an update in grid
        testRecord.set({
            test      : test,
            isRunning : true
        })

        testRecord.endEdit()

        var currentSelection = this.slots.filesTree.getSelectionModel().getLastSelected()

        // activate the assertions grid for currently selected row, or, if the main area is empty
        if (currentSelection && currentSelection.getId() == test.url) {
            var resultPanel = this.slots.resultPanel

            resultPanel.showTest(test, testRecord.get('assertionsStore'))
            resultPanel.setInitializing(false);

            T           = test
        }
    },


    // this method checks that test update, coming from given `test` is actual
    // update may be not actual, if user has re-launched the test, so new test already presents
    isTestUpdateActual : function (test, testRecord) {
        return test.launchId == this.project.currentLaunchId
    },


    onTestUpdate   : function (test, result, parentResult) {
        var testRecord = this.testsStore.getNodeById(test.url)

        // need to check that test record contains the same test instance as the test in arguments (or its sub-test)
        // test instance may change if user has restarted a test for example
        if (this.isTestUpdateActual(test, testRecord)) {
            this.processNewResult(testRecord.get('assertionsStore'), test, result, parentResult)
        }
    },

    onAssertionDiscarded: function (event, test, result) {
        var testRecord = this.testsStore.getNodeById(test.url)

        // need to check that test record contains the same test instance as the test in arguments (or its sub-test)
        // test instance may change if user has restarted a test for example
        if (this.isTestUpdateActual(test, testRecord)) {
            var assertionStore = testRecord.get('assertionsStore')

            assertionStore.getNodeById(result.id).remove()
        }
    },


    // only triggered for "root" tests
    onTestEnd      : function (test) {
        var testRecord = this.testsStore.getNodeById(test.url)

        // need to check that test record contains the same test instance as the test in arguments (or its sub-test)
        // test instance may change if user has restarted a test for example
        if (this.isTestUpdateActual(test, testRecord)) {
            testRecord.beginEdit()

            testRecord.set({
                'passCount'     : test.getPassCount(),
                'failCount'     : test.getFailCount(),
                'todoPassCount' : test.getTodoPassCount(),
                'todoFailCount' : test.getTodoFailCount(),
                'time'          : test.getDuration() + 'ms'
            });

            testRecord.endEdit()

            testRecord.parentNode && testRecord.parentNode.updateFolderStatus()
        }

        this.updateStatusIndicator()
    },


    // is bubbling and thus triggered for all tests (including sub-tests)
    onEveryTestEnd : function (event, test) {
        var testRecord = this.testsStore.getNodeById(test.url)

        // need to check that test record contains the same test instance as the test in arguments (or its sub-test)
        // test instance may change if user has restarted a test for example
        if (this.isTestUpdateActual(test, testRecord)) {
            this.processEveryTestEnd(testRecord.get('assertionsStore'), test)
        }
    },


    onTestFail : function (test, exception, stack) {
        var testRecord = this.testsStore.getNodeById(test.url)

        // need to check that test record contains the same test instance as the test in arguments
        // test instance may change if user has restarted a test for example
        if (this.isTestUpdateActual(test, testRecord) && !test.isTodo) {
            testRecord.set('isFailed', true)

            testRecord.parentNode && testRecord.parentNode.updateFolderStatus()
        }
    },


    getOption : function (name, onlyOwn) {
        switch (name) {
            case 'selection'    :
                return this.selection

            default             :
                var project     = this.project

                if (onlyOwn) return project.hasOwnProperty(name) ? project[ name ] : undefined

                return project[ name ]
        }
    },


    setOption : function (name, value) {
        var project     = this.project

        switch (name) {
            case 'selection' :
                return this.selection       = value || {}

            case 'collapsedNodes' :
                return this.collapsedNodes  = value

            case 'filter' :
                return this.filter          = value

            case 'filterGroups' :
                return this.filterGroups    = value

            case 'observerMode' :
                project.setObserverMode(value)
                break

            // UI only option, does not correspond to viewport/project config
            case 'mouseSimSlow' :
                if (value) {
                    project[ 'speedRun' ]       = false
                    project[ 'superSpeedRun' ]  = false
                }
                break

            // UI only option, does not correspond to viewport/project config
            case 'mouseSimFast' :
                if (value) {
                    project[ 'speedRun' ]       = true
                    project[ 'superSpeedRun' ]  = false
                }
                break

            // UI only option, does not correspond to viewport/project config
            case 'mouseSimFastest' :
                if (value) {
                    project[ 'speedRun' ]       = true
                    project[ 'superSpeedRun' ]  = true
                }
                break

            default :
                return project[ name ]      = value
        }
    },


    getState : function (onlyOwnProperties) {
        // getState is also called early, when there's no child components
        // the absense of "this.slots" indicates this
        var domContainer    = this.slots && this.down('domcontainer')

        var state   = {
            // UI configs
            scaleToFit      : domContainer ? domContainer.scaleToFit : this.scaleToFit,
            scaleToFitMode  : domContainer ? domContainer.scaleToFitMode : this.scaleToFitMode,

            selection       : this.getCheckedNodes(),
            collapsedNodes  : this.getCollapsedFolders(),

            filter          : this.slots ? this.slots.filesTree.getFilterValue() : this.filter,
            filterGroups    : this.slots ? this.slots.filesTree.getFilterGroups() : this.filterGroups
        }

        var me      = this

        // project configs
        Joose.A.each([
            'autoRun', 'speedRun', 'superSpeedRun', 'viewDOM', 'transparentEx', 'breakOnFail',
            'debuggerOnFail', 'observerMode'
        ], function (name) {
            var value   = me.getOption(name, onlyOwnProperties)

            if (onlyOwnProperties) {
                if (value !== undefined) state[ name ] = value
            } else
                state[ name ] = value
        })

        return state
    },


    getCheckedNodes : function () {
        var checked = {}

        this.testsStore.forEach(function (treeNode) {
            if (treeNode.get('checked')) checked[treeNode.getId()] = 1
        })

        return checked
    },


    getCollapsedFolders : function () {
        var collapsed = {}

        this.testsStore.forEach(function (treeNode) {
            if (!treeNode.isLeaf() && !treeNode.isExpanded()) collapsed[treeNode.getId()] = 1
        })

        return collapsed
    },


    applyState : function (state) {
        var me = this

        if (state) Joose.O.each(state, function (value, name) {
            me.setOption(name, value)
        })
    },


    getStateId : function () {
        return 'test-run-' + this.title
    },


    loadState : function () {
        var stateId     = this.getStateId()
        var state       = Ext.state.Manager.get(stateId)

        if (!state) return

        if (!state.collapsedNodes)  state.collapsedNodes = Ext.state.Manager.get(stateId + '-collapsed')
        if (!state.selection)       state.selection = Ext.state.Manager.get(stateId + '-selection')

        return state
    },


    saveState : function () {
        var stateId     = this.getStateId()
        var state       = this.getState(true)

        Ext.state.Manager.set(stateId + '-collapsed', state.collapsedNodes)
        Ext.state.Manager.set(stateId + '-selection', state.selection)

        delete state.collapsedNodes
        delete state.selection

        Ext.state.Manager.set(stateId, state)
    },


    uncheckAllExcept : function (testFile) {
        var me = this

        this.testsStore.forEach(function (node) {
            if (node != testFile) me.setNodeChecked(node, false, true)
        })
    },


    launchTest : function (testFile) {
        if (this.isReadOnlyReport) return

        var resultPanel = this.slots.resultPanel

        var isLeaf      = testFile instanceof Ext.data.Model ? testFile.data.leaf : true;
        var descriptor  = testFile instanceof Siesta.Test ? this.project.getScriptDescriptor(testFile) : testFile.get('descriptor');

        // clear the content of the result panel when launching a single test
        if (isLeaf) {
            Ext.History.add(descriptor.url);
            // assertions of the tests being launched will be cleared in the `onTestSuiteStart` method
            resultPanel.setInitializing(true);
        }

        this.project.launch([ descriptor ])
    },


    updateStatusIndicator : function () {
        // can remain neutral if all files are missing for example
//        var isNeutral       = true
//        var allGreen        = true
//        var hasFailures     = false

        var totalPassed = 0
        var totalFailed = 0

        this.testsStore.forEach(function (testFileRecord) {
            var test = testFileRecord.get('test')

            // if there's at least one test - state is not neutral
            if (test && test.isFinished()) {
//                isNeutral       = false

//                allGreen        = allGreen      && test.isPassed()
//                hasFailures     = hasFailures   || test.isFailed()

                totalPassed += test.getPassCount()
                totalFailed += test.getFailCount()
            }
        })

        this.slots.filesTree.updateStatus(totalPassed, totalFailed);
    },

    /**
     * Re-runs the latest executed test
     */
    runTest : function () {
        if (this.isReadOnlyReport) return

        var toRun = this.slots.filesTree.getSelectionModel().getSelection()[0] ||
                    this.slots.resultPanel.test;

        if (toRun) {
            this.launchTest(toRun);
        }
    },


    afterRender : function () {
        this.callParent(arguments);

        if (Ext.versions.extjs.isLessThan(this.expectedExtJSVersion) && window.console) {
            console.log('Wrong Ext JS version detected.', 'Siesta expects that you use Ext JS version >= ' + this.expectedExtJSVersion + '. You may experience errors when using another version');
        }

        if (!this.project.isAutomated) setTimeout(Ext.Function.bind(this.deferredSetup, this), 1000);
    },


    deferredSetup : function() {
        Ext.QuickTips && Ext.QuickTips.init();

        if (this.enableVersionCheck && Siesta.Project.Browser.UI.VersionUpdateButton) {
            new Siesta.Project.Browser.UI.VersionUpdateButton();
        }
    },


    onManualCloseOfForcedIframe : function (test) {
        var domContainer        = this.down('domcontainer')

        if (domContainer && domContainer.test == test) domContainer.alignIFrame(true)
    }
})
//eof Siesta.Project.Browser.UI.Viewport