package org.bouncycastle.bcpg;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Vector;

/**
 * generic signature packet
 */
public class SignaturePacket 
	extends ContainedPacket implements PublicKeyAlgorithmTags
{
	private int									version;
	private int 									signatureType;
	private long 								creationTime;
	private long 								keyID;
	private int 									keyAlgorithm;
	private int 									hashAlgorithm;
	private MPInteger[]					signature;
	private byte[]								fingerPrint;
	private SignatureSubpacket[]	hashedData;
	private SignatureSubpacket[]	unhashedData;
	
	SignaturePacket(
		BCPGInputStream	in)
		throws IOException
	{
		version = in.read();
		
		if (version == 3)
		{
			int	l = in.read();
			
			signatureType = in.read();
			creationTime = ((long)in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
			keyID |= (long)in.read() << 56;
			keyID |= (long)in.read() << 48;
			keyID |= (long)in.read() << 40;
			keyID |= (long)in.read() << 32;
			keyID |= (long)in.read() << 24;
			keyID |= (long)in.read() << 16;
			keyID |= (long)in.read() << 8;
			keyID |= in.read();
			keyAlgorithm = in.read();
			hashAlgorithm = in.read();
		}
		else if (version == 4)
		{
			signatureType = in.read();
			keyAlgorithm = in.read();
			hashAlgorithm = in.read();
			
			int		hashedLength = (in.read() << 8) | in.read();
			byte[]	hashed = new byte[hashedLength];
			
			in.readFully(hashed);

			//
			// read the signature sub packet data.
			//
			SignatureSubpacket	sub;
			SignatureSubpacketInputStream	sIn = new SignatureSubpacketInputStream(
																				new ByteArrayInputStream(hashed));

			Vector	v = new Vector();
			while ((sub = sIn.readPacket()) != null)
			{
				v.addElement(sub);
			}
			
			hashedData = new SignatureSubpacket[v.size()];
			
			for (int i = 0; i != hashedData.length; i++)
			{
				hashedData[i] = (SignatureSubpacket)v.elementAt(i);
			}
			
			int		unhashedLength = (in.read() << 8) | in.read();
			byte[]	unhashed = new byte[unhashedLength];
			
			in.readFully(unhashed);
			
			sIn = new SignatureSubpacketInputStream(
													new ByteArrayInputStream(unhashed));
													
			v.removeAllElements();
			while ((sub = sIn.readPacket()) != null)
			{
				v.addElement(sub);
			}
			
			unhashedData = new SignatureSubpacket[v.size()];
			
			for (int i = 0; i != unhashedData.length; i++)
			{
				unhashedData[i] = (SignatureSubpacket)v.elementAt(i);
			}
		}
		else
		{
		    throw new RuntimeException("unsupported version: " + version);
		}
		
		fingerPrint = new byte[2];
		in.readFully(fingerPrint);
		
		switch (keyAlgorithm)
		{
		case RSA_GENERAL:
		case RSA_SIGN:
			MPInteger	v = new MPInteger(in);
				
			signature = new MPInteger[1];
			signature[0] = v;
			break;
		case DSA:
			MPInteger	r = new MPInteger(in);
			MPInteger	s = new MPInteger(in);
				
			signature = new MPInteger[2];
			signature[0] = r;
			signature[1] = s;
			break;
		default:
			throw new IOException("unknown signature key algorithm");
		}
	}
	
	public SignaturePacket(
		int							signatureType,
		int							keyAlgorithm,
		int							hashAlgorithm,
		SignatureSubpacket[]	hashedData,
		SignatureSubpacket[]	unhashedData,
		byte[]						fingerPrint,
		MPInteger[]				signature)
	{
		this.version = 4;
		this.signatureType = signatureType;
		this.keyAlgorithm = keyAlgorithm;
		this.hashAlgorithm = hashAlgorithm;
		this.hashedData = hashedData;
		this.unhashedData = unhashedData;
		this.fingerPrint = fingerPrint;
		this.signature = signature;
	}
	
	/**
	 * get the version number
     */
	public int getVersion()
	{
		return version;
	}
	
	/**
	 * return the signature type.
	 */
	public int getSignatureType()
	{
		return signatureType;
	}
	
	/**
	 * return the signature trailer that must be included with the data
	 * to reconstruct the signature
	 * 
	 * @return byte[]
	 */
	public byte[] getSignatureTrailer()
	{
		byte[]	trailer = null;
		
		if (version == 3)
		{
			trailer = new byte[5];
			
			trailer[0] = (byte)signatureType;
			trailer[1] = (byte)(creationTime >> 24);
			trailer[2] = (byte)(creationTime >> 16);
			trailer[3] = (byte)(creationTime >> 8);
			trailer[4] = (byte)(creationTime);
		}
		else
		{
			ByteArrayOutputStream	sOut = new ByteArrayOutputStream();
		
			try
			{
				sOut.write((byte)this.getVersion());
				sOut.write((byte)this.getSignatureType());
				sOut.write((byte)this.getKeyAlgorithm());
				sOut.write((byte)this.getHashAlgorithm());
			
				ByteArrayOutputStream	hOut = new ByteArrayOutputStream();
				SignatureSubpacket[]		hashed = this.getHashedSubPackets();
			
				for (int i = 0; i != hashed.length; i++)
				{
					hashed[i].encode(hOut);
				}
				
				byte[]									data = hOut.toByteArray();
			
				sOut.write((byte)(data.length >> 8));
				sOut.write((byte)data.length);
				sOut.write(data);
			
				byte[]	hData = sOut.toByteArray();
			
				sOut.write((byte)this.getVersion());
				sOut.write((byte)0xff);
				sOut.write((byte)(hData.length>> 24));
				sOut.write((byte)(hData.length >> 16));
				sOut.write((byte)(hData.length >> 8));
				sOut.write((byte)(hData.length));
			}
			catch (IOException e)
			{
				throw new RuntimeException("exception generating trailer: " + e);
			}
				
			trailer = sOut.toByteArray();
		}
		
		return trailer;
	}
	
	/**
	 * return the encryption algorithm tag
	 */
	public int getKeyAlgorithm()
	{
		return keyAlgorithm;
	}
	
	/**
	 * return the hashAlgorithm tag
	 */
	public int getHashAlgorithm()
	{
		return hashAlgorithm;
	}
	
	/**
	 * return the signature bytes - note this is normalised to be the
	 * ASN.1 encoding of what appears in the signature packet.
	 */
	public MPInteger[] getSignature()
	{
		return signature;
	}
	
	public SignatureSubpacket[] getHashedSubPackets()
	{
		return hashedData;
	}
	
	public SignatureSubpacket[] getUnhashedSubPackets()
	{
		return unhashedData;
	}
	
	public void encode(
		BCPGOutputStream	out)
		throws IOException
	{
		ByteArrayOutputStream	bOut = new ByteArrayOutputStream();
		BCPGOutputStream			pOut = new BCPGOutputStream(bOut);
    	
		pOut.write(version);
    	
    	if (version == 3)
    	{
    		pOut.write(5); // the length of the next block
    		
			pOut.write(signatureType);
    		pOut.write((byte)(creationTime >> 24));
			pOut.write((byte)(creationTime >> 16));
			pOut.write((byte)(creationTime >> 8));
			pOut.write((byte)creationTime);

			pOut.write((byte)(keyID >> 56));
			pOut.write((byte)(keyID >> 48));
			pOut.write((byte)(keyID >> 40));
			pOut.write((byte)(keyID >> 32));
			pOut.write((byte)(keyID >> 24));
			pOut.write((byte)(keyID >> 16));
			pOut.write((byte)(keyID >> 8));
			pOut.write((byte)(keyID));
			
			pOut.write(keyAlgorithm);
			pOut.write(hashAlgorithm);
    	}
    	else if (version == 4)
    	{
    		pOut.write(signatureType);
    		pOut.write(keyAlgorithm);
    		pOut.write(hashAlgorithm);
    		
    		ByteArrayOutputStream	sOut = new ByteArrayOutputStream();
    		
    		for (int i = 0; i != hashedData.length; i++)
    		{
    			hashedData[i].encode(sOut);
    		}
    		
    		byte[]									data = sOut.toByteArray();
	
    		pOut.write(data.length >> 8);
    		pOut.write(data.length);
    		pOut.write(data);
    		
    		sOut.reset();
    		
			for (int i = 0; i != unhashedData.length; i++)
			{
				unhashedData[i].encode(sOut);
			}
			
			data = sOut.toByteArray();
  	
			pOut.write(data.length >> 8);
			pOut.write(data.length);
			pOut.write(data);
    	}
    	else
    	{
    		throw new IOException("unknown version");
    	}
    	
    	pOut.write(fingerPrint);
    	
    	for (int i = 0; i != signature.length; i++)
    	{
    		pOut.writeObject(signature[i]);
    	}
    	
		out.writePacket(SIGNATURE, bOut.toByteArray(), true);
	}
}
