package cx.ath.hoenicke.gwt.md5.client;

public class MD5 {
	private static final int[] s = {
		7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,
		5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,
		4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,
		6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21
	};
	private static native int toUInt32(double d) /*-{
	    return ~~d;
	}-*/;
	private static final int[] k  =  new int[64];
	static {
		for (int i = 0; i < 64; i++) 
			k[i] = toUInt32(Math.abs(Math.sin(i+1))*4294967296.0);
	}
	private int[] blk = new int[16];
	private int[] digest = new int[] {
		0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476 
	};
	private int len = 0;
	
	private static void updateBlock(int[] digin, int[] blk, int[] digout) {
		int A = digin[0], B = digin[1], C = digin[2], D = digin[3];
		/* Round 1 */
		int i = 0;
		for (; i < 16; i++) {
			A += (((C ^ D) & B) ^ D) + blk[i] + k[i];
			int t = B + ((A << s[i]) | (A >>> 32-s[i]));
			A = D; D = C; C = B; B = t;
		}
		/* Round 2 */
		for (; i < 32; i++) {
			A += (((B ^ C) & D) ^ C) + blk[(5*i+1)&15] + k[i];
			int t = B + ((A << s[i]) | (A >>> 32-s[i]));
			A = D; D = C; C = B; B = t;
		}
		/* Round 3 */
		for (;i < 48; i++) {
			A += (B ^ C ^ D) + blk[(3*i+5)&15] + k[i];
			int t = B + ((A << s[i]) | (A >>> 32-s[i]));
			A = D; D = C; C = B; B = t;
		}
		/* Round 4 */
		for (; i < 64; i++) {
			A += (C ^ (B | ~D)) + blk[(7*i)&15] + k[i];
			int t = B + ((A << s[i]) | (A >>> 32-s[i]));
			A = D; D = C; C = B; B = t;
		}
		digout[0] = ~~(digin[0] + A); 
		digout[1] = ~~(digin[1] + B); 
		digout[2] = ~~(digin[2] + C); 
		digout[3] = ~~(digin[3] + D); 
	}
	
	public void update(int b) {
		int value = b << (8*(len & 3));
		blk[(len >> 2) & 15] |= value;
		if ((++len & 63) == 0) {
			updateBlock(digest, blk, digest);
			blk = new int[16];
		}
	}
	public void update(byte[] bytes) {
		for (int i = 0; i < bytes.length; i++)
			update(bytes[i] & 0xff);
	}
	
	public void updateUTF8(char ch) {
		/* Encode utf-8 (but encode 0 as 0) */
		if (ch < 128) {
			update(ch);
		} else if (ch < 2048) {
			update(0xc0 + (ch >> 6));
			update(0x80 + (ch & 0x3f));
		} else {
			update(0xe0 + (ch >> 12));
			update(0x80 + ((ch >> 6) & 0x3f)); 
		    update(0x80 + (ch & 0x3f));
		}
	}
	public void update(String s) {
		for (int i = 0; i < s.length(); i++)
			updateUTF8(s.charAt(i));
	}
	
	public void finish() {
		int total = 8*len;
		update(0x80);
		if ((len & 63) > 64-8) {
			updateBlock(digest, blk, digest);
			blk = new int[16];
		}
		blk[14] = total;
		updateBlock(digest, blk, digest);
	}
	
	public String hash() {
		return hex(digest[0])+hex(digest[1])+hex(digest[2])+hex(digest[3]);
	}
	
	public static String md5(String s) {
		MD5 hash = new MD5();
		hash.update(s);
		hash.finish();
		return hash.hash();
	}
	
	private static String hex(int value) {
		String result = "";
		for (int i = 0; i < 4; i++) {
			result += Integer.toHexString((value & 0xf0) >> 4) 
			        + Integer.toHexString(value & 0xf);
			value >>= 8;
		}
		return result;
	}
	
	public static String otp(String passwd, int seq, String seed) {
		int[] initdig = new MD5().digest;
		MD5 hash = new MD5();
		hash.update(seed.toLowerCase());
		hash.update(passwd);
		hash.finish();

		int[] freshblk = new int[16];
		freshblk[2] = 0x80; 
		freshblk[14] = 64;
		int[] digest = hash.digest;
		
		for (int i = 0; i < seq; i++) {
			freshblk[0] = digest[0] ^ digest[2];
			freshblk[1] = digest[1] ^ digest[3];
			updateBlock(initdig, freshblk, digest);
		}
		return hex(digest[0]^digest[2]) + hex(digest[1]^digest[3]);
	}

	public static String otpjoho(String passwd, int seq, String seed) {
		int[] initdig = new MD5().digest;
		MD5 hash = new MD5();
		hash.update(passwd);
		hash.update(seed);
		hash.finish();
		
		hash.blk = new int[16];
		hash.blk[0] = hash.digest[0];
		hash.blk[1] = hash.digest[1];
		hash.blk[2] = hash.digest[2];
		hash.blk[3] = hash.digest[3];
		hash.len = 16;
		hash.update(seed);
		int total = 8*hash.len;
		hash.update(0x80);
		assert (hash.len <= 56);
		hash.blk[14] = total;

		for (int i = 0; i < seq; i++) {
			updateBlock(initdig, hash.blk, hash.blk);
		}
		return hex(hash.blk[0])+hex(hash.blk[1])+hex(hash.blk[2])+hex(hash.blk[3]);
	}
}

