package net.i2p.util;

/*
 * free (adj.): unencumbered; not under the control of others
 * Written by jrandom in 2003 and released into the public domain 
 * with no warranty of any kind, either expressed or implied.  
 * It probably won't make your computer catch on fire, or eat 
 * your children, but it might.  Use at your own risk.
 *
 */

import gnu.crypto.prng.AsyncFortunaStandalone;

import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random;

import net.i2p.I2PAppContext;
import net.i2p.crypto.EntropyHarvester;
import net.i2p.data.DataHelper;

/**
 * Wrapper around GNU-Crypto's Fortuna PRNG.  This seeds from /dev/urandom and
 * ./prngseed.rnd on startup (if they exist), writing a new seed to ./prngseed.rnd
 * on an explicit call to saveSeed().
 *
 */
public class FortunaRandomSource extends RandomSource implements EntropyHarvester {
    private final AsyncFortunaStandalone _fortuna;
    private double _nextGaussian;
    private boolean _haveNextGaussian;

    /**
     *  May block up to 10 seconds or forever
     */
    public FortunaRandomSource(I2PAppContext context) {
        super(context);
        _fortuna = new AsyncFortunaStandalone(context);
        byte seed[] = new byte[1024];
        // may block for 10 seconds
        if (initSeed(seed)) {
            _fortuna.seed(seed);
        } else {
            // may block forever
            //SecureRandom sr = new SecureRandom();
            // SecureRandom already failed in initSeed(), so try Random
            Random sr = new Random();
            sr.nextBytes(seed);
            _fortuna.seed(seed);
        }
        _fortuna.startup();
        // kickstart it
        _fortuna.nextBytes(seed);
        _haveNextGaussian = false;
    }
    
    /**
     *  Note - methods may hang or NPE or throw IllegalStateExceptions after this
     *  @since 0.8.8
     */
    public void shutdown() {
        synchronized(_fortuna) {
            _fortuna.shutdown();
        }
    }

    @Override
    public void setSeed(byte buf[]) {
        synchronized(_fortuna) {
            _fortuna.addRandomBytes(buf);
        }
    }

    /**
     * According to the java docs (http://java.sun.com/j2se/1.4.1/docs/api/java/util/Random.html#nextInt(int))
     * nextInt(n) should return a number between 0 and n (including 0 and excluding n).  However, their pseudocode,
     * as well as sun's, kaffe's, and classpath's implementation INCLUDES NEGATIVE VALUES.
     * Ok, so we're going to have it return between 0 and n (including 0, excluding n), since 
     * thats what it has been used for.
     *
     * Unlike Java's Random, which throws IAE, this returns 0 if n is 0.
     *
     * @param n non-negative
     * @throws IllegalArgumentException if n is negative
     */
    @Override
    public int nextInt(int n) {
        if (n == 1 || n == 0) return 0;
        int rv = signedNextInt(n);
        if (rv < 0) 
            rv = 0 - rv;
        rv %= n;
        return rv;
    }

    @Override
    public int nextInt() {
        return signedNextInt() & 0x7fffffff;
    }

    /**
     *  @return all possible int values, positive and negative
     *  @since 0.9.54
     */
    public int signedNextInt() {
        byte[] b = new byte[4];
        nextBytes(b);
        return (int) DataHelper.fromLong(b, 0, 4);
    }

    /**
     * Implementation from Sun's java.util.Random javadocs
     */
    private int signedNextInt(int n) {
        if (n<=0)
          throw new IllegalArgumentException("n must be positive");

        boolean isPowerOfTwo = (n & (n - 1)) == 0;
        // get at least 4 extra bits if possible for better
        // distribution after the %
        // No extra needed if power of two.
        int numBits;
        if (n > 0x1000000 || (n > 0x100000 && !isPowerOfTwo))
            numBits = 31;
        else if (n > 0x10000 || (n > 0x1000 && !isPowerOfTwo))
            numBits = 24;
        else if (n > 0x100 || (n > 0x10 && !isPowerOfTwo))
            numBits = 16;
        else
            numBits = 8;
        int rv;
        synchronized(_fortuna) {
            rv = nextBits(numBits);
        }
        return rv % n;
    }

    /**
     * Like the modified nextInt, nextLong(n) returns a random number from 0 through n,
     * including 0, excluding n.
     *
     * Unlike Java's Random, which throws IAE, this returns 0 if n is 0.
     *
     * @param n non-negative
     * @throws IllegalArgumentException if n is negative
     */
    @Override
    public long nextLong(long n) {
        if (n == 1 || n == 0) return 0;
        long rv = nextLong();
        if (rv < 0) 
            rv = 0 - rv;
        rv %= n;
        return rv;
    }
    
