package org.bouncycastle.openpgp;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Iterator;

import org.bouncycastle.bcpg.*;

import org.bouncycastle.jce.spec.ElGamalParameterSpec;
import org.bouncycastle.jce.spec.ElGamalPublicKeySpec;

/**
 * general class to handle a PGP public key object.
 */
public class PGPPublicKey
	implements PublicKeyAlgorithmTags
{
	private long 		keyID;
	private byte[]	fingerPrint;
	
	PublicKeyPacket	publicPk;
	ArrayList			ids;
	ArrayList			idSigs;
	
	PGPPublicKey(
		PublicKeyPacket 	publicPk,
		MessageDigest	sha,
		ArrayList			ids,
		ArrayList			idSigs)
		throws IOException
	{
		this.publicPk = publicPk;
		this.ids = ids;
		this.idSigs = idSigs;
		
		BCPGKey				key = publicPk.getKey();
		
		if (publicPk.getVersion() <= 3)
		{
			RSAPublicBCPGKey	rK = (RSAPublicBCPGKey)key;
			
			this.keyID = rK.getModulus().longValue();
				
			sha.update(new MPInteger(rK.getModulus()).getEncoded());
			sha.update(new MPInteger(rK.getPublicExponent()).getEncoded());
		}
		else
		{
				byte[]							kBytes = publicPk.getEncodedContents();

				sha.update((byte)0x99);
				sha.update((byte)(kBytes.length >> 8));
				sha.update((byte)kBytes.length);
				sha.update(kBytes);
				
				this.fingerPrint = sha.digest();
				this.keyID = ((long)(fingerPrint[fingerPrint.length - 8] & 0xff) << 56)
								| ((long)(fingerPrint[fingerPrint.length - 7] & 0xff) << 48)
								| ((long)(fingerPrint[fingerPrint.length - 6] & 0xff) << 40)
								| ((long)(fingerPrint[fingerPrint.length - 5] & 0xff) << 32)
								| ((long)(fingerPrint[fingerPrint.length - 4] & 0xff) << 24)
								| ((long)(fingerPrint[fingerPrint.length - 3] & 0xff) << 16)
								| ((long)(fingerPrint[fingerPrint.length - 2] & 0xff) << 8)
								| (      (fingerPrint[fingerPrint.length - 1] & 0xff));
		}
	}
	
	/**
	 * Return the keyID associated with the public key.
	 * 
	 * @return long
	 */
	public long getKeyID()
	{
		return keyID;
	}
	
	/**
	 * Return the algorithm code associated with the public key.
	 * 
	 * @return int
	 */
	public int getAlgorithm()
	{
		return publicPk.getAlgorithm();
	}
	
	/**
	 * Return the public key contained in the object.
	 * 
	 * @param provider provider to construct the key for.
	 * 
	 * @return PublicKey
	 * @throws PGPException
	 * @throws NoSuchProviderException
	 */
	public PublicKey getKey(
		String				provider)
		throws PGPException, NoSuchProviderException
	{
		KeyFactory						fact;
		
		try
		{
			switch (publicPk.getAlgorithm())
			{
			case RSA_ENCRYPT:
			case RSA_GENERAL:
				RSAPublicBCPGKey     rsaK = (RSAPublicBCPGKey)publicPk.getKey();
				RSAPublicKeySpec    rsaSpec = new RSAPublicKeySpec(rsaK.getModulus(), rsaK.getPublicExponent());
	
				fact = KeyFactory.getInstance("RSA", provider);
				
				return fact.generatePublic(rsaSpec);
			case DSA:
				DSAPublicBCPGKey	dsaK = (DSAPublicBCPGKey)publicPk.getKey();
				DSAPublicKeySpec 	dsaSpec = new DSAPublicKeySpec(dsaK.getY(), dsaK.getP(), dsaK.getQ(), dsaK.getG());
			
				fact = KeyFactory.getInstance("DSA", provider);
				
				return fact.generatePublic(dsaSpec);
			case ELGAMAL_ENCRYPT:
			case ELGAMAL_GENERAL:
				ElGamalPublicBCPGKey	elK = (ElGamalPublicBCPGKey)publicPk.getKey();
				ElGamalPublicKeySpec	elSpec = new ElGamalPublicKeySpec(elK.getY(), new ElGamalParameterSpec(elK.getP(), elK.getG()));
				
				fact = KeyFactory.getInstance("ElGamal", provider);
				
				return fact.generatePublic(elSpec);
			default:
				throw new PGPException("unknown public key algorithm encountered");
			}
		}
		catch (PGPException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new PGPException("exception constructing public key", e);
		}
	}
	
	public Iterator getUserIDs()
	{
		return ids.iterator();
	}
	
	public Iterator getSignaturesForID(
		String	id)
	{
		for (int i = 0; i != ids.size(); i++)
		{
			if (id.equals(ids.get(i)))
			{
				return ((ArrayList)idSigs.get(i)).iterator();
			}
		}
		
		return null;
	}
	
	public byte[] getEncoded() 
		throws IOException
	{
		ByteArrayOutputStream	bOut = new ByteArrayOutputStream();
		
		this.encode(bOut);
		
		return bOut.toByteArray();
	}
	
	public void encode(
		OutputStream	outStream) 
		throws IOException
	{
		BCPGOutputStream	out;
		
		if (outStream instanceof BCPGOutputStream)
		{
			out = (BCPGOutputStream)outStream;
		}
		else
		{
			out = new BCPGOutputStream(outStream);
		}
		
		out.writePacket(publicPk);
        
		for (int i = 0; i != ids.size(); i++)
		{
			String	id = (String)ids.get(i);
        	
			out.writePacket(new UserIDPacket(id));
        	
			ArrayList	sigs = (ArrayList)idSigs.get(i);
			for (int j = 0; j != sigs.size(); j++)
			{
				((PGPSignature)sigs.get(j)).encode(out);
			}
		}
	}
}
