/***** BEGIN LICENSE BLOCK *****
 * Version: EPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Eclipse Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/epl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the EPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the EPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby.ext.openssl;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.Random;

import org.jruby.Ruby;
import org.jruby.RubyBignum;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyInteger;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.RubyString;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.Arity;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.runtime.Visibility;

/**
 * OpenSSL::BN implementation. Wraps java.math.BigInteger, which provides
 * most functionality directly; the rest is easily derived.
 *
 * Beware that BN's are mutable -- I don't agree with this approach, but
 * must conform for compatibility with MRI's implementation. The offending methods
 * are set_bit!, clear_bit!, mask_bits! and copy.<p>
 *
 * I've included a few operations (& | ^ ~) that aren't defined by MRI/OpenSSL.
 * These are non-portable (i.e., won't work in C-Ruby), so use at your own risk.<p>
 *
 * @author <a href="mailto:bill.dortch@gmail.com">Bill Dortch</a>
 */
@JRubyClass(name = "OpenSSL::BN", include = "Comparable")
public class BN extends RubyObject {
    private static final long serialVersionUID = -5660938062191525498L;

    private static final BigInteger MAX_INT = BigInteger.valueOf(Integer.MAX_VALUE);
    static final BigInteger TWO = BigInteger.valueOf(2);

