/*
 * @(#)MD5AlgorithmImpl.java
 *
 * Copyright (c) 2002-2003 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */

package com.jscape.crypto;

/**
 * MD5 algorithm implementation.
 *
 * @author Alex Usun
 */
public class MD5Algorithm {

    /**
     * Algorithm block size.
     */
    public static final int BLOCK_SIZE = 64;

    /**
     * Algorithm sub-blocks count.
     */
    public static final int SUBLOCKS_COUNT = 16;

    /**
     * Algorithm message length size.
     */
    public static final int MESSAGE_LENGTH_SIZE = 8;


    private static final int A = 0x67452301;
    private static final int B = 0xEFCDAB89;
    private static final int C = 0x98BADCFE;
    private static final int D = 0x10325476;

    private int a;
    private int b;
    private int c;
    private int d;

    /**
     * Creates a new algorithm implementation.
     */
    public MD5Algorithm() {
        init();
    }

    /**
     * Resets the algorithm state.
     */
    public void reset() {
        init();
    }

    /**
     * Updates the algorithm state with new block.
     *
     * @param subBlocks the sub-blocks
     */
    public void update(int[] subBlocks) {
        int a = this.a;
        int b = this.b;
        int c = this.c;
        int d = this.d;

        // round 1
        a = computeFF(a, b, c, d, subBlocks[0], 7, 0xD76AA478);
        d = computeFF(d, a, b, c, subBlocks[1], 12, 0xE8C7B756);
        c = computeFF(c, d, a, b, subBlocks[2], 17, 0x242070DB);
        b = computeFF(b, c, d, a, subBlocks[3], 22, 0xC1BDCEEE);
        a = computeFF(a, b, c, d, subBlocks[4], 7, 0xF57C0FAF);
        d = computeFF(d, a, b, c, subBlocks[5], 12, 0x4787C62A);
        c = computeFF(c, d, a, b, subBlocks[6], 17, 0xA8304613);
        b = computeFF(b, c, d, a, subBlocks[7], 22, 0xFD469501);
        a = computeFF(a, b, c, d, subBlocks[8], 7, 0x698098D8);
        d = computeFF(d, a, b, c, subBlocks[9], 12, 0x8B44F7AF);
        c = computeFF(c, d, a, b, subBlocks[10], 17, 0xFFFF5BB1);
        b = computeFF(b, c, d, a, subBlocks[11], 22, 0x895CD7BE);
        a = computeFF(a, b, c, d, subBlocks[12], 7, 0x6B901122);
        d = computeFF(d, a, b, c, subBlocks[13], 12, 0xFD987193);
        c = computeFF(c, d, a, b, subBlocks[14], 17, 0xA679438E);
        b = computeFF(b, c, d, a, subBlocks[15], 22, 0x49B40821);

        // round 2
        a = computeGG(a, b, c, d, subBlocks[1], 5, 0xF61E2562);
        d = computeGG(d, a, b, c, subBlocks[6], 9, 0xC040B340);
        c = computeGG(c, d, a, b, subBlocks[11], 14, 0x265E5A51);
        b = computeGG(b, c, d, a, subBlocks[0], 20, 0xE9B6C7AA);
        a = computeGG(a, b, c, d, subBlocks[5], 5, 0xD62F105D);
        d = computeGG(d, a, b, c, subBlocks[10], 9, 0x02441453);
        c = computeGG(c, d, a, b, subBlocks[15], 14, 0xD8A1E681);
        b = computeGG(b, c, d, a, subBlocks[4], 20, 0xE7D3FBC8);
        a = computeGG(a, b, c, d, subBlocks[9], 5, 0x21E1CDE6);
        d = computeGG(d, a, b, c, subBlocks[14], 9, 0xC33707D6);
        c = computeGG(c, d, a, b, subBlocks[3], 14, 0xF4D50D87);
        b = computeGG(b, c, d, a, subBlocks[8], 20, 0x455A14ED);
        a = computeGG(a, b, c, d, subBlocks[13], 5, 0xA9E3E905);
        d = computeGG(d, a, b, c, subBlocks[2], 9, 0xFCEFA3F8);
        c = computeGG(c, d, a, b, subBlocks[7], 14, 0x676F02D9);
        b = computeGG(b, c, d, a, subBlocks[12], 20, 0x8D2A4C8A);

        //round 3
        a = computeHH(a, b, c, d, subBlocks[5], 4, 0xFFFA3942);
        d = computeHH(d, a, b, c, subBlocks[8], 11, 0x8771F681);
        c = computeHH(c, d, a, b, subBlocks[11], 16, 0x6D9D6122);
        b = computeHH(b, c, d, a, subBlocks[14], 23, 0xFDE5380C);
        a = computeHH(a, b, c, d, subBlocks[1], 4, 0xA4BEEA44);
        d = computeHH(d, a, b, c, subBlocks[4], 11, 0x4BDECFA9);
        c = computeHH(c, d, a, b, subBlocks[7], 16, 0xF6BB4B60);
        b = computeHH(b, c, d, a, subBlocks[10], 23, 0xBEBFBC70);
        a = computeHH(a, b, c, d, subBlocks[13], 4, 0x289B7EC6);
        d = computeHH(d, a, b, c, subBlocks[0], 11, 0xEAA127FA);
        c = computeHH(c, d, a, b, subBlocks[3], 16, 0xD4EF3085);
        b = computeHH(b, c, d, a, subBlocks[6], 23, 0x04881D05);
        a = computeHH(a, b, c, d, subBlocks[9], 4, 0xD9D4D039);
        d = computeHH(d, a, b, c, subBlocks[12], 11, 0xE6DB99E5);
        c = computeHH(c, d, a, b, subBlocks[15], 16, 0x1FA27CF8);
        b = computeHH(b, c, d, a, subBlocks[2], 23, 0xC4AC5665);

        // round 4
        a = computeII(a, b, c, d, subBlocks[0], 6, 0xF4292244);
        d = computeII(d, a, b, c, subBlocks[7], 10, 0x432AFF97);
        c = computeII(c, d, a, b, subBlocks[14], 15, 0xAB9423A7);
        b = computeII(b, c, d, a, subBlocks[5], 21, 0xFC93A039);
        a = computeII(a, b, c, d, subBlocks[12], 6, 0x655B59C3);
        d = computeII(d, a, b, c, subBlocks[3], 10, 0x8F0CCC92);
        c = computeII(c, d, a, b, subBlocks[10], 15, 0xFFEFF47D);
        b = computeII(b, c, d, a, subBlocks[1], 21, 0x85845DD1);
        a = computeII(a, b, c, d, subBlocks[8], 6, 0x6FA87E4F);
        d = computeII(d, a, b, c, subBlocks[15], 10, 0xFE2CE6E0);
        c = computeII(c, d, a, b, subBlocks[6], 15, 0xA3014314);
        b = computeII(b, c, d, a, subBlocks[13], 21, 0x4E0811A1);
        a = computeII(a, b, c, d, subBlocks[4], 6, 0xF7537E82);
        d = computeII(d, a, b, c, subBlocks[11], 10, 0xBD3AF235);
        c = computeII(c, d, a, b, subBlocks[2], 15, 0x2AD7D2BB);
        b = computeII(b, c, d, a, subBlocks[9], 21, 0xEB86D391);

        this.a += a;
        this.b += b;
        this.c += c;
        this.d += d;
    }

