"use strict";

var proxyTo = require('../common.js').proxyTo
  , _ = require('underscore')
  , logger = require('../../server/logger.js').get('appium')
  , exec = require('child_process').exec
  , spawn = require('child_process').spawn
  , async = require('async')
  , through = require('through')
  , isWindows = require('../../helpers.js').isWindows()
  , isLinux = require('../../helpers.js').isLinux()
  , path = require('path')
  , fs = require('fs');

var Chromedriver = function (args, onDie) {
  this.proxyHost = '127.0.0.1';
  this.proxyPort = args.port || 9515;
  this.deviceId = args.deviceId;
  this.enablePerformanceLogging = args.enablePerformanceLogging;
  this.proc = null;
  this.onChromedriverStart = null;
  this.onDie = onDie;
  this.exitCb = null;
  this.shuttingDown = false;
};

Chromedriver.prototype.ensureChromedriverExists = function (cb) {
  logger.info("Ensuring Chromedriver exists");
  var executable = isWindows ? "chromedriver.exe" : "chromedriver";
  var platform = "mac";
  if (isWindows) {
    platform = "windows";
  } else if (isLinux) {
    platform = "linux";
  }
  this.chromedriver = path.resolve(__dirname, "..", "..", "..", "build", "chromedriver", platform, executable);

  fs.exists(this.chromedriver, function (exists) {
    if (!exists) return cb(new Error("Could not find chromedriver. Need to run reset script?"));
    cb();
  });
};

Chromedriver.prototype.killOldChromedrivers = function (cb) {
  var cmd;
  if (isWindows) {
    cmd = "FOR /F \"usebackq tokens=5\" %%a in (`netstat -nao ^| findstr /R /C:\"" + this.proxyPort + " \"`) do (" +
            "FOR /F \"usebackq\" %%b in (`TASKLIST /FI \"PID eq %%a\" ^| findstr /I chromedriver.exe`) do (" +
              "IF NOT %%b==\"\" TASKKILL /F /PID %%b" +
            ")" +
          ")";
  } else {
    cmd = "ps -e | grep " + this.chromedriver + " | grep -v grep |" +
      "grep -e '--port=" + this.proxyPort + "$' | awk '{ print $1 }' | " +
      "xargs kill -15";
  }
  logger.info("Killing any old chromedrivers, running: " + cmd);
  exec(cmd, function (err) {
    if (err) {
      logger.info("No old chromedrivers seemed to exist");
    } else {
      logger.info("Successfully cleaned up old chromedrivers");
    }
    cb();
  });
};

Chromedriver.prototype.start = function (cb) {
  this.onChromedriverStart = cb;
  logger.info("Spawning chromedriver with: " + this.chromedriver);
  var alreadyReturned = false;
  var args = ["--url-base=wd/hub", "--port=" + this.proxyPort];
  this.proc = spawn(this.chromedriver, args);
  this.proc.stdout.setEncoding('utf8');
  this.proc.stderr.setEncoding('utf8');

  this.proc.on('error', function (err) {
    logger.error('Chromedriver process failed with error: ' + err.message);
    alreadyReturned = true;
    this.shuttingDown = true;
    logger.error('Killing chromedriver');
    this.proc.kill();
    this.onDie();
  }.bind(this));

  this.proc.stdout.pipe(through(function (data) {
    logger.info('[CHROMEDRIVER] ' + data.trim());
    if (!alreadyReturned && data.indexOf('Starting ChromeDriver') === 0) {
      this.chromedriverStarted = true;
      alreadyReturned = true;
      return cb();
    }
  }.bind(this)));

  this.proc.stderr.pipe(through(function (data) {
    logger.info('[CHROMEDRIVER STDERR] ' + data.trim());
  }));

  this.proc.on('exit', this.onClose.bind(this));
  this.proc.on('close', this.onClose.bind(this));
};

