package org.bouncycastle.bcpg;

import java.io.*;

/**
 * Basic output stream.
 */
public class BCPGOutputStream
	extends OutputStream
	implements PacketTags, CompressionAlgorithmTags
{
	OutputStream	out;
	boolean			oldPackets;
	
	public BCPGOutputStream(
		OutputStream	out)
	{
		this.out = out;
	}
	
	/**
     * Create a stream representing an old style partial object.
	 * 
	 * @param tag the packet tag for the object.
	 */
	public BCPGOutputStream(
		OutputStream	out,
		int					tag)
		throws IOException
	{
		this.out = out;
		this.writeHeader(tag, true, true, 0);
	}
	
	/**
	 * Create a stream representing a general packet.
	 * TODO: this should determine whether it is necessary to write a partial packet.
	 * 
	 * @param out
	 * @param tag
	 * @param length
	 * @param oldFormat
	 * @throws IOException
	 */
	public BCPGOutputStream(
		OutputStream	out,
		int					tag,
		long				length,
		boolean			oldFormat)
		throws IOException
	{
		this.out = out;
		this.writeHeader(tag, oldFormat, false, length);
	}
	
	/**
	 * 
	 * @param tag
	 * @param length
	 * @throws IOException
	 */
	public BCPGOutputStream(
		OutputStream	out,
		int					tag,
		long				length)
		throws IOException
	{
		this.out = out;
		
		this.writeHeader(tag, false, false, length);
	}
	
	private void writeHeader(
		int				tag,
		boolean		oldPackets,
		boolean		partial,
		long			bodyLen) 
		throws IOException
	{
		int	hdr = 0x80;
    	
		if (oldPackets)
		{
			hdr |= tag << 2;
			
			if (partial)
			{
				this.write(hdr | 0x03);
			}
			else
			{
				if (bodyLen <= 0xff)
				{
					this.write(hdr);
					this.write((byte)bodyLen);
				}
				else if (bodyLen <= 0xffff)
				{
					this.write(hdr | 0x01);
					this.write((byte)(bodyLen >> 8));
					this.write((byte)(bodyLen));
				}
				else
				{
					this.write(hdr | 0x02);
					this.write((byte)(bodyLen >> 24));
					this.write((byte)(bodyLen >> 16));
					this.write((byte)(bodyLen >> 8));
					this.write((byte)bodyLen);
				}
			}
		}
		else
		{
			hdr |= 0x40 | tag;
	
			if (partial)
			{
				throw new RuntimeException("not implemented");
			}
			else
			{
				this.write(hdr);
				
				if (bodyLen < 192)
				{
					this.write((byte)bodyLen);
				}
				else if (bodyLen <= 8383)
				{
					bodyLen -= 192;
					
					this.write((byte)(((bodyLen >> 8) & 0xff) + 192));
					this.write((byte)bodyLen);
				}
				else
				{
					this.write(0xff);
					this.write((byte)(bodyLen >> 24));
					this.write((byte)(bodyLen >> 16));
					this.write((byte)(bodyLen >> 8));
					this.write((byte)bodyLen);
				}
			}
		}
	}
	
	public void write(
		int	b)
		throws IOException
	{
		out.write(b);
	}
	
	public void write(
		byte[]	bytes,
		int		off,
		int		len)
		throws IOException
	{
		out.write(bytes, off, len);
	}
	
	public void writePacket(
		ContainedPacket	p)
		throws IOException
	{
		p.encode(this);
	}
	
	void writePacket(
		int			tag,
		byte[]		body,
		boolean	oldFormat)
		throws IOException
	{
		this.writeHeader(tag, oldFormat, false, body.length);
		this.write(body);
	}
	
	public void writeObject(
		BCPGObject	o)
		throws IOException
	{
		o.encode(this);
	}
	
	public void flush()
		throws IOException
	{
		out.flush();
	}
	
	public void close()
		throws IOException
	{
		out.flush();
		out.close();
	}
}
