package com.jscape.crypto;

import com.jscape.util.BigEndianBits;
import com.jscape.util.EndianBits;

import static com.jscape.util.Parameters.assertNotNull;

/**
 * The SHA-1 hash algorithm.
 *
 * @author Alex Usun
 */
public class SHA1
        implements Hash {

    private static final int HASH_LENGTH = 20;
    private static final EndianBits ENDIAN_BITS = new BigEndianBits();

    private final SHA1Algorithm algorithm;
    private final SHA1Buffer buffer;

    /**
     * Creates a default SHA-1 hash implementation.
     *
     * @return a default ASHA-1 hash implementation
     */
    public static SHA1 defaultHash() {
        return new SHA1(new SHA1Algorithm());
    }

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

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

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

    /**
     * {@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);
        ENDIAN_BITS.putInt(this.algorithm.getE(), ret, 16);

        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 "SHA1 {"
                + "algorithm=" + this.algorithm
                + ", buffer=" + this.buffer
                + '}';
    }
}
