(function () {
  'use strict';

  var hashMap = {
    'md5': 'MD5'
  , 'sha1': 'SHA-1'
  , 'sha256': 'SHA-256'
  , 'sha384': 'SHA-384'
  , 'sha512': 'SHA-512'
  //, 'sha3': 'SHA-3'
  };

  function getForgeProof(nodeObj) {
    return new Promise(function (resolve, reject) {
      var kdf = {
        node: nodeObj.node
      , type: nodeObj.type
      , kdf: 'PBKDF2'
      , algo: nodeObj.algo || 'SHA-256'
      , bits: nodeObj.bits || 128
      , iter: nodeObj.iter || Math.floor(Math.random() * 100) + 1001
      , salt: null
      };

      // generate a password-based 16-byte key
      // note an optional message digest can be passed as the final parameter
      if (nodeObj.salt) {
        kdf.salt = Unibabel.bufferToBinaryString(Unibabel.hexToBuffer(nodeObj.salt));
      } else {
        // uses binary string
        kdf.salt = forge.random.getBytesSync(16);
      }

      // kdf.proof = forge.pkcs5.pbkdf2(nodeObj.secret, kdf.salt, kdf.iter, kdf.byteLen);

      // generate key asynchronously
      forge.pkcs5.pbkdf2(
        nodeObj.secret
      , kdf.salt
      , kdf.iter                                    // 100
      , (kdf.bits / 8)                              // 16
      , kdf.algo.replace(/\-/g, '').toLowerCase()  // sha256
      , function(err, derivedKey) {
        // do something w/derivedKey
        if (err) {
          reject(err);
          return;
        }

        kdf.salt = Unibabel.bufferToHex(Unibabel.binaryStringToBuffer(kdf.salt));
        kdf.proof = Unibabel.bufferToHex(Unibabel.binaryStringToBuffer(derivedKey));

        resolve(kdf);
      });
    });
  }

  function getWebCryptoProof(nodeObj) {
    if (!window.crypto) {
      return new Promise(function (resolve, reject) {
        reject(new Error("Web Crypto Not Implemented"));
      });
    }

    var crypto = window.crypto;
    var Unibabel = window.Unibabel;
    var kdf = {
      node: nodeObj.node
    , type: nodeObj.type
    , kdf: 'PBKDF2'
    , algo: hashMap[nodeObj.algo] || (nodeObj.algo || 'SHA-256').toUpperCase().replace(/SHA-?/, 'SHA-')
    , bits: nodeObj.bits || 128
    , iter: nodeObj.iter || Math.floor(Math.random() * 100) + 1001
    , salt: null
    };

    // generate a password-based 16-byte key
    // note an optional message digest can be passed as the final parameter
    if (nodeObj.salt) {
      kdf.salt = Unibabel.hexToBuffer(nodeObj.salt);
    } else {
      // uses binary string
      kdf.salt = crypto.getRandomValues(new Uint8Array(16));
    }
    // 100 - probably safe even on a browser running from a raspberry pi using pure js ployfill
    // 10000 - no noticeable speed decrease on my MBP
    // 100000 - you can notice
    // 1000000 - annoyingly long
    // something a browser on a raspberry pi or old phone could do
    var aesname = "AES-CBC"; // AES-CTR is also popular
    var extractable = true;

    // First, create a PBKDF2 "key" containing the passphrase
    return crypto.subtle.importKey(
      "raw"
    , Unibabel.utf8ToBuffer(nodeObj.secret)
    , { "name": kdf.kdf }
    , false
    , ["deriveKey"]).
    // Derive a key from the password
    then(function (passphraseKey) {
      var keyconf = {
        "name": kdf.kdf
      , "salt": kdf.salt
      , "iterations": kdf.iter
      , "hash": kdf.algo
      };
      return crypto.subtle.deriveKey(
        keyconf
      , passphraseKey
        // required to be 128 or 256 bits
      , { "name": aesname, "length": kdf.bits } // Key we want
      , extractable                               // Extractble
      , [ "encrypt", "decrypt" ]                  // For new key
      );
    }).
    // Export it so we can display it
    then(function (aesKey) {
      return crypto.subtle.exportKey("raw", aesKey).then(function (arrbuf) {
        kdf.salt = Unibabel.bufferToHex(kdf.salt);
        kdf.proof = Unibabel.bufferToHex(new Uint8Array(arrbuf));

        return kdf;
      });
    });
    /*.
    catch(function (err) {
      window.alert("Key derivation failed: " + err.message);
    });
    */
  }

  // kdf, algo, iter, bits, secret
  window.getProofOfSecret = function (opts) {
    return getWebCryptoProof(opts).then(function (data) {
      return data;
    }, function (err) {
      return getForgeProof(opts);
    });
  };
}());
