import Cucumber from 'cucumber'
import { wrapCommands, executeHooksWithArgs } from 'wdio-sync'

import CucumberReporter from './reporter'

/**
 * Cucumber runner
 */
class CucumberAdapter {
    constructor (cid, config, specs, capabilities) {
        this.cid = cid
        this.config = config

        this.cucumberOpts = Object.assign({
            backtrace: false, // <boolean> show full backtrace for errors
            compiler: [], // <string[]> ("extension:module") require files with the given EXTENSION after requiring MODULE (repeatable)
            dryRun: false, // <boolean> invoke formatters without executing steps
            failFast: false, // <boolean> abort the run on first failure
            format: [ 'pretty' ], // <string[]> (type[:path]) specify the output format, optionally supply PATH to redirect formatter output (repeatable)
            colors: true, // <boolean> disable colors in formatter output
            snippets: true, // <boolean> hide step definition snippets for pending steps
            source: true, // <boolean> hide source uris
            profile: [], // <string> (name) specify the profile to use
            require: [], // <string> (file/dir) require files before executing features
            snippetSyntax: undefined, // <string> specify a custom snippet syntax
            strict: false, // fail if there are any undefined or pending steps
            tags: [] // <string[]> (expression) only execute the features or scenarios with tags matching the expression
        }, config.cucumberOpts)

        this.specs = specs
        this.capabilities = capabilities
        this.origStepDefinition = Cucumber.SupportCode.StepDefinition
    }

    async run () {
        let reporterOptions = {
            capabilities: this.capabilities,
            ignoreUndefinedDefinitions: !!this.cucumberOpts.ignoreUndefinedDefinitions
        }

        wrapCommands(global.browser, this.config.beforeCommand, this.config.afterCommand)

        let cucumberConf = Cucumber.Cli.Configuration(this.cucumberOpts, this.specs)
        let runtime = Cucumber.Runtime(cucumberConf)

        Cucumber.SupportCode.StepDefinition = this.getStepDefinition()

        let reporter = new CucumberReporter(Cucumber.Listener(), reporterOptions, this.cid)
        runtime.attachListener(reporter.getListener())

        await executeHooksWithArgs(this.config.before, [this.capabilities, this.specs])
        let result = await new Promise((resolve) => {
            runtime.start(() => {
                resolve(reporter.failedCount)
            })
        })
        await executeHooksWithArgs(this.config.after, [result, this.capabilities, this.specs])

        return result
    }

    getStepDefinition () {
        let { origStepDefinition } = this
        return (pattern, options, code, uri, line) => {
            const stepOpts = { pattern, options, uri, line }
            const stepDefinition = origStepDefinition(pattern, options, this.wrapStep(code, stepOpts), uri, line)
            stepDefinition.validCodeLengths = () => [0]
            return stepDefinition
        }
    }

    wrapStep (code, stepOpts) {
        const self = this

        return function (...args) {
            return executeHooksWithArgs(self.beforeTest, stepOpts).then(
                () => new Promise((r) => global.wdioSync(code, r).apply(this, args)),
                (e) => console.log(`Error in beforeTest hook: [${e}]`)
            ).then(
                () => executeHooksWithArgs(self.afterTest, stepOpts)
                    .catch((e) => console.log(`Error in afterTest hook: [${e}]`))
            )
        }
    }
}

const _CucumberAdapter = CucumberAdapter
const adapterFactory = {}

adapterFactory.run = async function (cid, config, specs, capabilities) {
    const adapter = new _CucumberAdapter(cid, config, specs, capabilities)
    return await adapter.run()
}

export default adapterFactory
export { CucumberAdapter, adapterFactory }

@@ -132,16 +132,79 @@
 orter'%0A%0A
+const DEFAULT_TIMEOUT = 10000%0Aconst DEFAULT_FORMAT = 'pretty'%0A%0A
 /**%0A * C
@@ -786,18 +786,22 @@
 t: %5B
- 'pretty' 
+DEFAULT_FORMAT
 %5D, /
@@ -1478,16 +1478,17 @@
 tags: %5B%5D
+,
  // %3Cstr
@@ -1576,24 +1576,61 @@
  expression%0A
+            timeout: DEFAULT_TIMEOUT%0A
         %7D, c
@@ -1784,24 +1784,88 @@
 pDefinition%0A
+        this.origAstTreeWalker = Cucumber.Runtime.AstTreeWalker%0A
     %7D%0A%0A    a
@@ -2353,16 +2353,81 @@
 nition()
+%0A        Cucumber.Runtime.AstTreeWalker = this.getAstTreeWalker()
 %0A%0A      
@@ -3297,16 +3297,16 @@
  =%3E %5B0%5D%0A
-
         
@@ -3344,24 +3344,334 @@
    %7D%0A    %7D%0A%0A
+    getAstTreeWalker () %7B%0A        const %7B origAstTreeWalker %7D = this%0A        return (features, supportCodeLibrary, listeners, options) =%3E %7B%0A            supportCodeLibrary.setDefaultTimeout(DEFAULT_TIMEOUT)%0A            return origAstTreeWalker(features, supportCodeLibrary, listeners, options)%0A        %7D%0A    %7D%0A%0A
     wrapStep

make timeout configurable