var url = require('url')
var assert = require('assert')
var util = require('util')
var semver = require('semver')
var HostedGit = require('hosted-git-info')

module.exports = npa

var isWindows = process.platform === 'win32' || global.FAKE_WINDOWS
var slashRe = isWindows ? /\\|[/]/ : /[/]/

var parseName = /^(?:@([^/]+?)[/])?([^/]+?)$/
var nameAt = /^(@([^/]+?)[/])?([^/]+?)@/
var debug = util.debuglog
  ? util.debuglog('npa')
  : /\bnpa\b/i.test(process.env.NODE_DEBUG || '')
    ? function () {
      console.error('NPA: ' + util.format.apply(util, arguments).split('\n').join('\nNPA: '))
    }
    : function () {}

function validName (name) {
  if (!name) {
    debug('not a name %j', name)
    return false
  }
  var n = name.trim()
  if (!n || n.charAt(0) === '.' ||
    !n.match(/^[a-zA-Z0-9]/) ||
    n.match(/[/()&?#|<>@:%\s\\*'"!~`]/) ||
    n.toLowerCase() === 'node_modules' ||
    n !== encodeURIComponent(n) ||
    n.toLowerCase() === 'favicon.ico') {
    debug('not a valid name %j', name)
    return false
  }
  return n
}

function npa (arg) {
  assert.equal(typeof arg, 'string')
  arg = arg.trim()

  var res = new Result()
  res.raw = arg
  res.scope = null
  res.escapedName = null

  // See if it's something like foo@...
  var nameparse = arg.match(nameAt)
  debug('nameparse', nameparse)
  if (nameparse && validName(nameparse[3]) &&
    (!nameparse[2] || validName(nameparse[2]))) {
    res.name = (nameparse[1] || '') + nameparse[3]
    res.escapedName = escapeName(res.name)
    if (nameparse[2]) {
      res.scope = '@' + nameparse[2]
    }
    arg = arg.substr(nameparse[0].length)
  } else {
    res.name = null
  }

  res.rawSpec = arg
  res.spec = arg

  var urlparse = url.parse(arg)
  debug('urlparse', urlparse)

  // windows paths look like urls
  // don't be fooled!
  if (isWindows && urlparse && urlparse.protocol &&
    urlparse.protocol.match(/^[a-zA-Z]:$/)) {
    debug('windows url-ish local path', urlparse)
    urlparse = {}
  }

  if (urlparse.protocol || HostedGit.fromUrl(arg)) {
    return parseUrl(res, arg, urlparse)
  }

  // at this point, it's not a url, and not hosted
  // If it's a valid name, and doesn't already have a name, then assume
  // $name@"" range
  //
  // if it's got / chars in it, then assume that it's local.

  if (res.name) {
    if (arg === '') arg = 'latest'
    var version = semver.valid(arg, true)
    var range = semver.validRange(arg, true)
    // foo@...
    if (version) {
      res.spec = version
      res.type = 'version'
    } else if (range) {
      res.spec = range
      res.type = 'range'
    } else if (slashRe.test(arg)) {
      parseLocal(res, arg)
    } else {
      res.type = 'tag'
      res.spec = arg
    }
  } else {
    var p = arg.match(parseName)
    if (p && validName(p[2]) &&
      (!p[1] || validName(p[1]))) {
      res.type = 'tag'
      res.spec = 'latest'
      res.rawSpec = ''
      res.name = arg
      res.escapedName = escapeName(res.name)
      if (p[1]) {
        res.scope = '@' + p[1]
      }
    } else {
      parseLocal(res, arg)
    }
  }

  return res
}

function escapeName (name) {
  // scoped packages in couch must have slash url-encoded, e.g. @foo%2Fbar
  return name && name.replace('/', '%2f')
}

function parseLocal (res, arg) {
  // turns out nearly every character is allowed in fs paths
  if (/\0/.test(arg)) {
    throw new Error('Invalid Path: ' + JSON.stringify(arg))
  }
  res.type = 'local'
  res.spec = arg
}

function parseUrl (res, arg, urlparse) {
  var gitHost = HostedGit.fromUrl(arg)
  if (gitHost) {
    res.type = 'hosted'
    res.spec = gitHost.toString()
    res.hosted = {
      type: gitHost.type,
      ssh: gitHost.ssh(),
      sshUrl: gitHost.sshurl(),
      httpsUrl: gitHost.https(),
      gitUrl: gitHost.git(),
      shortcut: gitHost.shortcut(),
      directUrl: gitHost.file('package.json')
    }
    return res
  }
  // check the protocol, and then see if it's git or not
  switch (urlparse.protocol) {
    case 'git:':
    case 'git+http:':
    case 'git+https:':
    case 'git+rsync:':
    case 'git+ftp:':
    case 'git+ssh:':
    case 'git+file:':
      res.type = 'git'
      res.spec = arg.replace(/^git[+]/, '')
      break

    case 'http:':
    case 'https:':
      res.type = 'remote'
      res.spec = arg
      break

    case 'file:':
      res.type = 'local'
      if (isWindows && arg.match(/^file:\/\/\/?[a-z]:/i)) {
        // Windows URIs usually parse all wrong, so we just take matters
        // into our own hands, in this case.
        res.spec = arg.replace(/^file:\/\/\/?/i, '')
      } else {
        res.spec = urlparse.pathname
      }
      break

    default:
      throw new Error('Unsupported URL Type: ' + arg)
  }

  return res
}

function Result () {
  if (!(this instanceof Result)) return new Result()
}
Result.prototype.name = null
Result.prototype.type = null
Result.prototype.spec = null
Result.prototype.raw = null
Result.prototype.hosted = null