    /**
     *  @return all possible long values, positive and negative
     */
    @Override
    public long nextLong() {
        byte[] b = new byte[8];
        nextBytes(b);
        return DataHelper.fromLong8(b, 0);
    }

    @Override
    public boolean nextBoolean() { 
        byte val;
        synchronized(_fortuna) {
            val = _fortuna.nextByte();
        }
        return ((val & 0x01) != 0);
    }

    @Override
    public void nextBytes(byte buf[]) { 
        synchronized(_fortuna) {
            _fortuna.nextBytes(buf);
        }
    }

    /**
     * Not part of java.util.SecureRandom, but added for efficiency, since Fortuna supports it.
     *
     * @since 0.8.12
     */
    @Override
    public void nextBytes(byte buf[], int offset, int length) {
        synchronized(_fortuna) {
            _fortuna.nextBytes(buf, offset, length);
        }
    }

    /**
     * Not part of java.util.SecureRandom, but added for efficiency, since Fortuna supports it.
     *
     * @since 0.9.24
     */
    public byte nextByte() { 
        synchronized(_fortuna) {
            return _fortuna.nextByte();
        }
    }

    /**
     * Implementation from sun's java.util.Random javadocs 
     */
    @Override
    public double nextDouble() { 
        long d;
        synchronized(_fortuna) {
            d = ((long)nextBits(26) << 27) + nextBits(27);
        }
        return d / (double)(1L << 53);
    }

    /**
     * Implementation from sun's java.util.Random javadocs 
     */
    @Override
    public float nextFloat() { 
        int d;
        synchronized(_fortuna) {
            d = nextBits(24);
        }
        return d / ((float)(1 << 24));
    }

    /**
     * Implementation from sun's java.util.Random javadocs 
     */
    @Override
    public double nextGaussian() { 
        synchronized (this) {
            if (_haveNextGaussian) {
                _haveNextGaussian = false;
                return _nextGaussian;
            }
            double v1, v2, s;
            do { 
                v1 = 2 * nextDouble() - 1;   // between -1.0 and 1.0
                v2 = 2 * nextDouble() - 1;   // between -1.0 and 1.0
                s = v1 * v1 + v2 * v2;
            } while (s >= 1 || s == 0);
            double multiplier = Math.sqrt(-2 * Math.log(s)/s);
            _nextGaussian = v2 * multiplier;
            _haveNextGaussian = true;
            return v1 * multiplier;
        }
    }

    /**
     * Pull the next numBits of random data off the fortuna instance (returning 0
     * through 2^numBits-1
     *
     * Caller must synchronize!
     */
    protected int nextBits(int numBits) {
        long rv = 0;
        int bytes = (numBits + 7) / 8;
        for (int i = 0; i < bytes; i++)
            rv += ((_fortuna.nextByte() & 0xFF) << i*8);
        //rv >>>= (64-numBits);
        if (rv < 0)
            rv = 0 - rv;
        int off = 8*bytes - numBits;
        rv >>>= off;
        return (int)rv;
    }
    
    /** reseed the fortuna */
    @Override
    public void feedEntropy(String source, long data, int bitoffset, int bits) {
        synchronized(_fortuna) {
            _fortuna.addRandomByte((byte)(data & 0xFF));
        }
    }
 
    /** reseed the fortuna */
    @Override
    public void feedEntropy(String source, byte[] data, int offset, int len) {
        try {
            synchronized(_fortuna) {
                _fortuna.addRandomBytes(data, offset, len);
            }
        } catch (RuntimeException e) {
            // AIOOBE seen, root cause unknown, ticket #1576
            Log log = _context.logManager().getLog(FortunaRandomSource.class);
            log.warn("feedEntropy()", e);
        }
    }
    
    /**
     *  Outputs to stdout for dieharder:
     *  <code>
     *  java -cp build/i2p.jar net.i2p.util.FortunaRandomSource | dieharder -a -g 200
     *  </code>
     */
    public static void main(String args[]) {
        try {
            java.util.Properties props = new java.util.Properties();
            props.setProperty("prng.buffers", "12");
            I2PAppContext ctx = new I2PAppContext(props);
            RandomSource rand = ctx.random();
            byte[] buf = new byte[65536];
            while (true) {
                rand.nextBytes(buf);
                System.out.write(buf);
            }
        } catch (IOException e) { e.printStackTrace(); }
    }
}
