package com.jscape.crypto;

/**
 * The SHA-1 algorithm implementation.
 *
 * @author Alex Usun
 */
public class SHA1Algorithm {

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

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

    /**
     * Algorithm extended sub-blocks count.
     */
    public static final int EXTENDED_SUBBLOCKS_COUNT = 80;

    /**
     * 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 static final int E = 0xC3D2E1F0;
    private static final int K1 = 0x5A827999;
    private static final int K2 = 0x6ED9EBA1;
    private static final int K3 = 0x8F1BBCDC;
    private static final int K4 = 0xCA62C1D6;

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

    /**
     * Creates a new algorithm implementation.
     */
    public SHA1Algorithm() {
        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;
        int e = this.e;

        for (int i = 0; i < subBlocks.length; ++i) {
            if (i >= SUBBLOCKS_COUNT) {
                subBlocks[i] = CryptoTools.rotateLeft(subBlocks[i - 3] ^ subBlocks[i - 8] ^ subBlocks[i - 14] ^ subBlocks[i - 16], 1);
            }

            int tmp;

            if (i < 20) {
                tmp = CryptoTools.rotateLeft(a, 5) + computeF1(b, c, d) + e + subBlocks[i] + K1;
            } else if (i < 40) {
                tmp = CryptoTools.rotateLeft(a, 5) + computeF2(b, c, d) + e + subBlocks[i] + K2;
            } else if (i < 60) {
                tmp = CryptoTools.rotateLeft(a, 5) + computeF3(b, c, d) + e + subBlocks[i] + K3;
            } else {
                tmp = CryptoTools.rotateLeft(a, 5) + computeF4(b, c, d) + e + subBlocks[i] + K4;
            }

            e = d;
            d = c;
            c = CryptoTools.rotateLeft(b, 30);
            b = a;
            a = tmp;
        }

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


    /**
     * 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;
    }

    /**
     * Returns the E value.
     *
     * @return the E value
     */
    public int getE() {
        return this.e;
    }

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

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

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

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

    private int computeF3(int x, int y, int z) {
        return (x & y) | (x & z) | (y & z);
    }

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

}
