/*
 * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
 * Please refer to the LICENSE.txt for licensing details.
 */
package ch.ethz.ssh2.crypto.cipher;

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

/**
 * CipherInputStream.
 *
 * @author Christian Plattner
 * @version $Id$
 */
public class CipherInputStream
{
	BlockCipher currentCipher;
	InputStream bi;
	byte[] buffer;
	byte[] enc;
	int blockSize;
	int pos;

	/*
	 * We cannot use java.io.BufferedInputStream, since that is not available in
	 * J2ME. Everything could be improved alot here.
	 */

	private static final int BUFF_SIZE = 8192;
	byte[] input_buffer = new byte[BUFF_SIZE];
	int input_buffer_pos = 0;
	int input_buffer_size = 0;

	public CipherInputStream(BlockCipher tc, InputStream bi)
	{
		this.bi = bi;
		changeCipher(tc);
	}

	private int fill_buffer() throws IOException
	{
		input_buffer_pos = 0;
		input_buffer_size = 0;
		input_buffer_size = bi.read(input_buffer, 0, BUFF_SIZE);
		return input_buffer_size;
	}

	private int internal_read(byte[] b, int off, int len) throws IOException
	{
		if (input_buffer_size < 0)
		{
			return -1;
		}

		if (input_buffer_pos >= input_buffer_size)
		{
			if (fill_buffer() <= 0)
			{
				return -1;
			}
		}

		int avail = input_buffer_size - input_buffer_pos;
		int thiscopy = (len > avail) ? avail : len;

		System.arraycopy(input_buffer, input_buffer_pos, b, off, thiscopy);
		input_buffer_pos += thiscopy;

		return thiscopy;
	}

	public void changeCipher(BlockCipher bc)
	{
		this.currentCipher = bc;
		blockSize = bc.getBlockSize();
		buffer = new byte[blockSize];
		enc = new byte[blockSize];
		pos = blockSize;
	}

	private void getBlock() throws IOException
	{
		int n = 0;
		while (n < blockSize)
		{
			int len = internal_read(enc, n, blockSize - n);
			if (len < 0)
			{
				throw new IOException("Cannot read full block, EOF reached.");
			}
			n += len;
		}

		try
		{
			currentCipher.transformBlock(enc, 0, buffer, 0);
		}
		catch (Exception e)
		{
			throw new IOException("Error while decrypting block.");
		}
		pos = 0;
	}

	public int read(byte[] dst) throws IOException
	{
		return read(dst, 0, dst.length);
	}

	public int read(byte[] dst, int off, int len) throws IOException
	{
		int count = 0;

		while (len > 0)
		{
			if (pos >= blockSize)
			{
				getBlock();
			}

			int avail = blockSize - pos;
			int copy = Math.min(avail, len);
			System.arraycopy(buffer, pos, dst, off, copy);
			pos += copy;
			off += copy;
			len -= copy;
			count += copy;
		}
		return count;
	}

	public int read() throws IOException
	{
		if (pos >= blockSize)
		{
			getBlock();
		}
		return buffer[pos++] & 0xff;
	}

	public int readPlain(byte[] b, int off, int len) throws IOException
	{
		if (pos != blockSize)
		{
			throw new IOException("Cannot read plain since crypto buffer is not aligned.");
		}
		int n = 0;
		while (n < len)
		{
			int cnt = internal_read(b, off + n, len - n);
			if (cnt < 0)
			{
				throw new IOException("Cannot fill buffer, EOF reached.");
			}
			n += cnt;
		}
		return n;
	}
}
