package aalg.crypto;

import java.io.IOException;
import java.io.InputStream;

/**
 * Base implementation for byte padding reader.
 * Reader normally reads all blocks till the last one that is decoded
 * by method decodePadding.
 * @author Ing. Petr Ležák
 */
abstract public class BytePaddingReader implements IPaddingReader {
	protected class UnpaddedStream extends InputStream {
		@Override
		public int read() throws IOException {
			if (blockBegin >= blockEnd)
				readBlock();
			
			if (blockBegin < blockEnd) {
				final int b = block[blockBegin];
				blockBegin++;
				
				return b;
			}
			else
				return -1;
		}
	}
	
	protected InputStream paddedStream;
	protected UnpaddedStream unpaddedStream;
	protected int blockLength;
	
	// Buffer with padded data
	protected byte[] buffer;
	protected int blocksInBuffer;

	// One block with decoded (unpadded) data
	protected byte[] block;
	protected int blockBegin;
	protected int blockEnd;
	
	private void readBlock() throws IOException {
		readBuffer();
		
		switch (blocksInBuffer) {
			case 2:
				System.arraycopy(buffer, 0, block, 0, blockLength);
				System.arraycopy(buffer, blockLength, buffer, 0, blockLength);
				blocksInBuffer = 1;
				blockBegin = 0;
				blockEnd = blockLength;
				break;
			
			case 1:
				decodePadding();
				blocksInBuffer = 0;
				break;
		}
	}
	
	private void readBuffer() throws IOException {
		int begin = blocksInBuffer * blockLength;
		
		while (begin < buffer.length) {
			final int ret = paddedStream.read(buffer, begin, buffer.length - begin);
			
			if (ret >= 0)
				begin += ret;
			else
				break;
		}
		
		if (begin % blockLength != 0)
			throw new RuntimeException("Padded stream has no length as multiple of blocks");
		
		blocksInBuffer = begin / blockLength;
	}

	@Override
	public void initialize(final InputStream paddedStream, final int blockLength) throws IOException {
		this.paddedStream = paddedStream;
		this.unpaddedStream = new UnpaddedStream();
		this.blockLength = blockLength;
		this.buffer = new byte[2*blockLength];
		this.block = new byte[blockLength];
		this.blockBegin = 0;
		this.blockEnd = 0;
		this.blocksInBuffer = 0;
	}

	@Override
	public InputStream getSourceStream() throws IOException {
		return paddedStream;
	}

	@Override
	public InputStream getTargetStream() throws IOException {
		return unpaddedStream;
	}

	@Override
	public void finish() throws IOException {
		
	}

	protected abstract void decodePadding();
}
