package erjang.m.crypto;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.logging.Level;

import erjang.BIF;
import erjang.EBinary;
import erjang.ENative;
import erjang.EObject;
import erjang.ERT;

public class Native extends ENative {

	int get_int32(EBinary bin) {
		return bin.intBitsAt(0, 32);
	}
	
	static SecureRandom rand;
	
	static {
		try {
			rand = SecureRandom.getInstance("SHA1PRNG");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
	}
	
	@BIF
	public static EObject sha(EObject data) 
	{
		EBinary bin = data.testBinary();
		if (bin == null) 
			throw ERT.badarg(data);
		
		MessageDigest sha;
		try {
			sha = MessageDigest.getInstance("SHA");
		} catch (NoSuchAlgorithmException e) {
			throw ERT.badarg(data);
		}

		sha.digest(bin.getByteArray());
		
		byte[] res = sha.digest();
		
		return EBinary.make(res);
	}
	
	@BIF
	public static EObject md5(EObject data) 
	{
		EBinary bin = data.testBinary();
		if (bin == null) 
			throw ERT.badarg(data);
		
		MessageDigest md5;
		try {
			md5 = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			throw ERT.badarg(data);
		}

		md5.digest(bin.getByteArray());
		
		byte[] res = md5.digest();
		
		return EBinary.make(res);
	}
	
	@BIF
	public static EObject rand_uniform_nif(EObject from, EObject to)
	{
		EBinary fb = from.testBinary();
		EBinary tb = to.testBinary();
		
		if (fb == null || tb == null) {
			throw ERT.badarg(from, to);
		}
		
		BigInteger fi = mp2big(fb);
		BigInteger ti = mp2big(tb);
		
		if (log.isLoggable(Level.FINE)) log.fine("rand_uniform ("+fb+", "+tb+")");
		
		BigInteger interval = ti.subtract(fi).subtract(BigInteger.ONE);
		
		BigInteger base_value = new BigInteger(interval.bitLength(), rand);
		
		BigInteger result;
		while (interval.compareTo(base_value) == 1) {
			base_value = new BigInteger(interval.bitLength(), rand);
		}

		result = fi.add(base_value);
		
		if (log.isLoggable(Level.FINE)) log.fine("rand_uniform ("+fi+", "+ti+") -> "+result);

		EBinary res = big2mp(result);
		return res;
	}

	private static EBinary big2mp(BigInteger result) {
		byte[] bytes = result.toByteArray();
		
		ByteBuffer out = ByteBuffer.allocate(4 + bytes.length);
		out.putInt(bytes.length);
		out.put(bytes);
		out.position(0);
		
		EBinary res = EBinary.make(out);
		return res;
	}

	private static BigInteger mp2big(EBinary bin) {
		ByteBuffer bb = bin.toByteBuffer();
		int len = bb.getInt();
		byte[] data = new byte[len];
		bb.get(data);
		return new BigInteger(data);
	}
	
	
}

@@ -333,16 +333,38 @@
 ang.ERT;
+%0Aimport erjang.ESmall;
 %0A%0Apublic
@@ -1395,32 +1395,312 @@
 es);%0A%09%7D%0A%09%0A%09@BIF%0A
+%09public static EBinary rand_bytes(EObject howmany)%0A%09%7B%0A%09    ESmall sm = howmany.testSmall();%0A%09    if (sm == null %7C%7C sm.value %3C 0) throw ERT.badarg(howmany);%0A%09    byte%5B%5D bytes = new byte%5Bsm.value%5D;%0A%09    %0A%09    rand.nextBytes(bytes);%0A%09    %0A%09    return EBinary.make(bytes);%0A%09%7D%0A%09%0A%09@BIF%0A
 %09public static E

Add crypto:rand_bytes/1 BIF