var util = require('util')

var split = require('split')
var Promise = require('bluebird')

var devutil = module.exports = Object.create(null)

function closedError(err) {
  return err.message.indexOf('closed') !== -1
}

devutil.ensureUnusedPort = function(adb, serial, port) {
  return adb.openTcp(serial, port)
    .then(function(conn) {
      conn.end()
      throw new Error(util.format('Port "%d" should be unused', port))
    })
    .catch(closedError, function() {
      return Promise.resolve(port)
    })
}

devutil.waitForPort = function(adb, serial, port) {
  return adb.openTcp(serial, port)
    .then(function(conn) {
      conn.port = port
      return conn
    })
    .catch(closedError, function() {
      return Promise.delay(100)
        .then(function() {
          return devutil.waitForPort(adb, serial, port)
        })
    })
}

devutil.waitForPortToFree = function(adb, serial, port) {
  return adb.openTcp(serial, port)
    .then(function(conn) {
      var resolver = Promise.defer()

      function endListener() {
        resolver.resolve(port)
      }

      function errorListener(err) {
        resolver.reject(err)
      }

      conn.on('end', endListener)
      conn.on('error', errorListener)

      return resolver.promise.finally(function() {
        conn.removeListener('end', endListener)
        conn.removeListener('error', errorListener)
        conn.end()
      })
    })
    .catch(closedError, function() {
      return port
    })
}

devutil.listPidsByComm = function(adb, serial, comm, bin) {
  var users = {
    shell: true
  }

  return adb.shell(serial, ['ps', comm])
    .then(function(out) {
      return new Promise(function(resolve) {
        var header = false
          , pids = []
        out.pipe(split())
          .on('data', function(chunk) {
            if (header) {
              header = false
            }
            else {
              var cols = chunk.toString().split(/\s+/)
              if (cols.pop() === bin && users[cols[0]]) {
                pids.push(+cols[1])
              }
            }
          })
          .on('end', function() {
            resolve(pids)
          })
      })
    })
}

devutil.waitForProcsToDie = function(adb, serial, comm, bin) {
  return devutil.listPidsByComm(adb, serial, comm, bin)
    .then(function(pids) {
      if (pids.length) {
        return Promise.delay(100)
          .then(function() {
            return devutil.waitForProcsToDie(adb, serial, comm, bin)
          })
      }
    })
}

devutil.killProcsByComm = function(adb, serial, comm, bin, mode) {
  return devutil.listPidsByComm(adb, serial, comm, bin, mode)
    .then(function(pids) {
      if (!pids.length) {
        return Promise.resolve()
      }
      return adb.shell(serial, ['kill', mode || -15].concat(pids))
        .then(function(out) {
          return new Promise(function(resolve) {
            out.on('end', resolve)
          })
        })
        .then(function() {
          return devutil.waitForProcsToDie(adb, serial, comm, bin)
        })
        .timeout(2000)
        .catch(function() {
          return devutil.killProcsByComm(adb, serial, comm, bin, -9)
        })
    })
}

devutil.makeIdentity = function(serial, properties) {
  var model = properties['ro.product.model']
    , brand = properties['ro.product.brand']
    , manufacturer = properties['ro.product.manufacturer']
    , operator = properties['gsm.sim.operator.alpha'] ||
        properties['gsm.operator.alpha']
    , version = properties['ro.build.version.release']
    , sdk = properties['ro.build.version.sdk']
    , abi = properties['ro.product.cpu.abi']
    , product = properties['ro.product.name']

  // Remove brand prefix for consistency
  if (model.substr(0, brand.length) === brand) {
    model = model.substr(brand.length)
  }

  // Remove manufacturer prefix for consistency
  if (model.substr(0, manufacturer.length) === manufacturer) {
    model = model.substr(manufacturer.length)
  }

  // Clean up remaining model name
  // model = model.replace(/[_ ]/g, '')
  return {
    serial: serial
  , platform: 'Android'
  , manufacturer: manufacturer.toUpperCase()
  , operator: operator || null
  , model: model
  , version: version
  , abi: abi
  , sdk: sdk
  , product: product
  }
}