Chromedriver.prototype.onClose = function (code, signal) {
  if (!this.shuttingDown) {
    this.shuttingDown = true;
    logger.info("Chromedriver exited with code " + code);
    if (signal) {
      logger.info("(killed by signal " + signal + ")");
    }
    if (!this.chromedriverStarted) {
      return this.onChromedriverStart(
          new Error("Chromedriver quit before it was available"));
    }
    if (this.exitCb !== null) {
      return this.exitCb();
    }
    this.onDie();
  }
};

Chromedriver.prototype.createSession = function (caps, cb) {
  logger.info("Creating Chrome session");
  caps.chromeOptions.androidDeviceSerial = this.deviceId;
  if (this.enablePerformanceLogging) {
    caps.loggingPrefs = {performance: 'ALL'};
  }
  var data = {
    sessionId: null,
    desiredCapabilities: caps
  };
  async.waterfall([
    this.ensureChromedriverExists.bind(this),
    this.killOldChromedrivers.bind(this),
    this.start.bind(this),
    _.partial(this.proxyNewSession.bind(this), data)
  ], cb);
};

Chromedriver.prototype.proxyNewSession = function (data, cb) {
  var maxRetries = 5;
  var curRetry = 0;
  var retryInt = 500;
  var doProxy = function (alreadyRestarted) {
    this.proxyTo('/wd/hub/session', 'POST', data, function (err, res, body) {
      if (err) {
        if (/ECONNREFUSED/.test(err.message) && curRetry < maxRetries) {
          logger.info("Could not connect yet; retrying");
          curRetry++;
          setTimeout(doProxy, retryInt);
          return;
        }
        return cb(err);
      }

      // first checking if we get a well formed success response
      this.chromeSessionId = null;
      try {
        if (body.status === 0 && body.sessionId) {
          logger.info("Successfully started chrome session");
          this.chromeSessionId = body.sessionId;
        }
      } catch (ignore) {}
      if (this.chromeSessionId) return cb(null, this.chromeSessionId);

      // then check redirect success case
      try {
        if (res.statusCode === 303 && res.headers.location) {
          logger.info("Successfully started chrome session");
          var loc = res.headers.location;
          this.chromeSessionId = /\/([^\/]+)$/.exec(loc)[1];
        }
      } catch (ignore) {}
      if (this.chromeSessionId) return cb(null, this.chromeSessionId);

      // those are error cases
      if (typeof body !== "undefined" &&
                 typeof body.value !== "undefined" &&
                 typeof body.value.message !== "undefined" &&
                 body.value.message.indexOf("Failed to run adb command") !== -1) {
        logger.error("Chromedriver had trouble running adb");
        if (!alreadyRestarted) {
          logger.error("Restarting adb for chromedriver");
          return this.adb.restartAdb(function () {
            this.adb.getConnectedDevices(function () {
              doProxy(true);
            }.bind(this));
          }.bind(this));
        } else {
          cb(new Error("Chromedriver wasn't able to use adb. Is the server up?"));
        }
      } else {
        logger.error("Chromedriver create session did not work. Status was " +
                     res.statusCode + " and body was " +
                     JSON.stringify(body));
        cb(new Error("Did not get session redirect from Chromedriver"));
      }
    }.bind(this));
  }.bind(this);

  doProxy();
};

Chromedriver.prototype.deleteSession = function (cb) {
  logger.info("Deleting Chrome session");
  var url = '/wd/hub/session/' + this.chromeSessionId;
  this.proxyTo(url, 'DELETE', null, function (err, res) {
    if (err) return cb(err);
    if (res.statusCode !== 200) return cb(new Error("Status was not 200"));
    cb();
  }.bind(this));
};

Chromedriver.prototype.stop = function (cb) {
  logger.info('Killing chromedriver');
  this.exitCb = cb;
  this.proc.kill();
};

Chromedriver.prototype.proxyTo = proxyTo;

module.exports = Chromedriver;