    /**
     * Returns the A value.
     *
     * @return the A value
     */
    public int getA() {
        return this.a;
    }

    /**
     * Returns the B value.
     *
     * @return the B value
     */
    public int getB() {
        return this.b;
    }

    /**
     * Returns the C value.
     *
     * @return the C value
     */
    public int getC() {
        return this.c;
    }

    /**
     * Returns the D value.
     *
     * @return the D value
     */
    public int getD() {
        return this.d;
    }

    @Override
    public String toString() {
        return "MD5Algorithm {"
                + "a=" + this.a
                + ", b=" + this.b
                + ", c=" + this.c
                + ", d=" + this.d
                + '}';
    }

    private void init() {
        this.a = A;
        this.b = B;
        this.c = C;
        this.d = D;
    }

    private int computeF(int x, int y, int z) {
        return ((y ^ z) & x) ^ z;
    }

    private int computeG(int x, int y, int z) {
        return ((x ^ y) & z) ^ y;
    }

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

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

    private int computeFF(int a, int b, int c, int d, int m, int s, int t) {
        return b + CryptoTools.rotateLeft((a + computeF(b, c, d) + m + t), s);
    }

    private int computeGG(int a, int b, int c, int d, int m, int s, int t) {
        return b + CryptoTools.rotateLeft((a + computeG(b, c, d) + m + t), s);
    }

    private int computeHH(int a, int b, int c, int d, int m, int s, int t) {
        return b + CryptoTools.rotateLeft((a + computeH(b, c, d) + m + t), s);
    }

    private int computeII(int a, int b, int c, int d, int m, int s, int t) {
        return b + CryptoTools.rotateLeft((a + computeI(b, c, d) + m + t), s);
    }
}
