
module.exports = publish

var path = require("path")
  , url = require("url")
  , semver = require("semver")
  , crypto = require("crypto")
  , fs = require("fs")

function publish (data, tarball, cb) {
  var email = this.conf.get('email')
  var auth = this.conf.get('_auth')
  var username = this.conf.get('username')

  if (!email || !auth || !username) {
    var er = new Error("auth and email required for publishing")
    er.code = 'ENEEDAUTH'
    return cb(er)
  }

  if (data.name !== encodeURIComponent(data.name).toLowerCase())
    return cb(new Error('invalid name: must be lowercase and url-safe'))

  var ver = semver.clean(data.version)
  if (!ver)
    return cb(new Error('invalid semver: ' + data.version))
  data.version = ver

  var self = this
  fs.stat(tarball, function(er, s) {
    if (er) return cb(er)
    fs.readFile(tarball, 'base64', function(er, tardata) {
      if (er) return cb(er)
      putFirst.call(self, data, tardata, s, username, email, cb)
    })
  })
}

function putFirst (data, tardata, stat, username, email, cb) {
  // optimistically try to PUT all in one single atomic thing.
  // If 409, then GET and merge, try again.
  // If other error, then fail.

  var root =
    { _id : data.name
    , name : data.name
    , description : data.description
    , "dist-tags" : {}
    , versions : {}
    , readme: data.readme || ""
    , maintainers :
      [ { name : username
        , email : email
        }
      ]
    }

  root.versions[ data.version ] = data
  data.maintainers = JSON.parse(JSON.stringify(root.maintainers))
  var tag = data.tag || this.conf.get('tag') || "latest"
  root["dist-tags"][tag] = data.version

  var registry = this.conf.get('registry')
  var tbName = data.name + "-" + data.version + ".tgz"
    , tbURI = data.name + "/-/" + tbName

  data._id = data.name+"@"+data.version
  data.dist = data.dist || {}
  data.dist.shasum = crypto.createHash("sha1").update(tardata, 'base64').digest("hex")
  data.dist.tarball = url.resolve(registry, tbURI)
                         .replace(/^https:\/\//, "http://")

  root._attachments = {}
  root._attachments[ tbName ] = {
    content_type: 'application/octet-stream',
    data: tardata,
    length: stat.size
  };

  this.request("PUT", data.name, root, function (er, parsed, json, res) {
    var r409 = "must supply latest _rev to update existing package"
    var r409b = "Document update conflict."
    var conflict = res && res.statusCode === 409
    if (parsed && (parsed.reason === r409 || parsed.reason === r409b))
      conflict = true

    // a 409 is typical here.  GET the data and merge in.
    if (er && !conflict) {
      this.log.error("publish", "Failed PUT "
                    +(res && res.statusCode))
      return cb(er)
    }

    if (!er && !conflict)
      return cb(er, parsed, json, res)

    // let's see what versions are already published.
    this.request("GET", data.name, function (er, current) {
      if (er)
        return cb(er)
      putNext.call(this, data.version, root, current, cb)
    }.bind(this))
  }.bind(this))
}

function putNext(newVersion, root, current, cb) {
  // already have the tardata on the root object
  // just merge in existing stuff
  // if the version already exists, and not a --force, then raise error
  var force = this.conf.get('force')
  var curVers = Object.keys(current.versions || {}).map(function (v) {
    return semver.clean(v, true)
  })

  if (!force && curVers.indexOf(newVersion) !== -1) {
    return cb(conflictError(root.name))
  }

  current.versions[newVersion] = root.versions[newVersion]
  for (var i in root) {
    switch (i) {
      // objects that copy over the new stuffs
      case 'dist-tags':
      case 'versions':
      case '_attachments':
        for (var j in root[i])
          current[i][j] = root[i][j]
        break

      // ignore these
      case 'maintainers':
        break;

      // copy
      default:
        current[i] = root[i]
    }
  }
  var maint = JSON.parse(JSON.stringify(root.maintainers))
  root.versions[newVersion].maintainers = maint

  this.request("PUT", root.name, current, cb)
}

function conflictError (pkgid) {
  var e = new Error("cannot modify existing version")
  e.code = "EPUBLISHCONFLICT"
  e.pkgid = pkgid
  return e
}
