'use strict'
const crypto = require('crypto')
const bindings = require('bindings')('argon2')
const Promise = require('any-promise')

const limits = Object.freeze(bindings.limits)
const types = Object.freeze(bindings.types)
const version = bindings.version

const defaults = Object.freeze({
  hashLength: 32,
  timeCost: 3,
  memoryCost: 12,
  parallelism: 1,
  type: types.argon2i,
  raw: false
})

const type2string = []

module.exports = {
  defaults,
  limits,

  hash (plain, options) {
    options = Object.assign({}, defaults, options)

    return new Promise((resolve, reject) => {
      for (let key of Object.keys(limits)) {
        const {max, min} = limits[key]
        const value = options[key]
        if (value > max || value < min) {
          reject(new Error(`Invalid ${key}, must be between ${min} and ${max}.`))
        }
      }

      if ('salt' in options) {
        return resolve()
      }

      crypto.randomBytes(16, (err, salt) => {
        if (err) {
          reject(err)
        }
        options.salt = salt
        resolve()
      })
    }).then(() => {
      return new Promise((resolve, reject) => {
        bindings.hash(Buffer.from(plain), options, resolve, reject)
      })
    }).then(hash => {
      return new Promise((resolve, reject) => {
        if (options.raw) {
          resolve(hash)
        }

        const algo = `$${type2string[options.type]}$v=${version}`
        const params = [
          `m=${1 << options.memoryCost}`,
          `t=${options.timeCost}`,
          `p=${options.parallelism}`
        ].join(',')
        const base64hash = hash.toString('base64').replace(/=/g, '')
        const base64salt = options.salt.toString('base64').replace(/=/g, '')
        resolve([algo, params, base64salt, base64hash].join('$'))
      })
    })
  },

  verify (hash, plain) {
    return new Promise((resolve, reject) => {
      bindings.verify(Buffer.from(hash), Buffer.from(plain), resolve, reject)
    })
  }
}

for (const k of Object.keys(types)) {
  module.exports[k] = types[k]
  type2string[types[k]] = k
}

@@ -416,16 +416,86 @@
 g = %5B%5D%0A%0A
+const rightPad = encoded =%3E encoded + '='.repeat(encoded.length %25 4)%0A%0A
 module.e
@@ -1050,24 +1050,31 @@
 %7B%0A          
+return 
 reject(err)%0A
@@ -1111,32 +1111,39 @@
  = salt%0A        
+return 
 resolve()%0A      
@@ -1389,32 +1389,39 @@
 aw) %7B%0A          
+return 
 resolve(hash)%0A  
@@ -1808,16 +1808,23 @@
         
+return 
 resolve(
@@ -1920,99 +1920,1351 @@
 lain
-) %7B%0A    return new Promise((resolve, reject) =%3E %7B%0A      bindings.verify(Buffer.from(hash), 
+, options) %7B%0A    options = Object.assign(%7B%7D, options)%0A    const parsed = %7B%7D%0A%0A    const sections = hash.split('$')%0A    return new Promise((resolve, reject) =%3E %7B%0A      if ('type' in options) %7B%0A        return resolve()%0A      %7D%0A%0A      parsed.type = types%5Bsections%5B1%5D%5D%0A      return resolve()%0A    %7D).then(() =%3E %7B%0A      return new Promise((resolve, reject) =%3E %7B%0A        const params = sections%5Bsections.length - 3%5D%0A%0A        if (!('memoryCost' in options)) %7B%0A          const memoryCost = /m=(%5Cd+)/.exec(params)%0A          parsed.memoryCost = Math.log2(+memoryCost%5B1%5D)%0A        %7D%0A%0A        if (!('timeCost' in options)) %7B%0A          const timeCost = /t=(%5Cd+)/.exec(params)%0A          parsed.timeCost = +timeCost%5B1%5D%0A        %7D%0A%0A        if (!('parallelism' in options)) %7B%0A          const parallelism = /p=(%5Cd+)/.exec(params)%0A          parsed.parallelism = +parallelism%5B1%5D%0A        %7D%0A%0A        return resolve()%0A      %7D)%0A    %7D).then(() =%3E %7B%0A      return new Promise((resolve, reject) =%3E %7B%0A        if ('salt' in options) %7B%0A          return resolve()%0A        %7D%0A%0A        const salt = sections%5Bsections.length - 2%5D%0A        parsed.salt = Buffer.from(rightPad(salt), 'base64')%0A        return resolve()%0A      %7D)%0A    %7D).then(() =%3E %7B%0A      options = Object.assign(%7B%7D, defaults, parsed, options)%0A%0A      return new Promise((resolve, reject) =%3E %7B%0A        return bindings.hash(
 Buff
@@ -3278,16 +3278,25 @@
 (plain),
+ options,
  resolve
@@ -3301,24 +3301,227 @@
 ve, reject)%0A
+      %7D)%0A    %7D).then(raw =%3E %7B%0A      const expected = sections%5Bsections.length - 1%5D%0A%0A      const base64hash = raw.toString('base64').replace(/=/g, '')%0A      return Promise.resolve(base64hash == expected)%0A
     %7D)%0A  %7D%0A%7D

Refactor verify binding