var files = require('../fs/files');
var runLog = require('./run-log.js');
var utils = require('../utils/utils.js');

// options: runner url browserId xunitOutputFile
var Selenium = function (options) {
  var self = this;
  options = options || {};

  self.driver = null;
  self.server = null;

  self.browserId = options.browserId || 'xunit';
  self.url = options.url || 'http://localhost:3000/' + self.browserId;
  self.xunitOutputFile = options.xunitOutputFile || 'test-results.xml';

  self.runner = options.runner;
  self.browser = options.browser || 'chrome';

  self.xunitLines = null;
};


// The magic prefix for special log output
// Must match packages/test-in-console/driver.js
var MAGIC_PREFIX = '##_meteor_magic##';

// For some reason, we can't see the console.log output
// unless we 'flush' it by sending another console.log via execute()
// Note that this is actually a magic message, so we get it echoed back to us;
// that's not necessary for this to work, but it keeps the output clean for users.
var DUMMY_FLUSH = MAGIC_PREFIX + "flush: flush";

Object.assign(Selenium.prototype, {
  // Start the selenium server, block (yield) until it is ready to go
  // (actively listening on outer and proxying to inner), and then
  // return.
  start: async function () {
    var self = this;

    if (self.server) {
      throw new Error("already running?");
    }

    self.xunitLines = [];

    var webdriver = require('selenium-webdriver');

    var capabilities;
    var loggingPrefs;
    if (self.browser === 'chrome') {
      capabilities = webdriver.Capabilities.chrome();
      loggingPrefs = {'browser': 'ALL'};
    } else if (self.browser === 'firefox') {
      capabilities = webdriver.Capabilities.firefox();
      loggingPrefs = {'browser': 'ALL'};
    } else {
      throw new Error("Unhandled browser: " + self.browser);
    }

    if (loggingPrefs) {
      capabilities = capabilities.set('loggingPrefs', loggingPrefs);
    }

    var builder = new webdriver.Builder().withCapabilities(capabilities);
    self.driver = builder.build();

    await self.driver.getSession();
    await self.driver.get(self.url);

    try {
      await self._pollLogs();
    } catch (err) {
      runLog.log("Log polling exited unexpectedly: " + err);
    }
  },

  stop: async function () {
    var self = this;

    if (! self.driver) {
      return;
    }

    await self.driver.close();
    await self.driver.quit();

    self.driver = null;
  },

  _flushLogs: async function () {
    var self = this;
    await self.driver.executeScript("console.log('" + DUMMY_FLUSH + "');", []);
  },

  _getLogs: async function () {
    var self = this;

    await self.driver.manage().logs().get('browser');
  },

  _gotStateDone: function () {
    var self = this;

    if (self.xunitOutputFile) {
      runLog.log("Writing xunit output to: " + self.xunitOutputFile);
      files.writeFile(self.xunitOutputFile, self.xunitLines.join('\n'));
    }

    if (self.runner) {
      runLog.log("Shutting down in response to 'done' state");
      self.runner.stop();
      process.exit(0);
    }
  },

  _gotState: function (state) {
    var self = this;

    runLog.log("State -> " + state);

    if (state === "done") {
      self._gotStateDone();
    }
  },

  _gotMagicLog: function (facility, msg) {
    var self = this;

    if (facility === 'xunit') {
      self.xunitLines.push(msg);
    } else if (facility === 'state') {
      self._gotState(msg);
    } else if (facility === 'flush') {
      // Ignore
    } else {
      runLog.log("Unknown magic: " + facility + ": " + msg);
    }
  },

  _pollLogsOnce: async function () {
    var self = this;

    await self._flushLogs();
    var logs = await self._getLogs() || [];
    logs.forEach(function (log) {
      var msg = log.message;
      var regex = /([^\s]*)\s*([^\s]*)\s*(.*)/i;
      var match = regex.exec(msg);
      if (!match) {
        runLog.log("Unknown console.log message format: " + JSON.stringify(log));
        return;
      }
      msg = match[3];
      if (msg === DUMMY_FLUSH) {
        return;
      }
      if (msg.indexOf(MAGIC_PREFIX) === 0) {
        msg = msg.substring(MAGIC_PREFIX.length);
        var colonIndex = msg.indexOf(': ');
        if (colonIndex === -1) {
          self._gotMagicLog('', msg);
        } else {
          var facility = msg.substring(0, colonIndex);
          msg = msg.substring(colonIndex + 2);
          self._gotMagicLog(facility, msg);
        }
      } else {
        runLog.log(msg);
      }
    });
  },

  _pollLogs: async function () {
    var self = this;

    while (self.driver) {
      try {
        await self._pollLogsOnce();
      } catch (err) {
        runLog.log("Error reading console log: " + err);
      }
      await utils.sleepMs(1000);
    }
  },
});

exports.Selenium = Selenium;
