import "pkg:/source/rooibos/TestResult.bs"
import "pkg:/source/rooibos/TestGroup.bs"

namespace rooibos
    ' @ignore
    class Test

        public name
        public isSolo
        public noCatch = false
        public funcName
        public isIgnored
        public lineNumber
        public paramLineNumber
        public testSuite = invalid
        public testGroup = invalid
        public deferred = invalid

        public rawParams
        public paramTestIndex
        public isParamTest = false
        public isParamsValid = false
        public expectedNumberOfParams = 0

        public result = invalid

        function new(testGroup as rooibos.TestGroup, data as roAssociativeArray, testSuite = invalid as rooibos.BaseTestSuite)
            m.testGroup = testGroup
            if testSuite <> invalid then
                m.testSuite = testSuite
            else
                m.testSuite = testGroup.testSuite
            end if
            m.isSolo = data.isSolo
            m.noCatch = data.noCatch
            m.funcName = data.funcName
            m.isIgnored = data.isIgnored
            m.isAsync = data.isAsync
            m.asyncTimeout = data.asyncTimeout
            m.slow = data.slow
            m.name = data.name
            m.lineNumber = data.lineNumber
            m.paramLineNumber = data.paramLineNumber
            m.rawParams = data.rawParams
            m.paramTestIndex = data.paramTestIndex
            m.isParamTest = data.isParamTest
            m.expectedNumberOfParams = data.expectedNumberOfParams

            if m.testSuite.isNodeTest then
                m.deferred = rooibos.promises.create()
            end if

            if m.isParamTest then
                m.name = m.name + strI(m.paramTestIndex)
            end if

            m.result = new rooibos.TestResult(m)
        end function

        function run() as dynamic
            m.rooibosTimer = createObject("roTimespan")

            if m.isParamTest then
                m.rooibosTimer.mark()
                promise = m.runParamsTest()
            else
                promise = m.testSuite[m.funcName]()
            end if

            if rooibos.promises.isPromise(promise) then
                if m.testSuite.isNodeTest then
                    m.markDoneWhenTestCompletes(promise)
                else
                    throw "Can not return a promise from a non-node test"
                end if
            else if m.testSuite.isNodeTest and not m.isAsync then
                ' The test is a node test and not async so we need to resolve the deferred
                ' immediately
                m.recordExecutionTime()
                rooibos.promises.resolve(invalid, m.deferred)
            end if

            if m.deferred <> invalid then
                m.recordExecutionTimeWhenDone()
                m.registerTimeout()
            else
                m.recordExecutionTime()
            end if

            return m.deferred
        end function

        ' Sets up a promise chain to link the deferred to the test promise results
        ' and resolves or rejects the deferred based on the result of the test promise.
        ' Also records the execution time of the test if not already recorded.
        function markDoneWhenTestCompletes(testPromise as roSGNoderooibos_promises_Promise)
            rooibos.promises.chain(testPromise, m).then(function(result as dynamic, m as roAssociativeArray)
                m.recordExecutionTime()
                rooibos.common.logDebug("Test promise resolved")
                rooibos.promises.resolve(result, m.deferred)
            end function).catch(function(error as dynamic, m as roAssociativeArray)
                m.recordExecutionTime()
                rooibos.common.logDebug("Test promise rejected")
                rooibos.promises.reject(error, m.deferred)
            end function)
        end function

        function registerTimeout()
            rooibos.promises.internal.delay(function(m as roAssociativeArray)
                if not rooibos.promises.isComplete(m.deferred) then
                    m.recordExecutionTime(m.asyncTimeout)
                    m.testSuite.fail(`Test execution exceeded ${m.asyncTimeout}ms`)
                    rooibos.promises.resolve(invalid, m.deferred)
                end if
            end function, m, m.asyncTimeout / 1000)
        end function

        function recordExecutionTimeWhenDone()
            rooibos.promises.chain(m.deferred, m).then(function(result as dynamic, m as roAssociativeArray)
                m.recordExecutionTime()
                rooibos.promises.resolve(result, m.deferred)
            end function).catch(function(error as dynamic, m as roAssociativeArray)
                m.recordExecutionTime()
                rooibos.promises.reject(error, m.deferred)
            end function)
        end function

        function recordExecutionTime(time = m.rooibosTimer.totalMilliseconds() as integer)
            if m.result.time = -1 then
                m.result.time = time
            end if
        end function

        function runParamsTest() as dynamic
            testParams = m.getTestParams()

            if m.expectedNumberOfParams = 1 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0])
            else if m.expectedNumberOfParams = 2 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1])
            else if m.expectedNumberOfParams = 3 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1], testParams[2])
            else if m.expectedNumberOfParams = 4 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1], testParams[2], testParams[3])
            else if m.expectedNumberOfParams = 5 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1], testParams[2], testParams[3], testParams[4])
            else if m.expectedNumberOfParams = 6 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1], testParams[2], testParams[3], testParams[4], testParams[5])
            else if m.expectedNumberOfParams = 7 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1], testParams[2], testParams[3], testParams[4], testParams[5], testParams[6])
            else if m.expectedNumberOfParams = 8 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1], testParams[2], testParams[3], testParams[4], testParams[5], testParams[6], testParams[7])
            else if m.expectedNumberOfParams = 9 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1], testParams[2], testParams[3], testParams[4], testParams[5], testParams[6], testParams[7], testParams[8])
            else if m.expectedNumberOfParams = 10 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1], testParams[2], testParams[3], testParams[4], testParams[5], testParams[6], testParams[7], testParams[8], testParams[9])
            else if m.expectedNumberOfParams = 11 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1], testParams[2], testParams[3], testParams[4], testParams[5], testParams[6], testParams[7], testParams[8], testParams[9], testParams[10])
            else if m.expectedNumberOfParams = 12 then
                m.rooibosTimer.mark()
                return m.testSuite[m.funcName](testParams[0], testParams[1], testParams[2], testParams[3], testParams[4], testParams[5], testParams[6], testParams[7], testParams[8], testParams[9], testParams[10], testParams[11])
            else if m.expectedNumberOfParams > 12 then
                m.rooibosTimer.mark()
                m.testSuite.fail("Test case had more than 12 params. Max of 12 params is supported")
            end if

            return invalid
        end function

        function getTestParams() as dynamic
            params = []
            for paramIndex = 0 to m.rawParams.count()
                paramValue = m.rawParams[paramIndex]
                if type(paramValue) = "roString" and len(paramValue) >= 8 and left(paramValue, 8) = "#RBSNode" then
                    nodeType = "ContentNode"
                    paramDirectiveArgs = paramValue.split("|")
                    if paramDirectiveArgs.count() > 1 then
                        nodeType = paramDirectiveArgs[1]
                    end if
                    paramValue = createObject("roSGNode", nodeType)
                end if
                params.push(paramValue)
            end for
            return params
        end function

    end class
end namespace
