/*
 * @(#)MD5.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;

import com.jscape.util.EndianBits;
import com.jscape.util.LittleEndianBits;
import com.jscape.util.Parameters;

/**
 * The MD5 hash algorithm.
 *
 * @author Alex Usun
 */
public class MD5
        implements Hash {

    private static final int HASH_LENGTH = 16;
    private static final EndianBits ENDIAN_BITS = new LittleEndianBits();

    private final MD5Algorithm algorithm;
    private final MD5Buffer buffer;

    /**
     * Creates a default MD5 hash implementation.
     *
     * @return a default MD5 hash implementation
     */
    public static MD5 defaultHash() {
        return new MD5(new MD5Algorithm());
    }

    /**
     * Creates a new hash instance.
     *
     * @param algorithm an algorithm implementation
     */
    public MD5(MD5Algorithm algorithm) {
        Parameters.assertNotNull(algorithm);
        this.algorithm = algorithm;

        this.buffer = new MD5Buffer(this.algorithm);
    }

    /**
     * Creates a new hash instance.
     *
     * @deprecated use <code>MD5.defaultHash()</code> instead
     */
    public MD5() {
        this(new MD5Algorithm());
    }

    /**
     * {@inheritDoc}
     */
    public int getLength() {
        return HASH_LENGTH;
    }

    /**
     * {@inheritDoc}
     */
    public Hash reset() {
        this.buffer.reset();
        this.algorithm.reset();
        return this;
    }

    /**
     * {@inheritDoc}
     */
    public Hash update(byte data) {
        this.buffer.put(data);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    public Hash update(byte[] data) {
        return update(data, 0, data.length);
    }

    /**
     * {@inheritDoc}
     */
    public Hash update(byte[] data, int offset, int length) {
        this.buffer.put(data, offset, length);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    public byte[] compute() {
        this.buffer.complete();

        byte[] ret = new byte[HASH_LENGTH];
        ENDIAN_BITS.putInt(this.algorithm.getA(), ret, 0);
        ENDIAN_BITS.putInt(this.algorithm.getB(), ret, 4);
        ENDIAN_BITS.putInt(this.algorithm.getC(), ret, 8);
        ENDIAN_BITS.putInt(this.algorithm.getD(), ret, 12);

        reset();

        return ret;
    }

    /**
     * {@inheritDoc}
     */
    public byte[] compute(byte[] data) {
        return compute(data, 0, data.length);
    }

    /**
     * {@inheritDoc}
     */
    public byte[] compute(byte[] data, int offset, int length) {
        update(data, offset, length);
        return compute();
    }

    @Override
    public String toString() {
        return "MD5 {"
                + "algorithm=" + this.algorithm
                + ", buffer=" + this.buffer
                + '}';
    }
}
