package org.bouncycastle.bcpg;

import java.io.*;

/**
 * reader for Base64 armored objects - read the headers and then start returning
 * bytes when the data is reached. An IOException is thrown if the CRC check
 * fails.
 */
public class ArmoredInputStream
    extends InputStream
{
	/*
	 * set up the decoding table.
	 */
	private static final byte[] decodingTable;

	static
	{
		decodingTable = new byte[128];

		for (int i = 'A'; i <= 'Z'; i++)
		{
			decodingTable[i] = (byte)(i - 'A');
		}

		for (int i = 'a'; i <= 'z'; i++)
		{
			decodingTable[i] = (byte)(i - 'a' + 26);
		}

		for (int i = '0'; i <= '9'; i++)
		{
			decodingTable[i] = (byte)(i - '0' + 52);
		}

		decodingTable['+'] = 62;
		decodingTable['/'] = 63;
	}

	/**
	 * decode the base 64 encoded input data.
	 *
	 * @return the offset the data starts in out.
	 */
	private int decode(
		int		in0,
		int		in1,
		int		in2,
		int		in3,
		int[]	out)
		throws EOFException
	{
		int	b1, b2, b3, b4;

		if (in3 < 0)
		{
			throw new EOFException("unexpected end of file in armored stream.");
		}

		if (in2 == '=')
		{
			b1 = decodingTable[in0] &0xff;
			b2 = decodingTable[in1] & 0xff;

			out[2] = ((b1 << 2) | (b2 >> 4)) & 0xff;

			return 2;
		}
		else if (in3 == '=')
		{
			b1 = decodingTable[in0];
			b2 = decodingTable[in1];
			b3 = decodingTable[in2];

			out[1] = ((b1 << 2) | (b2 >> 4)) & 0xff;
			out[2] = ((b2 << 4) | (b3 >> 2)) & 0xff;

			return 1;
		}
		else
		{
			b1 = decodingTable[in0];
			b2 = decodingTable[in1];
			b3 = decodingTable[in2];
			b4 = decodingTable[in3];

			out[0] = ((b1 << 2) | (b2 >> 4)) & 0xff;
			out[1] = ((b2 << 4) | (b3 >> 2)) & 0xff;
			out[2] = ((b3 << 6) | b4) & 0xff;

			return 0;
		}
	}

	InputStream	in;
	boolean		start = true;
	int[]		outBuf = new int[3];
	int			bufPtr = 3;
	CRC24		crc = new CRC24();
	boolean		crcFound = false;
	
    public ArmoredInputStream(
    	InputStream	in)
    {
        this.in = in;
    }
    
    public int available()
    	throws IOException
    {
    	return in.available();
    }
    
	private boolean parseHeaders()
		throws IOException
	{
		//
		// TODO: parse the headers properly!
		//
		int		c;
		int		last = 0;
		boolean	headerFound = false;
	
		while ((c = in.read()) >= 0)
		{
			if (c == '-' && (last == 0 || last == '\n'))
			{
				headerFound = true;
				break;
			}

			last = c;
		}

		if (headerFound)
		{
			while ((c = in.read()) >= 0)
			{
				if (last == '\n' && c == '\n')
				{
					break;
				}
				if (c != '\r')
				{
					last = c;
				}
			}
		}

		return headerFound;
	}

    public int read()
    	throws IOException
    {
		int	c;

		if (start)
		{
			parseHeaders();

			start = false;
		}

		if (bufPtr > 2)
		{
			c = in.read();
			if (c == '\n' || c == '\r')
			{
				c = in.read();
				if (c == '\n')
				{
					c = in.read();
				}

				if (c < 0)				// EOF
				{
					return -1;
				}
	
				if (c == '=')			// crc reached
				{
					bufPtr = decode(in.read(), in.read(), in.read(), in.read(), outBuf);
					if (bufPtr == 0)
					{
						int i = ((outBuf[0] & 0xff) << 16)
								| ((outBuf[1] & 0xff) << 8)
								| (outBuf[2] & 0xff);

						crcFound = true;

						if (i != crc.getValue())
						{
							throw new IOException("crc check failed in armored message.");
						}

						return -1;
					}
					else
					{
						throw new IOException("no crc found in armored message.");
					}
				}
				else if (c == '-')		// end of record reached
				{
					while ((c = in.read()) >= 0)
					{
						if (c == '\n')
						{
							break;
						}
					}

					if (!crcFound)
					{
						throw new IOException("crc check not found.");
					}

					return -1;
				}
				else				   // data
				{
					bufPtr = decode(c, in.read(), in.read(), in.read(), outBuf);
				}
			}
			else
			{
				if (c >= 0)
				{
					bufPtr = decode(c, in.read(), in.read(), in.read(), outBuf);
				}
				else
				{
					return -1;
				}
			}
		}

		c = outBuf[bufPtr++];

		crc.update(c);

		return c;
	}
}
