var parseKeys = require('parse-asn1');
var mgf = require('./mgf');
var xor = require('./xor');
var bn = require('bn.js');
var constants = {
  RSA_PKCS1_OAEP_PADDING: 4,
  RSA_PKCS1_PADDIN: 1,
  RSA_NO_PADDING: 3
};

module.exports = function (crypto) {
  return publicEncrypt;
  function publicEncrypt(public_key, msg) {
    var padding;
    if (public_key.padding) {
      padding = public_key.padding;
    } else {
      padding = 4;
    }
    var key = parseKeys(public_key);
    var paddedMsg;
    if (padding === 4) {
      paddedMsg = oaep(key, msg, crypto);
    } else if (padding === 1) {
      paddedMsg = pkcs1(key, msg, crypto);
    } else if (padding === 3) {
      paddedMsg = new bn(msg);
      if (paddedMsg.cmp(key.modulus) >= 0) {
        throw new Error('data too long for modulus');
      }
    } else {
      throw new Error('unknown padding');
    }
    var enc = paddedMsg
      .toRed(bn.mont(key.modulus))
      .redPow(new bn(key.publicExponent))
      .fromRed()
      .toArray();
    return new Buffer(enc);
  }
};

function oaep(key, msg, crypto){
  var k = key.modulus.byteLength();
  var mLen = msg.length;
  var iHash = crypto.createHash('sha1').update(new Buffer('')).digest();
  var hLen = iHash.length;
  var hLen2 = 2 * hLen;
  if (mLen > k - hLen2 - 2) {
    throw new Error('message too long');
  }
  var ps = new Buffer(k - mLen - hLen2 - 2);
  ps.fill(0);
  var dblen = k - hLen - 1;
  var seed = crypto.randomBytes(hLen);
  var maskedDb = xor(Buffer.concat([iHash, ps, new Buffer([1]), msg], dblen), mgf(seed, dblen, crypto));
  var maskedSeed = xor(seed, mgf(maskedDb, hLen, crypto));
  return new bn(Buffer.concat([new Buffer([0]), maskedSeed, maskedDb], k));
}
function pkcs1(key, msg, crypto){
  var mLen = msg.length;
  var k = key.modulus.byteLength();
  if (mLen > k - 11) {
    throw new Error('message too long');
  }
  var ps = nonZero(k - mLen - 3, crypto);
  return new bn(Buffer.concat([new Buffer([0, 2]), ps, new Buffer([0]), msg], k));
}
function nonZero(len, crypto) {
  var out = new Buffer(len);
  var i = 0;
  var cache = crypto.randomBytes(len*2);
  var cur = 0;
  var num;
  while (i < len) {
    if (cur === cache.length) {
      cache = crypto.randomBytes(len*2);
      cur = 0;
    }
    num = cache[cur++];
    if (num) {
      out[i++] = num;
    }
  }
  return out;
}