(function() {

var system = require("system");
var webpage = require("webpage");

this.Runner = (function() {

  function Runner() {
    this.url = system.args[1];
    this.timeout = parseInt(system.args[2] || 180) * 1000;

    var _this = this;
    this.waitForResults = function() { Runner.prototype.waitForResults.apply(_this, arguments) };
  }

  Runner.prototype.run = function() {
    this.initPage();
    this.loadPage();
  };

  Runner.prototype.initPage = function() {
    this.page = webpage.create();
    this.page.viewportSize = {
      width: 800,
      height: 800
    };
  };

  Runner.prototype.loadPage = function() {
    this.page.open(this.url);
    var callbacks = this.pageCallbacks();
    for (var name in callbacks) {
      this.page[name] = callbacks[name];
    }
  };

  Runner.prototype.waitForResults = function() {
    if ((new Date().getTime() - this.start) >= this.timeout) this.fail("Timed out");
    var finished = this.page.evaluate(function() { return window.Teaspoon && window.Teaspoon.finished });
    finished ? this.finish() : setTimeout(this.waitForResults, 200);
  };

  Runner.prototype.fail = function(msg, errno) {
    if (msg == null) msg = null;
    if (errno == null) errno = 1;

    console.log(JSON.stringify({
      _teaspoon: true,
      type: "exception",
      message: msg
    }));
    phantom.exit(errno);
  };

  Runner.prototype.finish = function() {
    phantom.exit(0);
  };

  Runner.prototype.pageCallbacks = function() {
    var _this = this;
    return {
      onError: function(message, trace) {
        var started = _this.page.evaluate(function() {
          return window.Teaspoon && window.Teaspoon.started;
        });
        console.log(JSON.stringify({
          _teaspoon: true,
          type: started ? "error" : "exception",
          message: message,
          trace: trace
        }));
        _this.errored = true;
        if (/^TeaspoonError: /.test(message || "")) {
          _this.fail("Execution halted.");
        }
      },

      onConsoleMessage: function(msg) {
        console.log(msg);
        if (_this.errorTimeout) clearTimeout(_this.errorTimeout);
        if (_this.errored) {
          _this.errorTimeout = setTimeout((function() {
            return _this.fail("Javascript error has caused a timeout.");
          }), _this.timeout);
          return _this.errored = false;
        }
      },

      onLoadFinished: function(status) {
        if (_this.start) return;
        _this.start = new Date().getTime();
        var defined = _this.page.evaluate(function() {
          return window.Teaspoon;
        });
        if (!(status === "success" && defined)) {
          if (status === "success") {
            // Could not load the window.Teaspoon object from the JavaScript on the
            // rendered page. This indicates that a problem occured. Lets therfore
            // print the page as a failure description.
            // Get plain text of the page, intend all lines (better readable)
            var ind = "   ";
            var error = _this.page.plainText.replace(/(?:\n)/g, "\n" + ind);
            // take only first 10 lines, as they usually provide a good entry
            // point for debugging and we should not spam our console.
            var erroroutput = error.split("\n").slice(0, 10);
            if (erroroutput !== error) {
              erroroutput.push("... (further lines have been removed)");
            }
            var fail = [
              "Failed to get Teaspoon result object on page: " + _this.url,
              "The title of this page was '" + _this.page.title + "'.",
              "",
              erroroutput.join("\n")
            ];

            _this.fail(fail.join(" \n" + ind));
          }
          else {
            // Status is not 'success'
            _this.fail("Failed to load: " + _this.url + ". Status: " + status);
            return;
          }
        }
        _this.waitForResults();
      }
    };
  };

  return Runner;

})();

new Runner().run();

}).call(this);