    private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
    private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);

    private static final int DEFAULT_CERTAINTY = 100;

    private static final ObjectAllocator BN_ALLOCATOR = new ObjectAllocator() {
        public BN allocate(Ruby runtime, RubyClass klass) { return new BN(runtime, klass); }
    };

    public static BN newBN(Ruby runtime, BigInteger value) {
        return newInstance(runtime, value);
    }

    static BN newInstance(final Ruby runtime, BigInteger value) {
        return new BN(runtime, value != null ? value : BigInteger.ZERO);
    }

    //static BN newInstance(final Ruby runtime, long value) {
    //    return new BN(runtime, BigInteger.valueOf(value));
    //}

    public static void createBN(final Ruby runtime, final RubyModule OpenSSL) {
        final RubyClass OpenSSLError = OpenSSL.getClass("OpenSSLError");
        OpenSSL.defineClassUnder("BNError", OpenSSLError, OpenSSLError.getAllocator());

        RubyClass BN = OpenSSL.defineClassUnder("BN", runtime.getObject(), BN_ALLOCATOR);
        BN.includeModule( runtime.getModule("Comparable") );
        BN.defineAnnotatedMethods(BN.class);
    }

    private volatile BigInteger value;

    private BN(Ruby runtime, RubyClass clazz) {
        super(runtime, clazz);
        this.value = BigInteger.ZERO;
    }

    protected BN(Ruby runtime, BigInteger value) {
        super(runtime, runtime.getModule("OpenSSL").getClass("BN"));
        this.value = value;
    }

    public final BigInteger getValue() {
        return value;
    }

    @JRubyMethod(name="initialize", required=1, optional=1, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(final ThreadContext context,
        final IRubyObject[] args) {
        final Ruby runtime = context.runtime;
        if (this.value != BigInteger.ZERO) { // already initialized
            throw newBNError(runtime, "illegal initialization");
        }
        int argc = Arity.checkArgumentCount(runtime, args, 1, 2);
        int base = argc == 2 ? RubyNumeric.num2int(args[1]) : 10;
        final RubyString str = args[0].asString();
        switch (base) {
        case 0:
            final byte[] bytes = str.getBytes(); final int signum;
            if ( ( bytes[0] & 0x80 ) != 0 ) {
                bytes[0] &= 0x7f; signum = -1;
            }
            else {
                signum = 1;
            }
            this.value = new BigInteger(signum, bytes);
            break;
        case 2:
            // this seems wrong to me, but is the behavior of the
            // MRI implementation. rather than interpreting the string
            // as ASCII-encoded binary digits, the raw binary value of
            // the string is used instead. the value is always interpreted
            // as positive, hence the use of the signum version of the BI
            // constructor here:
            this.value = new BigInteger(1, str.getBytes());
            break;
        case 10:
        case 16:
            // here, the ASCII-encoded decimal or hex string is used
            try {
                this.value = new BigInteger(str.toString(), base);
                break;
            } catch (NumberFormatException e) {
                throw runtime.newArgumentError("value " + str + " is not legal for radix " + base);
            }
        default:
            throw runtime.newArgumentError("illegal radix: " + base);
        }
        return this;
    }

    @Override
    public synchronized IRubyObject initialize_copy(final IRubyObject that) {
        super.initialize_copy(that);
        if ( this != that ) this.value = ((BN) that).value;
        return this;
    }

    @JRubyMethod(name = "copy")
    public IRubyObject copy(IRubyObject other) {
        if (this != other) {
            this.value = asBigInteger(other);
        }
        return this;
    }

    @JRubyMethod(name = "to_s", rest = true, optional = 1)
    public RubyString to_s(IRubyObject[] args) {
        int argc = Arity.checkArgumentCount(getRuntime(), args, 0, 1);
        return to_s( argc == 1 ? RubyNumeric.num2int(args[0]) : 10 );
    }

    // 1.6.8 can not handle - this way :

    @Override
    //@JRubyMethod(name = "to_s")
    public RubyString to_s() { return to_s(10); }

    //@JRubyMethod(name = "to_s")
    //public RubyString to_s(IRubyObject base) {
    //    return to_s( RubyNumeric.num2int(base) );
    //}

    private RubyString to_s(final int base) {
        final Ruby runtime = getRuntime();

        byte[] bytes;
        switch (base) {
        case 0:
            bytes = this.value.abs().toByteArray();
            int offset = 0;
            if (bytes[0] == 0) {
                offset = 1;
            }
            int length = bytes.length - offset;
            boolean negative = BigInteger.ZERO.compareTo(this.value) > 0;
            // for positive values with most significant bit in first byte,
            // add leading '\0'
            boolean need0 = !negative && (bytes[offset] & 0x80) != 0;
            if (negative) {
                // for negative values, set most significant bit in first byte
                bytes[offset] |= 0x80;
            } else if (need0) {
                length++;
            }
            byte[] data = new byte[5 + length];
            data[0] = (byte)(0xff & (length >> 24));
            data[1] = (byte)(0xff & (length >> 16));
            data[2] = (byte)(0xff & (length >>  8));
            data[3] = (byte)(0xff & (length >>  0));
            if (need0) {
                data[4] = 0;
                System.arraycopy(bytes, offset, data, 5, length - 1);
            } else {
                System.arraycopy(bytes, offset, data, 4, length);
            }
            return runtime.newString(new ByteList(data, 0, 4 + length, false));
        case 2:
            // again, following MRI implementation, wherein base 2 deals
            // with strings as byte arrays rather than ASCII-encoded binary
            // digits.  note that negative values are returned as though positive:
            bytes = this.value.abs().toByteArray();
            // suppress leading 0 byte to conform to MRI behavior
            if (bytes[0] == 0) {
                return runtime.newString(new ByteList(bytes, 1, bytes.length - 1, false));
            }
            return runtime.newString(new ByteList(bytes, false));
        case 10:
            return runtime.newString(value.toString(10));
        case 16:
            final String hex = value.toString(16);
            final int len = hex.length();
            final ByteList val = new ByteList(len + 1);
            if ( value.signum() == 1 && len % 2 != 0 ) val.append('0');
            for ( int i = 0; i < len ; i++ ) {
                val.append( Character.toUpperCase(hex.charAt(i)) );
            }
            return runtime.newString(val);
        default:
            throw runtime.newArgumentError("illegal radix: " + base);
        }
    }

    @Override
    public String toString() {
        return to_s().toString();
    }

    public String toString(int base) {
        return to_s(base).toString();
    }

    @Override
    @SuppressWarnings("unchecked")
    @JRubyMethod
    public IRubyObject inspect() {
        return ObjectSupport.inspect(this, Collections.EMPTY_LIST);
    }

    @Override
    public boolean equals(Object other) {
        return (other instanceof BN) ? this.value.equals(((BN) other).value) : false;
    }

    @Override
    public int hashCode() {
        return 997 * value.hashCode();
    }

    @JRubyMethod(name = "hash")
    public RubyInteger hash(final ThreadContext context) {
        return context.runtime.newFixnum(hashCode());
    }

    @JRubyMethod(name = "to_i")
    public RubyInteger to_i() {
        if ( value.compareTo( MAX_LONG ) > 0 || value.compareTo( MIN_LONG ) < 0 ) {
            return RubyBignum.newBignum(getRuntime(), value);
        }
        return RubyFixnum.newFixnum(getRuntime(), value.longValue());
    }

    @JRubyMethod(name = "to_bn")
    public BN to_bn() {
        return this;
    }

    @JRubyMethod(name="coerce")
    // FIXME: is this right? don't see how it would be useful...
    public IRubyObject coerce(IRubyObject other) {
        final Ruby runtime = getRuntime();
        IRubyObject self;
        if ( other instanceof RubyString ) {
            self = runtime.newString(value.toString());
        }
        else if ( other instanceof RubyInteger ) {
            self = to_i();
        }
        else if ( other instanceof BN ) {
            self = this;
        }
        else {
            throw runtime.newTypeError("don't know how to coerce to " + other.getMetaClass().getName());
        }
        return runtime.newArray(other, self);
    }

    @JRubyMethod(name="zero?")
    public RubyBoolean zero_p(final ThreadContext context) {
        return context.runtime.newBoolean( value.equals(BigInteger.ZERO) );
    }

    @JRubyMethod(name="one?")
    public RubyBoolean one_p(final ThreadContext context) {
        return context.runtime.newBoolean( value.equals(BigInteger.ONE) );
    }

    @JRubyMethod(name="odd?")
    public RubyBoolean odd_p(final ThreadContext context) {
        return context.runtime.newBoolean( value.testBit(0) );
    }

    @JRubyMethod(name={"cmp", "<=>"})
    public IRubyObject cmp(final ThreadContext context, IRubyObject other) {
        return context.runtime.newFixnum( value.compareTo( asBigInteger(other) ) );
    }

    @JRubyMethod(name="ucmp")
    public IRubyObject ucmp(final ThreadContext context, IRubyObject other) {
        return context.runtime.newFixnum( value.abs().compareTo( asBigInteger(other).abs() ) );
    }

    @JRubyMethod(name={"eql?", "==", "==="})
    public RubyBoolean eql_p(final ThreadContext context, IRubyObject other) {
        return context.runtime.newBoolean( value.equals( asBigInteger(other) ) );
    }

    @JRubyMethod(name="sqr")
    public BN sqr(final ThreadContext context) {
        // TODO: check whether mult n * n is faster
        return newBN(context.runtime, value.pow(2));
    }

    @JRubyMethod(name="~")
    public BN not(final ThreadContext context) {
        return newBN(context.runtime, value.not());
    }

    @JRubyMethod(name="+")
    public BN add(final ThreadContext context, IRubyObject other) {
        return newBN(context.runtime, value.add(asBigInteger(other)));
    }

    @JRubyMethod(name="-")
    public BN sub(final ThreadContext context, IRubyObject other) {
        return newBN(context.runtime, value.subtract(asBigInteger(other)));
    }

    @JRubyMethod(name="*")
    public BN mul(final ThreadContext context, IRubyObject other) {
        return newBN(context.runtime, value.multiply(asBigInteger(other)));
    }

    @JRubyMethod(name="%")
    public BN mod(final ThreadContext context, IRubyObject other) {
        try {
            return newBN(context.runtime, value.mod(asBigInteger(other)));
        }
        catch (ArithmeticException e) {
            throw context.runtime.newZeroDivisionError();
        }
    }

    @JRubyMethod(name="/")
    public IRubyObject div(final ThreadContext context, IRubyObject other) {
        final Ruby runtime = context.runtime;
        try {
            BigInteger[] result = value.divideAndRemainder(asBigInteger(other));
            return runtime.newArray(newBN(runtime, result[0]), newBN(runtime, result[1]));
        }
        catch (ArithmeticException e) {
            throw runtime.newZeroDivisionError();
        }
    }

    @JRubyMethod(name="&")
    public BN and(final ThreadContext context, IRubyObject other) {
        return newBN(context.runtime, value.and(asBigInteger(other)));
    }

    @JRubyMethod(name="|")
    public BN or(final ThreadContext context, IRubyObject other) {
        return newBN(context.runtime, value.or(asBigInteger(other)));
    }

    @JRubyMethod(name="^")
    public BN xor(final ThreadContext context, IRubyObject other) {
        return newBN(context.runtime, value.xor(asBigInteger(other)));
    }

    @JRubyMethod(name="**")
    public BN exp(final ThreadContext context, IRubyObject other) {
        // somewhat strangely, BigInteger takes int rather than BigInteger
        // as the argument to pow.  so we'll have to narrow the value, and
        // raise an exception if data would be lost. (on the other hand, an
        // exponent even approaching Integer.MAX_VALUE would be silly big, and
        // the value would take a very, very long time to calculate.)
        // we'll check for values < 0 (illegal) while we're at it
        int exp = -1;

        if ( other instanceof RubyInteger ) {
            long val = ((RubyInteger) other).getLongValue();
            if ( val >= 0 && val <= Integer.MAX_VALUE ) {
                exp = (int) val;
            }
            else if ( other instanceof RubyBignum ) { // inherently too big
                throw newBNError(context.runtime, "invalid exponent");
            }
        }

        if ( exp == -1 ) {
            if ( ! (other instanceof BN) ) {
                throw context.runtime.newTypeError("Cannot convert into " + other.getMetaClass().getName());
            }
            BigInteger val = ((BN) other).value;
            if (val.compareTo(BigInteger.ZERO) < 0 || val.compareTo(MAX_INT) > 0) {
                throw newBNError(context.runtime, "invalid exponent");
            }
            exp = val.intValue();
        }

        try {
            return newBN(context.runtime, value.pow(exp));
        }
        catch (ArithmeticException e) {
            // shouldn't happen, we've already checked for < 0
            throw newBNError(context.runtime, "invalid exponent");
        }
    }

    @JRubyMethod(name="gcd")
    public BN gcd(final ThreadContext context, IRubyObject other) {
        return newBN(context.runtime, value.gcd(asBigInteger(other)));
    }

    @JRubyMethod(name="mod_sqr")
    public BN mod_sqr(final ThreadContext context, IRubyObject other) {
        try {
            return newBN(context.runtime, value.modPow(TWO, asBigInteger(other)));
        }
        catch (ArithmeticException e) {
            throw context.runtime.newZeroDivisionError();
        }
    }

    @JRubyMethod(name="mod_inverse")
    public BN mod_inverse(final ThreadContext context, IRubyObject other) {
        try {
            return newBN(context.runtime, value.modInverse(asBigInteger(other)));
        }
        catch (ArithmeticException e) {
            throw context.runtime.newZeroDivisionError();
        }
    }

    @JRubyMethod(name="mod_add")
    public BN mod_add(final ThreadContext context, IRubyObject other, IRubyObject mod) {
        try {
            return newBN(context.runtime, value.add(asBigInteger(other)).mod(asBigInteger(mod)));
        }
        catch (ArithmeticException e) {
            throw context.runtime.newZeroDivisionError();
        }
    }

    @JRubyMethod(name="mod_sub")
    public BN mod_sub(final ThreadContext context, IRubyObject other, IRubyObject mod) {
        try {
            return newBN(context.runtime, value.subtract(asBigInteger(other)).mod(asBigInteger(mod)));
        }
        catch (ArithmeticException e) {
            throw context.runtime.newZeroDivisionError();
        }
    }

    @JRubyMethod(name="mod_mul")
    public BN mod_mul(final ThreadContext context, IRubyObject other, IRubyObject mod) {
        try {
            return newBN(context.runtime, value.multiply(asBigInteger(other)).mod(asBigInteger(mod)));
        }
        catch (ArithmeticException e) {
            throw context.runtime.newZeroDivisionError();
        }
    }

    @JRubyMethod(name="mod_exp")
    public BN mod_exp(final ThreadContext context, IRubyObject other, IRubyObject mod) {
        try {
            return newBN(context.runtime, value.modPow(asBigInteger(other), asBigInteger(mod)));
        }
        catch (ArithmeticException e) {
            throw context.runtime.newZeroDivisionError();
        }
    }

    @JRubyMethod(name="set_bit!")
    public synchronized IRubyObject set_bit(IRubyObject n) {
        // evil mutable BN
        int pos = RubyNumeric.num2int(n);
        BigInteger oldValue = this.value;
        // FIXME? in MRI/OSSL-BIGNUM, the original sign of a BN is remembered, so if
        // you set the value of an (originally) negative number to zero (through some
        // combination of clear_bit! and/or mask_bits! calls), and later call set_bit!,
        // the resulting value will be negative.  this seems unintuitive and, frankly,
        // wrong, not to mention expensive to carry the extra sign field.
        // I'm not duplicating this behavior here at this time. -BD
        try {
            if (oldValue.signum() >= 0) {
                this.value = oldValue.setBit(pos);
            } else {
                this.value = oldValue.abs().setBit(pos).negate();
            }
        } catch (ArithmeticException e) {
            throw newBNError(getRuntime(), "invalid pos");
        }
        return this;
    }

    @JRubyMethod(name="clear_bit!")
    public synchronized IRubyObject clear_bit(IRubyObject n) {
        // evil mutable BN
        int pos = RubyNumeric.num2int(n);
        BigInteger oldValue = this.value;
        try {
            if (oldValue.signum() >= 0) {
                this.value = oldValue.clearBit(pos);
            } else {
                this.value = oldValue.abs().clearBit(pos).negate();
            }
        } catch (ArithmeticException e) {
            throw newBNError(getRuntime(), "invalid pos");
        }
        return this;
    }

    /**
     * Truncates value to n bits
     */
    @JRubyMethod(name="mask_bits!")
    public synchronized IRubyObject mask_bits(IRubyObject n) {
        // evil mutable BN

        int pos = RubyNumeric.num2int(n);
        if (pos < 0) throw newBNError(getRuntime(), "invalid pos");

        BigInteger oldValue = this.value;

        // TODO: cache 2 ** n values?
        if (oldValue.signum() >= 0) {
            if (oldValue.bitLength() < pos) throw newBNError(getRuntime(), "invalid pos");
            this.value = oldValue.mod(TWO.pow(pos));
        } else {
            BigInteger absValue = oldValue.abs();
            if (absValue.bitLength() < pos) throw newBNError(getRuntime(), "invalid pos");
            this.value = absValue.mod(TWO.pow(pos)).negate();
        }

        return this;
    }

    @JRubyMethod(name="bit_set?")
    public RubyBoolean bit_set_p(final ThreadContext context, IRubyObject n) {
        int pos = RubyNumeric.num2int(n);
        BigInteger val = this.value;
        try {
            if (val.signum() >= 0) {
                return context.runtime.newBoolean(val.testBit(pos));
            }
            return context.runtime.newBoolean(val.abs().testBit(pos));
        }
        catch (ArithmeticException e) {
            throw newBNError(context.runtime, "invalid pos");
        }
    }

    @JRubyMethod(name="<<")
    public BN lshift(final ThreadContext context, IRubyObject n) {
        int nbits = RubyNumeric.num2int(n);
        BigInteger val = this.value;
        if (val.signum() >= 0) {
            return newBN(context.runtime, val.shiftLeft(nbits));
        }
        return newBN(context.runtime, val.abs().shiftLeft(nbits).negate());
    }

    @JRubyMethod(name=">>")
    public BN rshift(final ThreadContext context, IRubyObject n) {
        int nbits = RubyNumeric.num2int(n);
        BigInteger val = this.value;
        if (val.signum() >= 0) {
            return newBN(context.runtime, val.shiftRight(nbits));
        }
        return newBN(context.runtime, val.abs().shiftRight(nbits).negate());
    }

    @JRubyMethod(name="num_bits")
    public RubyFixnum num_bits(final ThreadContext context) {
        return context.runtime.newFixnum( this.value.abs().bitLength() );
    }

    @JRubyMethod(name="num_bytes")
    public RubyFixnum num_bytes(final ThreadContext context) {
        return context.runtime.newFixnum( (this.value.abs().bitLength() + 7) / 8 );
    }

    @JRubyMethod(name="num_bits_set")
    public RubyFixnum num_bits_set(final ThreadContext context) {
        return context.runtime.newFixnum( this.value.abs().bitCount() );
    }

    // note that there is a bug in the MRI version, in argument handling,
    // so apparently no one ever calls this...
    @JRubyMethod(name = "prime?", rest = true)
    public IRubyObject prime_p(IRubyObject[] args) {
        final Ruby runtime = getRuntime();
        int argc = Arity.checkArgumentCount(runtime, args, 0, 1);

        // negative numbers are always considered non-prime
        if (this.value.signum() < 0) return runtime.getFalse();

        int certainty = argc == 0 ? DEFAULT_CERTAINTY : RubyNumeric.fix2int(args[0]);

        // BigInteger#isProbablePrime will actually limit checks to a maximum of 50,
        // depending on bit count.
        return runtime.newBoolean(this.value.isProbablePrime(certainty));
    }

    // NOTE: BigInteger doesn't supply this, so right now this is
    // ... (essentially) the same as prime?
    @JRubyMethod(name = "prime_fasttest?", rest = true)
    public IRubyObject prime_fasttest_p(IRubyObject[] args) {
        final Ruby runtime = getRuntime();
        int argc = Arity.checkArgumentCount(runtime, args, 0, 2);

        // negative numbers are always considered non-prime
        if (this.value.signum() < 0) return runtime.getFalse();

        int certainty = argc == 0 ? DEFAULT_CERTAINTY : RubyNumeric.fix2int(args[0]);
        
        // BigInteger#isProbablePrime will actually limit checks to a maximum of 50,
        // depending on bit count.
        return runtime.newBoolean(this.value.isProbablePrime(certainty));
    }

    @JRubyMethod(name = "generate_prime", meta = true, rest = true)
    public static IRubyObject generate_prime(IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = recv.getRuntime();
        int argc = Arity.checkArgumentCount(runtime, args, 1, 4);
        int bits = RubyNumeric.num2int(args[0]);
        boolean safe = argc > 1 ? args[1] != runtime.getFalse() : true;
        BigInteger add = argc > 2 ? asBigInteger(args[2]) : null;
        BigInteger rem = argc > 3 ? asBigInteger(args[3]) : null;
        if (bits < 3) {
            if (safe) throw runtime.newArgumentError("bits < 3");
            if (bits < 2) throw runtime.newArgumentError("bits < 2");
        }
        return newBN(runtime, generatePrime(bits, safe, add, rem));
    }

    public static BigInteger generatePrime(int bits, boolean safe, BigInteger add, BigInteger rem) {
        // From OpenSSL man page BN_generate_prime(3):
        //
        // "If add is not NULL, the prime will fulfill the condition p % add == rem
        // (p % add == 1 if rem == NULL) in order to suit a given generator."
        //
        // "If safe is true, it will be a safe prime (i.e. a prime p so that
        // (p-1)/2 is also prime)."
        //
        // see [ossl]/crypto/bn/bn_prime.c #BN_generate_prime_ex
        //

        if (add != null && rem == null) {
            rem = BigInteger.ONE;
        }

        // borrowing technique from org.bouncycastle.crypto.generators.DHParametersHelper
        // (unfortunately the code has package visibility), wherein for safe primes,
        // we'll use the lowest useful certainty (2) for generation of q, then if
        // p ( = 2q + 1) is prime to our required certainty (100), we'll verify that q
        // is as well.
        //
        // for typical bit lengths ( >= 1024), this should speed things up by reducing
        // initial Miller-Rabin iterations from 2 to 1 for candidate values of q.
        //
        // it's still painfully slow...
        //
        BigInteger p, q;
        int qbits = bits - 1;
        SecureRandom secureRandom = getSecureRandom();
        do {
            if (safe) {
                do {
                    q = new BigInteger(qbits, 2, secureRandom);
                    p = q.shiftLeft(1).setBit(0);
                } while (!(p.isProbablePrime(DEFAULT_CERTAINTY) && q.isProbablePrime(DEFAULT_CERTAINTY)));
            } else {
                p = BigInteger.probablePrime(bits, secureRandom);
            }
        } while (add != null && !p.mod(add).equals(rem));
        return p;
    }

    public static BigInteger generatePrime(int bits, boolean safe) {
        return generatePrime(bits, safe, null, null);
    }

    @JRubyMethod(name = "rand", meta = true, rest = true)
    public static IRubyObject rand(IRubyObject recv, IRubyObject[] args) {
        return getRandomBN(recv.getRuntime(), args, getSecureRandom());
    }

    @JRubyMethod(name = "pseudo_rand", meta = true, rest = true)
    public static IRubyObject pseudo_rand(IRubyObject recv, IRubyObject[] args) {
        return getRandomBN(recv.getRuntime(), args, getRandom());
    }

    public static BN getRandomBN(Ruby runtime, IRubyObject[] args, Random random) {
        int argc = Arity.checkArgumentCount(runtime, args, 1, 3);
        int bits = RubyNumeric.num2int(args[0]);
        int top;
        boolean bottom;
        if (argc > 1) {
            top = RubyNumeric.fix2int(args[1]);
            bottom = argc == 3 ? args[2].isTrue() : false;
        } else {
            top = 0;
            bottom = false;
        }

        BigInteger value;
        try {
            value = getRandomBI(bits, top, bottom, random);
        } catch (IllegalArgumentException e) {
            throw runtime.newArgumentError(e.getMessage());
        }
        return newBN(runtime, value);
    }

    public static BigInteger getRandomBI(int bits, int top, boolean bottom, Random random) {
        // From OpenSSL man page BN_rand(3):
        //
        // "If top is -1, the most significant bit of the random number can be zero.
        // If top is 0, it is set to 1, and if top is 1, the two most significant bits
        // of the number will be set to 1, so that the product of two such random numbers
        // will always have 2*bits length."
        //
        // "If bottom is true, the number will be odd."
        //
        if (bits <= 0) {
            if (bits == 0) return BigInteger.ZERO;
            throw new IllegalArgumentException("Illegal bit length");
        }
        if (top < -1 || top > 1) {
            throw new IllegalArgumentException("Illegal top value");
        }

        // top/bottom handling adapted from OpenSSL's crypto/bn/bn_rand.c
        int bytes = (bits + 7) / 8;
        int bit = (bits - 1) % 8;
        int mask = 0xff << (bit + 1);

        byte[] buf;
        random.nextBytes(buf = new byte[bytes]);
        if (top >= 0) {
            if (top == 0) {
                buf[0] |= (1 << bit);
            } else {
                if (bit == 0) {
                    buf[0] = 1;
                    buf[1] |= 0x80;
                }
                else {
                    buf[0] |= (3 << (bit - 1));
                }
            }
        }
        buf[0] &= ~mask;
        if (bottom) {
            buf[bytes-1] |= 1;
        }

        // treating result as unsigned
        return new BigInteger(1, buf);
    }

    @JRubyMethod(name = "rand_range", meta = true)
    public static IRubyObject rand_range(IRubyObject recv, IRubyObject arg) {
        return randomValueInRange(recv.getRuntime(), asBigInteger(arg), getSecureRandom());
    }

    @JRubyMethod(name = "pseudo_rand_range", meta = true)
    public static IRubyObject pseudo_rand_range(IRubyObject recv, IRubyObject arg) {
        return randomValueInRange(recv.getRuntime(), asBigInteger(arg), getRandom());
    }

    private static BN randomValueInRange(Ruby runtime, BigInteger limit, Random random) {
        BigInteger value;
        try {
            value = randomIntegerInRange(limit, random);
        }
        catch (IllegalArgumentException e) {
            throw newBNError(runtime, e.getMessage());
        }
        return newInstance(runtime, value);
    }

    public static BigInteger randomIntegerInRange(BigInteger limit, Random random) {
        if (limit.signum() < 0) {
            throw new IllegalArgumentException("illegal range: " + limit);
        }
        int bits = limit.bitLength();
        BigInteger value;
        do {
            value = new BigInteger(bits, random);
        } while (value.compareTo(limit) >= 0);
        return value;
    }

    private static Random random;

    private static Random getRandom() {
        final Random rnd;
        if ( ( rnd = BN.random ) != null ) {
            return rnd;
        }
        return BN.random = new Random();
    }

    private static SecureRandom secureRandom;

    private static SecureRandom getSecureRandom() {
        final SecureRandom rnd;
        if ( ( rnd = BN.secureRandom ) != null ) {
            return rnd;
        }
        // NOTE: will use (default) Sun's even if BC provider is set
        return BN.secureRandom = new SecureRandom();
    }

    public static RaiseException newBNError(Ruby runtime, String message) {
        return new RaiseException(runtime, runtime.getModule("OpenSSL").getClass("BNError"), message, true);
    }

    public static BigInteger asBigInteger(final IRubyObject arg) {
        if ( arg.isNil() ) return null;

        if ( arg instanceof RubyInteger ) {
            return ((RubyInteger) arg).getBigIntegerValue();
        }

        if ( arg instanceof BN ) return ((BN) arg).value;

        throw arg.getRuntime().newTypeError("Cannot convert into OpenSSL::BN");
    }

    public static BigInteger asBigInteger(final BN arg) {
        return arg.isNil() ? null : arg.value;
    }

    @Deprecated
    public static BigInteger getBigInteger(final IRubyObject arg) {
        return asBigInteger(arg);
    }

    @Override
    public Object toJava(Class target) {
        if ( target.isAssignableFrom(BigInteger.class) || target == Number.class ) return value;
        if ( target == Long.class || target == Long.TYPE ) return value.longValue();
        if ( target == Integer.class || target == Integer.TYPE ) return value.intValue();
        if ( target == Double.class || target == Double.TYPE ) return value.doubleValue();
        if ( target == Float.class || target == Float.TYPE ) return value.floatValue();
        return super.toJava(target);
    }

}
