package java.security;

import java.util.Random;

import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.prng.RandomGenerator;
import org.bouncycastle.crypto.prng.DigestRandomGenerator;

/**
 * An implementation of SecureRandom specifically for the light-weight API, JDK
 * 1.0, and the J2ME. Random generation is based on the traditional SHA1 with
 * counter. Calling setSeed will always increase the entropy of the hash.
 * <p>
 * <b>Do not use this class without calling setSeed at least once</b>! There
 * are some example seed generators in the org.bouncycastle.prng package.
 */
public class SecureRandom extends java.util.Random
{
    // Note: all objects of this class should be deriving their random data from
    // a single generator appropriate to the digest being used.
    private static final RandomGenerator sha1Generator = new DigestRandomGenerator(new SHA1Digest());
    private static final RandomGenerator sha256Generator = new DigestRandomGenerator(new SHA256Digest());

    protected RandomGenerator             generator;

    // public constructors
    public SecureRandom()
    {
        this(sha1Generator);
        setSeed(System.currentTimeMillis());
    }

    public SecureRandom(byte[] inSeed)
    {
        this(sha1Generator);
        setSeed(inSeed);
    }

    protected SecureRandom(
        RandomGenerator generator)
    {
        super(0);
        this.generator = generator;
    }

    // protected constructors
    // protected SecureRandom(SecureRandomSpi srs, Provider provider);

    // public class methods
    public static SecureRandom getInstance(String algorithm)
    {
        if (algorithm.equals("SHA1PRNG"))
        {
            return new SecureRandom(sha1Generator);
        }
        if (algorithm.equals("SHA256PRNG"))
        {
            return new SecureRandom(sha256Generator);
        }
        return new SecureRandom();    // follow old behaviour
    }

    public static SecureRandom getInstance(String algorithm, String provider)
    {
        return getInstance(algorithm);
    }

    public String getAlgorithm()
    {
        return "unknown";
    }

    public static byte[] getSeed(int numBytes)
    {
        byte[] rv = new byte[numBytes];

        sha1Generator.addSeedMaterial(System.currentTimeMillis());
        sha1Generator.nextBytes(rv);

        return rv;
    }

    // public instance methods
    public byte[] generateSeed(int numBytes)
    {
        byte[] rv = new byte[numBytes];

        nextBytes(rv);

        return rv;
    }

    // public final Provider getProvider();
    public void setSeed(byte[] inSeed)
    {
        generator.addSeedMaterial(inSeed);
    }

    // public methods overriding random
    public void nextBytes(byte[] bytes)
    {
        generator.nextBytes(bytes);
    }

    public void setSeed(long rSeed)
    {
        if (rSeed != 0)    // to avoid problems with Random calling setSeed in construction
        {
            generator.addSeedMaterial(rSeed);
        }
    }

    public int nextInt()
    {
        byte[] intBytes = new byte[4];

        nextBytes(intBytes);

        int result = 0;

        for (int i = 0; i < 4; i++)
        {
            result = (result << 8) + (intBytes[i] & 0xff);
        }

        return result;
    }

    protected final int next(int numBits)
    {
        int size = (numBits + 7) / 8;
        byte[] bytes = new byte[size];

        nextBytes(bytes);

        int result = 0;

        for (int i = 0; i < size; i++)
        {
            result = (result << 8) + (bytes[i] & 0xff);
        }

        return result & ((1 << numBits) - 1);
    }
}
