// Load our dependencies
var stringify = require('../common/stringify')

// Define our context Karma constructor
function ContextKarma (callParentKarmaMethod) {
  // Define local variables
  var hasError = false
  var self = this
  var isLoaded = false

  // Define our loggers
  // DEV: These are intentionally repeated in client and context
  this.log = function (type, args) {
    var values = []

    for (var i = 0; i < args.length; i++) {
      values.push(this.stringify(args[i], 3))
    }

    this.info({ log: values.join(', '), type: type })
  }

  this.stringify = stringify

  // Define our proxy error handler
  // DEV: We require one in our context to track `hasError`
  this.error = function () {
    hasError = true
    callParentKarmaMethod('error', [].slice.call(arguments))
    return false
  }

  // Define our start handler
  function UNIMPLEMENTED_START () {
    this.error('You need to include some adapter that implements __karma__.start method!')
  }
  // all files loaded, let's start the execution
  this.loaded = function () {
    // has error -> cancel
    if (!hasError && !isLoaded) {
      isLoaded = true
      try {
        this.start(this.config)
      } catch (error) {
        this.error(error.stack || error.toString())
      }
    }

    // remove reference to child iframe
    this.start = UNIMPLEMENTED_START
  }
  // supposed to be overridden by the context
  // TODO(vojta): support multiple callbacks (queue)
  this.start = UNIMPLEMENTED_START

  // Define proxy methods
  // DEV: This is a closured `for` loop (same as a `forEach`) for IE support
  var proxyMethods = ['complete', 'info', 'result']
  for (var i = 0; i < proxyMethods.length; i++) {
    (function bindProxyMethod (methodName) {
      self[methodName] = function boundProxyMethod () {
        callParentKarmaMethod(methodName, [].slice.call(arguments))
      }
    }(proxyMethods[i]))
  }

  // Define bindings for context window
  this.setupContext = function (contextWindow) {
    // If we clear the context after every run and we already had an error
    //   then stop now. Otherwise, carry on.
    if (self.config.clearContext && hasError) {
      return
    }

    // Perform window level bindings
    // DEV: We return `self.error` since we want to `return false` to ignore errors
    contextWindow.onerror = function () {
      return self.error.apply(self, arguments)
    }

    contextWindow.onbeforeunload = function () {
      return self.error('Some of your tests did a full page reload!')
    }

    contextWindow.dump = function () {
      self.log('dump', arguments)
    }

    var _confirm = contextWindow.confirm
    var _prompt = contextWindow.prompt

    contextWindow.alert = function (msg) {
      self.log('alert', [msg])
    }

    contextWindow.confirm = function (msg) {
      self.log('confirm', [msg])
      return _confirm(msg)
    }

    contextWindow.prompt = function (msg, defaultVal) {
      self.log('prompt', [msg, defaultVal])
      return _prompt(msg, defaultVal)
    }

    // If we want to overload our console, then do it
    function getConsole (currentWindow) {
      return currentWindow.console || {
        log: function () {},
        info: function () {},
        warn: function () {},
        error: function () {},
        debug: function () {}
      }
    }
    if (self.config.captureConsole) {
      // patch the console
      var localConsole = contextWindow.console = getConsole(contextWindow)
      var logMethods = ['log', 'info', 'warn', 'error', 'debug']
      var patchConsoleMethod = function (method) {
        var orig = localConsole[method]
        if (!orig) {
          return
        }
        localConsole[method] = function () {
          self.log(method, arguments)
          try {
            return Function.prototype.apply.call(orig, localConsole, arguments)
          } catch (error) {
            self.log('warn', ['Console method ' + method + ' threw: ' + error])
          }
        }
      }
      for (var i = 0; i < logMethods.length; i++) {
        patchConsoleMethod(logMethods[i])
      }
    }
  }
}

// Define call/proxy methods
ContextKarma.getDirectCallParentKarmaMethod = function (parentWindow) {
  return function directCallParentKarmaMethod (method, args) {
    // If the method doesn't exist, then error out
    if (!parentWindow.karma[method]) {
      parentWindow.karma.error('Expected Karma method "' + method + '" to exist but it doesn\'t')
      return
    }

    // Otherwise, run our method
    parentWindow.karma[method].apply(parentWindow.karma, args)
  }
}
ContextKarma.getPostMessageCallParentKarmaMethod = function (parentWindow) {
  return function postMessageCallParentKarmaMethod (method, args) {
    parentWindow.postMessage({ __karmaMethod: method, __karmaArguments: args }, window.location.origin)
  }
}

// Export our module
module.exports = ContextKarma
