var randomBytes = require("./randombytes.js");

module.exports = findPrime;
findPrime.simpleSieve = simpleSieve;
findPrime.fermatTest = fermatTest;

var BN = require("./bn.js");

var TWENTYFOUR = new BN(24);

var MillerRabin = require("./miller-rabin.js");

var millerRabin = new MillerRabin();
var ONE = new BN(1);
var TWO = new BN(2);
var FIVE = new BN(5);
var SIXTEEN = new BN(16);
var EIGHT = new BN(8);
var TEN = new BN(10);
var THREE = new BN(3);
var SEVEN = new BN(7);
var ELEVEN = new BN(11);
var FOUR = new BN(4);
var TWELVE = new BN(12);
var primes = null;

function _getPrimes() {
  if (primes !== null) return primes;
  var limit = 0x100000;
  var res = [];
  res[0] = 2;

  for (var i = 1, k = 3; k < limit; k += 2) {
    var sqrt = Math.ceil(Math.sqrt(k));

    for (var j = 0; j < i && res[j] <= sqrt; j++) if (k % res[j] === 0) break;

    if (i !== j && res[j] <= sqrt) continue;
    res[i++] = k;
  }

  primes = res;
  return res;
}

function simpleSieve(p) {
  var primes = _getPrimes();

  for (var i = 0; i < primes.length; i++) if (p.modn(primes[i]) === 0) {
    if (p.cmpn(primes[i]) === 0) {
      return true;
    } else {
      return false;
    }
  }

  return true;
}

function fermatTest(p) {
  var red = BN.mont(p);
  return TWO.toRed(red).redPow(p.subn(1)).fromRed().cmpn(1) === 0;
}

function findPrime(bits, gen) {
  if (bits < 16) {
    // this is what openssl does
    if (gen === 2 || gen === 5) {
      return new BN([0x8c, 0x7b]);
    } else {
      return new BN([0x8c, 0x27]);
    }
  }

  gen = new BN(gen);
  var num, n2;

  while (true) {
    num = new BN(randomBytes(Math.ceil(bits / 8)));

    while (num.bitLength() > bits) {
      num.ishrn(1);
    }

    if (num.isEven()) {
      num.iadd(ONE);
    }

    if (!num.testn(1)) {
      num.iadd(TWO);
    }

    if (!gen.cmp(TWO)) {
      while (num.mod(TWENTYFOUR).cmp(ELEVEN)) {
        num.iadd(FOUR);
      }
    } else if (!gen.cmp(FIVE)) {
      while (num.mod(TEN).cmp(THREE)) {
        num.iadd(FOUR);
      }
    }

    n2 = num.shrn(1);

    if (simpleSieve(n2) && simpleSieve(num) && fermatTest(n2) && fermatTest(num) && millerRabin.test(n2) && millerRabin.test(num)) {
      return num;
    }
  }
}