sh                = require('shelljs')
async             = require('async')
TEST_DB           = 'habitrpg_test'
TEST_DB_URI       = "mongodb://localhost/#{TEST_DB}"
TEST_SERVER_PORT  = 3001
MAX_WAIT          = 60

announce = (msg) ->
  sh.echo '\x1b[36m%s\x1b[0m', "TEST SUITE: #{msg}"

Suite =
  # Primary Task
  run: ->
    announce "Preparing the test environment."
    Suite.prepareEnvironment ->
      announce "Test prep complete. Waiting for server availability."
      Suite.awaitServers ->
        announce "Servers are ready. Beginning tests."
        Suite.summarize
          "API Specs":       Suite.runApiSpecs()
          "Common Specs":    Suite.runCommonSpecs()
          "End-to-End Specs": Suite.runE2ESpecs()
          "Karma Specs":      Suite.runKarmaSpecs()

  # Output summary report when tests are done.
  summarize: (results) ->
    anyFailed = 0
    sh.echo ""
    announce "Tests complete!\n\nSummary\n-------\n"
    for name, result of results
      if result is 0
        sh.echo '\x1b[36m%s\x1b[0m', "#{name}: \x1b[32mpassing"
      else
        anyFailed = 1
        sh.echo '\x1b[36m%s\x1b[0m', "#{name}: \x1b[31mfailing"
    sh.echo ""
    announce "Thanks for helping keep Habitica clean!"
    process.exit(anyFailed)

  # Prepare files, db, and spin up servers.
  prepareEnvironment: (cb) ->
    sh.exec "grunt build:test"
    sh.exec "mongo \"#{TEST_DB}\" --eval \"db.dropDatabase()\""
    sh.exec "./node_modules/protractor/bin/webdriver-manager update"

    # Spin this up even if we're not in a headless environment. Shouldn't matter.
    sh.exec "Xvfb :99 -screen 0 1024x768x24 -extension RANDR", silent: true, async: true

    sh.exec "./node_modules/protractor/bin/webdriver-manager start", silent: true, async: true
    sh.exec "NODE_DB_URI=\"#{TEST_DB_URI}\" PORT=\"#{TEST_SERVER_PORT}\" node ./website/src/server.js", silent: true, async: true
    cb()

  # Ensure both the selenium and node servers are available
  awaitServers: (cb) ->
    async.parallel [Suite.awaitSelenium, Suite.awaitNode], (err, results) ->
      throw err if err?
      cb()

  awaitSelenium: (cb) ->
    waited = 0
    interval = setInterval ->
      if sh.exec('nc -z localhost 4444').code is 0
        clearInterval(interval)
        cb()
      waited += 1
      if waited > MAX_WAIT
        clearInterval(interval)
        cb(new Error("Timed out waiting for Selenium"))
    , 1000

  awaitNode: (cb) ->
    waited = 0
    interval = setInterval ->
      if sh.exec('nc -z localhost 3001').code is 0
        clearInterval(interval)
        cb()
      waited += 1
      if waited > MAX_WAIT
        clearInterval(interval)
        cb(new Error("Timed out waiting for Node server"))
    , 1000

  runApiSpecs: ->
    announce "Running API Specs (Mocha)"
    sh.exec("NODE_ENV=testing ./node_modules/mocha/bin/mocha test/api").code

  runCommonSpecs: ->
    announce "Running Common Specs (Mocha)"
    sh.exec("NODE_ENV=testing ./node_modules/mocha/bin/mocha test/common").code

  runE2ESpecs: ->
    announce "Running End-to-End Specs (Protractor)"
    sh.exec("DISPLAY=:99 NODE_ENV=testing ./node_modules/protractor/bin/protractor protractor.conf.js").code

  runKarmaSpecs: ->
    announce "Running Karma Specs"
    sh.exec("NODE_ENV=testing grunt karma:continuous").code

Suite.run()
