package railo.runtime.cache.eh;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import railo.commons.io.CharsetUtil;


/**
 * 
 */
public final class MD5
{
    /**
     * return md5 from string as string
     * @param str plain string to get md5 from
     * @return md5 from string
     * @throws IOException
     */
    public static String getDigestAsString(String str) throws IOException {
       return  new MD5(str).getDigest();
    }

	
	
	private static String stringify(byte digest[]) {
		char[] chars = new char[2*digest.length];
		int h;
		int l;
		int count=0;
		for (int i = 0 ; i < digest.length; i++) {
		    h = (digest[i] & 0xf0) >> 4 ;
		    l = (digest[i] & 0x0f) ;
		    chars[count++]= ((char)((h>9) ? 'a'+h-10 : '0'+h));
		    chars[count++]= ((char)((l>9) ? 'a'+l-10 : '0'+l));
		}
		return new String(chars) ;
	}

 private final int F(int x, int y, int z)
 {
     return x & y | ~x & z;
 }

 private final int G(int x, int y, int z)
 {
     return x & z | y & ~z;
 }

 private final int H(int x, int y, int z)
 {
     return x ^ y ^ z;
 }

 private final int I(int x, int y, int z)
 {
     return y ^ (x | ~z);
 }

 private final int rotate_left(int x, int n)
 {
     return x << n | x >>> 32 - n;
 }

 private final int FF(int a, int b, int c, int d, int x, int s, int ac)
 {
     a += F(b, c, d) + x + ac;
     a = rotate_left(a, s);
     a += b;
     return a;
 }

 private final int GG(int a, int b, int c, int d, int x, int s, int ac)
 {
     a += G(b, c, d) + x + ac;
     a = rotate_left(a, s);
     a += b;
     return a;
 }

 private final int HH(int a, int b, int c, int d, int x, int s, int ac)
 {
     a += H(b, c, d) + x + ac;
     a = rotate_left(a, s);
     a += b;
     return a;
 }

 private final int II(int a, int b, int c, int d, int x, int s, int ac)
 {
     a += I(b, c, d) + x + ac;
     a = rotate_left(a, s);
     a += b;
     return a;
 }

 private final void decode(int output[], byte input[], int off, int len)
 {
     int i = 0;
     for(int j = 0; j < len; j += 4)
     {
         output[i] = input[off + j] & 0xff | (input[off + j + 1] & 0xff) << 8 | (input[off + j + 2] & 0xff) << 16 | (input[off + j + 3] & 0xff) << 24;
         i++;
     }

 }

 private final void transform(byte block[], int offset)
 {
     int a = state[0];
     int b = state[1];
     int c = state[2];
     int d = state[3];
     int x[] = new int[16];

     
     decode(x, block, offset, 64);
     a = FF(a, b, c, d, x[0], 7, 0xd76aa478);
     d = FF(d, a, b, c, x[1], 12, 0xe8c7b756);
     c = FF(c, d, a, b, x[2], 17, 0x242070db);
     b = FF(b, c, d, a, x[3], 22, 0xc1bdceee);
     a = FF(a, b, c, d, x[4], 7, 0xf57c0faf);
     d = FF(d, a, b, c, x[5], 12, 0x4787c62a);
     c = FF(c, d, a, b, x[6], 17, 0xa8304613);
     b = FF(b, c, d, a, x[7], 22, 0xfd469501);
     a = FF(a, b, c, d, x[8], 7, 0x698098d8);
     d = FF(d, a, b, c, x[9], 12, 0x8b44f7af);
     c = FF(c, d, a, b, x[10], 17, -42063);
     b = FF(b, c, d, a, x[11], 22, 0x895cd7be);
     a = FF(a, b, c, d, x[12], 7, 0x6b901122);
     d = FF(d, a, b, c, x[13], 12, 0xfd987193);
     c = FF(c, d, a, b, x[14], 17, 0xa679438e);
     b = FF(b, c, d, a, x[15], 22, 0x49b40821);
     a = GG(a, b, c, d, x[1], 5, 0xf61e2562);
     d = GG(d, a, b, c, x[6], 9, 0xc040b340);
     c = GG(c, d, a, b, x[11], 14, 0x265e5a51);
     b = GG(b, c, d, a, x[0], 20, 0xe9b6c7aa);
     a = GG(a, b, c, d, x[5], 5, 0xd62f105d);
     d = GG(d, a, b, c, x[10], 9, 0x2441453);
     c = GG(c, d, a, b, x[15], 14, 0xd8a1e681);
     b = GG(b, c, d, a, x[4], 20, 0xe7d3fbc8);
     a = GG(a, b, c, d, x[9], 5, 0x21e1cde6);
     d = GG(d, a, b, c, x[14], 9, 0xc33707d6);
     c = GG(c, d, a, b, x[3], 14, 0xf4d50d87);
     b = GG(b, c, d, a, x[8], 20, 0x455a14ed);
     a = GG(a, b, c, d, x[13], 5, 0xa9e3e905);
     d = GG(d, a, b, c, x[2], 9, 0xfcefa3f8);
     c = GG(c, d, a, b, x[7], 14, 0x676f02d9);
     b = GG(b, c, d, a, x[12], 20, 0x8d2a4c8a);
     a = HH(a, b, c, d, x[5], 4, 0xfffa3942);
     d = HH(d, a, b, c, x[8], 11, 0x8771f681);
     c = HH(c, d, a, b, x[11], 16, 0x6d9d6122);
     b = HH(b, c, d, a, x[14], 23, 0xfde5380c);
     a = HH(a, b, c, d, x[1], 4, 0xa4beea44);
     d = HH(d, a, b, c, x[4], 11, 0x4bdecfa9);
     c = HH(c, d, a, b, x[7], 16, 0xf6bb4b60);
     b = HH(b, c, d, a, x[10], 23, 0xbebfbc70);
     a = HH(a, b, c, d, x[13], 4, 0x289b7ec6);
     d = HH(d, a, b, c, x[0], 11, 0xeaa127fa);
     c = HH(c, d, a, b, x[3], 16, 0xd4ef3085);
     b = HH(b, c, d, a, x[6], 23, 0x4881d05);
     a = HH(a, b, c, d, x[9], 4, 0xd9d4d039);
     d = HH(d, a, b, c, x[12], 11, 0xe6db99e5);
     c = HH(c, d, a, b, x[15], 16, 0x1fa27cf8);
     b = HH(b, c, d, a, x[2], 23, 0xc4ac5665);
     a = II(a, b, c, d, x[0], 6, 0xf4292244);
     d = II(d, a, b, c, x[7], 10, 0x432aff97);
     c = II(c, d, a, b, x[14], 15, 0xab9423a7);
     b = II(b, c, d, a, x[5], 21, 0xfc93a039);
     a = II(a, b, c, d, x[12], 6, 0x655b59c3);
     d = II(d, a, b, c, x[3], 10, 0x8f0ccc92);
     c = II(c, d, a, b, x[10], 15, 0xffeff47d);
     b = II(b, c, d, a, x[1], 21, 0x85845dd1);
     a = II(a, b, c, d, x[8], 6, 0x6fa87e4f);
     d = II(d, a, b, c, x[15], 10, 0xfe2ce6e0);
     c = II(c, d, a, b, x[6], 15, 0xa3014314);
     b = II(b, c, d, a, x[13], 21, 0x4e0811a1);
     a = II(a, b, c, d, x[4], 6, 0xf7537e82);
     d = II(d, a, b, c, x[11], 10, 0xbd3af235);
     c = II(c, d, a, b, x[2], 15, 0x2ad7d2bb);
     b = II(b, c, d, a, x[9], 21, 0xeb86d391);
     state[0] += a;
     state[1] += b;
     state[2] += c;
     state[3] += d;
 }

