package aalg.crypto;

import java.util.Arrays;

abstract public class MDHashFunction extends HashFunctionImpl{

	protected final int blockSize;
	private final byte[] byteBlock;
	protected long[] longBlock;
	protected long messageLength;
	private int bytesInBlock;
	protected long[] hash;
	
	public MDHashFunction(int blockSize) {
		this.blockSize = blockSize;
		this.byteBlock = new byte[blockSize];
	
	}
	
	@Override
	public void initialize() {
		
		messageLength = 0;
		bytesInBlock = 0;
	}
	
	@Override
	public void update (final byte[] data, final int offset, final int size) {
		messageLength += size;
		
		final int endIndex = offset + size;
		int inputIndex = offset;
		
		// Finish begin block part
		if (bytesInBlock > 0) {
			final int toCopy = Math.min(endIndex - inputIndex, blockSize - bytesInBlock);
			System.arraycopy(data, inputIndex, byteBlock, bytesInBlock, toCopy);
			
			inputIndex += toCopy;
			bytesInBlock += toCopy;
			
			if (bytesInBlock < blockSize)
				return;
				
			parseBlock(byteBlock, 0);
			updateHashByBlock();
			
			bytesInBlock = 0;
		}
		
		// Hash whole blocks
		while (inputIndex + blockSize <= endIndex) {
			parseBlock(data, inputIndex);
			updateHashByBlock();
			
			inputIndex += blockSize;
		}
		
		// Store remaining part of data
		bytesInBlock = endIndex - inputIndex;
		System.arraycopy(data, inputIndex, byteBlock, 0, bytesInBlock);
	}
	
	@Override
	public byte[] finish() {
		byteBlock[bytesInBlock] = (byte) 0x80;
		bytesInBlock++;
		
		Arrays.fill(byteBlock, bytesInBlock, blockSize, (byte) 0);
		parseBlock(byteBlock, 0);
		
		if (blockSize - bytesInBlock < 16) {
			updateHashByBlock();
			Arrays.fill(longBlock, (byte) 0);
		}
		
		return hashToBytes (hash);

	}

	abstract protected byte[] hashToBytes(long[] hash2);
	abstract protected void updateHashByBlock();
	abstract protected void parseBlock(final byte[] input, final int offset);
	
}
