var fs       = require('fs')
  , debug    = require('debug')('win-detect-browsers')
  , which    = require('which')
  , env      = require('./env')
  , Browser  = require('./browser')
  , exec     = require('./exec')
  , util     = require('util')
  , abbrs    = { HKEY_LOCAL_MACHINE: 'HKLM', HKEY_CURRENT_USER: 'HKCU'}
  , hives    = Object.keys(abbrs)
  , after    = require('after')

module.exports = Finder

function Finder(name, opts) {
  opts || (opts = {})

  this.name = name
  this.bin = opts.bin || (name + '.exe')
  this._find = opts.find || function() {}
}

Finder.prototype.file = function(envVar, file) {
  if (!this.plan()) return

  if (file === undefined) file = envVar, envVar = null

  if (envVar) {
    if (!env[envVar]) return this.unplan()
    file = [env[envVar], file].join('\\')
  }

  fs.exists(file, function(exists){
    this.unplan(exists && file, null, 'default location')
  }.bind(this))
}

Finder.prototype.dir = function(envVar, path) {
  if (path === undefined) path = envVar, envVar = null
  this.file(envVar, path + '\\' + this.bin)
}

Finder.prototype.registry = function(key) { 
  if (!this.plan()) return

  var next  = after(hives.length, this.unplan.bind(this))

  hives.forEach(function(hive){
    this.pathFromRegistry(key, hive, null, next)
  }, this)
}

// First query for version, then for path
Finder.prototype.versionRegistry = function (versionKey, pathKey) { 
  if (!this.plan()) return

  var next  = after(hives.length, this.unplan.bind(this))
    , finder = this

  hives.forEach(function(hive){
    regQuery(versionKey, hive, function (err, out) {
      var matches = out && /\s([\d\.]+).*$/.exec(out)
      var version = matches && matches[1]
      
      if (!version) return next()
      
      debug('%s: found version "%s" in registry', finder.name, version)

      // We need the full regex match for the next query.
      // The path to Firefox for example, is stored under
      // a registry key like `33.0.2 (x86 en-US)` but 
      // for our version number we only want the digits.
      finder.pathFromRegistry(pathKey, [hive, matches[0].trim()], version, next)
    })
  })
}

Finder.prototype.pathFromRegistry = function(tpl, args, version, cb) {
  regQuery(tpl, args, function(err, out, key){
    if (out) { // find a path
      out = out.trim().split(/\r\n/).pop()
      var m = /([a-z]:\\[^:\t"]+)/i.exec(out)
      var path = m && m[1] && m[1].trim()
      if (path) this.found(path, version, 'registry ('+short(key)+')')
    }

    cb()
  }.bind(this))
}

Finder.prototype.startMenu = function(startEntry) {
  // eg, 'Google Chrome' or 'iexplore.exe'
  startEntry || (startEntry = this.bin)

  this.registry(
    '"%s\\Software\\Clients\\StartMenuInternet\\'
    + startEntry + '\\shell\\open\\command"'
  )

  if (process.arch == 'x64') {
    this.registry(
      '"%s\\Software\\Wow6432Node\\Clients\\StartMenuInternet\\'
      + startEntry + '\\shell\\open\\command"'
    )
  }
}

Finder.prototype.find = function(opts, done) {
  this.lucky = !! opts.lucky
  this.planned = 0
  this.numFound = 0
  this.results = {}
  this.complete = done
 
  // Call find function
  this._find()

  // Find in PATH
  this.plan()

  which(this.bin, function(err, path){
    if (path) this.found(path, null, 'PATH')
    this.unplan()
  }.bind(this))
}

// Callback to gather results. If no version
// is given it will be retrieved later.
Finder.prototype.found = function(path, version, method) {
  if (this.planned==null) return

  debug('%s: found "%s" in %s', this.name, path, method)

  // Normalize to catch duplicates.
  var normal = path.toLowerCase().replace('/', '\\')

  if (normal.slice(-4)==='.cmd')
    normal = normal.slice(0,-4)

  if (this.results[normal] != null) {
    var prev = this.results[normal]
    if (normal!==path) prev.path = path
    if (!prev.version && version) prev.setVersion(version)
    return
  }

  this.numFound++
  this.results[normal] = new Browser(this.name, path, version)
}

Finder.prototype.plan = function() {
  if (this.planned==null) return
  return ++this.planned
}

Finder.prototype.unplan = function(path) {
  if (this.planned==null) return

  // shortcut for `this.found(xx); this.unplan()`
  if (path) this.found.apply(this, arguments)

  var end = function() {
    if (this.planned == null)
      return

    if (--this.planned<=0 || (this.numFound && this.lucky)) {
      this.planned = null
      
      var a = [] // toArray
      for(var k in this.results)
        a.push(this.results[k])
      
      this.complete(null, a)
    }
  }.bind(this)

  if (this.numFound && this.lucky) end()
  else setImmediate(end)  // allow more to be planned
}

function regQuery(tpl, args, cb) {
  var key = util.format.apply(null, [tpl].concat(args))
  exec('reg', ['query', key], function(err, out){
    cb(err, out, key)
  })
}

function short(key) {
  for(var hive in abbrs)
    key = key.replace(hive, abbrs[hive])
  return key
}