 private final void update(byte input[], int len)
 {
     int index = (int)(count >> 3) & 0x3f;
     count += len << 3;
     int partLen = 64 - index;
     int i = 0;
     if(len >= partLen)
     {
         System.arraycopy(input, 0, buffer, index, partLen);
         transform(buffer, 0);
         for(i = partLen; i + 63 < len; i += 64)
             transform(input, i);

         index = 0;
     } else
     {
         i = 0;
     }
     System.arraycopy(input, i, buffer, index, len - i);
 }

 private byte[] end()
 {
     byte bits[] = new byte[8];
     for(int i = 0; i < 8; i++)
         bits[i] = (byte)(int)(count >>> i * 8 & 255L);

     int index = (int)(count >> 3) & 0x3f;
     int padlen = index >= 56 ? 120 - index : 56 - index;
     update(padding, padlen);
     update(bits, 8);
     return encode(state, 16);
 }

 private byte[] encode(int input[], int len)
 {
     byte output[] = new byte[len];
     int i = 0;
     for(int j = 0; j < len; j += 4)
     {
         output[j] = (byte)(input[i] & 0xff);
         output[j + 1] = (byte)(input[i] >> 8 & 0xff);
         output[j + 2] = (byte)(input[i] >> 16 & 0xff);
         output[j + 3] = (byte)(input[i] >> 24 & 0xff);
         i++;
     }

     return output;
 }

 /**
 * @return return the digest
 * @throws IOException
 */
public String getDigest() throws IOException {
     byte buffer[] = new byte[1024];
     int got = -1;
     if(digest != null)
         return stringify(digest);
     while((got = in.read(buffer)) > 0) 
         update(buffer, got);
     digest = end();
    return stringify(digest);
    
 }



 /**
 * @param input
 */
public MD5(String input) {
     in = null;
     //stringp = false;
     state = null;
     count = 0L;
     buffer = null;
     digest = null;
     byte bytes[] = input.getBytes(CharsetUtil.UTF8);
     in = new ByteArrayInputStream(bytes);
     state = new int[4];
     buffer = new byte[64];
     count = 0L;
     state[0] = 0x67452301;
     state[1] = 0xefcdab89;
     state[2] = 0x98badcfe;
     state[3] = 0x10325476;
 }
/*
 public static final int DIGEST_CHARS = 32;
 public static final int DIGEST_BYTES = 16;
 private static final int BUFFER_SIZE = 1024;
 private static final int S11 = 7;
 private static final int S12 = 12;
 private static final int S13 = 17;
 private static final int S14 = 22;
 private static final int S21 = 5;
 private static final int S22 = 9;
 private static final int S23 = 14;
 private static final int S24 = 20;
 private static final int S31 = 4;
 private static final int S32 = 11;
 private static final int S33 = 16;
 private static final int S34 = 23;
 private static final int S41 = 6;
 private static final int S42 = 10;
 private static final int S43 = 15;
 private static final int S44 = 21;*/
 private static byte padding[] = {
     -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0
 };
 private InputStream in;
 //private boolean stringp;
 private int state[];
 private long count;
 private byte buffer[];
 private byte digest[];

}