using System;
using System.IO;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Date;
namespace Org.BouncyCastle.Bcpg.OpenPgp
{
	/// A PGP signature object.
    public class PgpSignature
    {
        public const int BinaryDocument = 0x00;
        public const int CanonicalTextDocument = 0x01;
        public const int StandAlone = 0x02;
        public const int DefaultCertification = 0x10;
        public const int NoCertification = 0x11;
        public const int CasualCertification = 0x12;
        public const int PositiveCertification = 0x13;
        public const int SubkeyBinding = 0x18;
        public const int DirectKey = 0x1f;
        public const int KeyRevocation = 0x20;
        public const int SubkeyRevocation = 0x28;
        public const int CertificationRevocation = 0x30;
        public const int Timestamp = 0x40;
        private readonly SignaturePacket	sigPck;
        private readonly int				signatureType;
        private readonly TrustPacket		trustPck;
		private ISigner	sig;
		private byte	lastb; // Initial value anything but '\r'
		internal PgpSignature(
            BcpgInputStream bcpgInput)
            : this((SignaturePacket)bcpgInput.ReadPacket())
        {
        }
		internal PgpSignature(
            SignaturePacket sigPacket)
			: this(sigPacket, null)
        {
        }
        internal PgpSignature(
            SignaturePacket	sigPacket,
            TrustPacket		trustPacket)
        {
			if (sigPacket == null)
				throw new ArgumentNullException("sigPacket");
			this.sigPck = sigPacket;
			this.signatureType = sigPck.SignatureType;
			this.trustPck = trustPacket;
        }
		private void GetSig()
        {
            this.sig = SignerUtilities.GetSigner(
				PgpUtilities.GetSignatureName(sigPck.KeyAlgorithm, sigPck.HashAlgorithm));
        }
		/// The OpenPGP version number for this signature.
		public int Version
		{
			get { return sigPck.Version; }
		}
		/// The key algorithm associated with this signature.
		public PublicKeyAlgorithmTag KeyAlgorithm
		{
			get { return sigPck.KeyAlgorithm; }
		}
		/// The hash algorithm associated with this signature.
		public HashAlgorithmTag HashAlgorithm
		{
			get { return sigPck.HashAlgorithm; }
		}
		public void InitVerify(
            PgpPublicKey pubKey)
        {
			lastb = 0;
            if (sig == null)
            {
                GetSig();
            }
            try
            {
                sig.Init(false, pubKey.GetKey());
            }
            catch (InvalidKeyException e)
            {
                throw new PgpException("invalid key.", e);
            }
        }
        public void Update(
            byte b)
        {
            if (signatureType == CanonicalTextDocument)
            {
				doCanonicalUpdateByte(b);
            }
            else
            {
                sig.Update(b);
            }
        }
		private void doCanonicalUpdateByte(
			byte b)
		{
			if (b == '\r')
			{
				doUpdateCRLF();
			}
			else if (b == '\n')
			{
				if (lastb != '\r')
				{
					doUpdateCRLF();
				}
			}
			else
			{
				sig.Update(b);
			}
			lastb = b;
		}
		private void doUpdateCRLF()
		{
			sig.Update((byte)'\r');
			sig.Update((byte)'\n');
		}
		public void Update(
            params byte[] bytes)
        {
			Update(bytes, 0, bytes.Length);
        }
		public void Update(
            byte[]	bytes,
            int		off,
            int		length)
        {
            if (signatureType == CanonicalTextDocument)
            {
                int finish = off + length;
				for (int i = off; i != finish; i++)
                {
                    doCanonicalUpdateByte(bytes[i]);
                }
            }
            else
            {
                sig.BlockUpdate(bytes, off, length);
            }
        }
		public bool Verify()
        {
            byte[] trailer = GetSignatureTrailer();
            sig.BlockUpdate(trailer, 0, trailer.Length);
			return sig.VerifySignature(GetSignature());
        }
		private void UpdateWithIdData(
			int		header,
			byte[]	idBytes)
		{
			this.Update(
				(byte) header,
				(byte)(idBytes.Length >> 24),
				(byte)(idBytes.Length >> 16),
				(byte)(idBytes.Length >> 8),
				(byte)(idBytes.Length));
			this.Update(idBytes);
		}
		private void UpdateWithPublicKey(
			PgpPublicKey key)
		{
			byte[] keyBytes = GetEncodedPublicKey(key);
			this.Update(
				(byte) 0x99,
				(byte)(keyBytes.Length >> 8),
				(byte)(keyBytes.Length));
			this.Update(keyBytes);
		}
		/// 
		/// Verify the signature as certifying the passed in public key as associated
		/// with the passed in user attributes.
		/// 
		/// User attributes the key was stored under.
		/// The key to be verified.
		/// True, if the signature matches, false otherwise.
		public bool VerifyCertification(
			PgpUserAttributeSubpacketVector	userAttributes,
			PgpPublicKey					key)
		{
			UpdateWithPublicKey(key);
			//
			// hash in the userAttributes
			//
			try
			{
				MemoryStream bOut = new MemoryStream();
				foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray())
				{
					packet.Encode(bOut);
				}
				UpdateWithIdData(0xd1, bOut.ToArray());
			}
			catch (IOException e)
			{
				throw new PgpException("cannot encode subpacket array", e);
			}
			this.Update(sigPck.GetSignatureTrailer());
			return sig.VerifySignature(this.GetSignature());
		}
		/// 
		/// Verify the signature as certifying the passed in public key as associated
		/// with the passed in ID.
		/// 
		/// ID the key was stored under.
		/// The key to be verified.
		/// True, if the signature matches, false otherwise.
        public bool VerifyCertification(
            string			id,
            PgpPublicKey	key)
        {
			UpdateWithPublicKey(key);
			//
            // hash in the id
            //
			UpdateWithIdData(0xb4, Strings.ToByteArray(id));
			Update(sigPck.GetSignatureTrailer());
			return sig.VerifySignature(GetSignature());
        }
		/// Verify a certification for the passed in key against the passed in master key.
		/// The key we are verifying against.
		/// The key we are verifying.
		/// True, if the certification is valid, false otherwise.
        public bool VerifyCertification(
            PgpPublicKey	masterKey,
            PgpPublicKey	pubKey)
        {
			UpdateWithPublicKey(masterKey);
			UpdateWithPublicKey(pubKey);
			Update(sigPck.GetSignatureTrailer());
			return sig.VerifySignature(GetSignature());
        }
		/// Verify a key certification, such as revocation, for the passed in key.
		/// The key we are checking.
		/// True, if the certification is valid, false otherwise.
        public bool VerifyCertification(
            PgpPublicKey pubKey)
        {
            if (SignatureType != KeyRevocation
                && SignatureType != SubkeyRevocation)
            {
                throw new InvalidOperationException("signature is not a key signature");
            }
			UpdateWithPublicKey(pubKey);
            Update(sigPck.GetSignatureTrailer());
			return sig.VerifySignature(GetSignature());
        }
		public int SignatureType
        {
			get { return sigPck.SignatureType; }
        }
		/// The ID of the key that created the signature.
        public long KeyId
        {
            get { return sigPck.KeyId; }
        }
		[Obsolete("Use 'CreationTime' property instead")]
		public DateTime GetCreationTime()
		{
			return CreationTime;
		}
		/// The creation time of this signature.
        public DateTime CreationTime
        {
			get { return DateTimeUtilities.UnixMsToDateTime(sigPck.CreationTime); }
        }
		public byte[] GetSignatureTrailer()
        {
            return sigPck.GetSignatureTrailer();
        }
		public PgpSignatureSubpacketVector GetHashedSubPackets()
        {
            return createSubpacketVector(sigPck.GetHashedSubPackets());
        }
		public PgpSignatureSubpacketVector GetUnhashedSubPackets()
        {
            return createSubpacketVector(sigPck.GetUnhashedSubPackets());
        }
		private PgpSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks)
		{
			return pcks == null ? null : new PgpSignatureSubpacketVector(pcks);
		}
		public byte[] GetSignature()
        {
            MPInteger[] sigValues = sigPck.GetSignature();
            byte[] signature;
			if (sigValues != null)
			{
				if (sigValues.Length == 1)    // an RSA signature
				{
					signature = sigValues[0].Value.ToByteArrayUnsigned();
				}
				else
				{
					try
					{
						signature = new DerSequence(
							new DerInteger(sigValues[0].Value),
							new DerInteger(sigValues[1].Value)).GetEncoded();
					}
					catch (IOException e)
					{
						throw new PgpException("exception encoding DSA sig.", e);
					}
				}
			}
			else
			{
				signature = sigPck.GetSignatureBytes();
			}
			return signature;
        }
		// TODO Handle the encoding stuff by subclassing BcpgObject?
		public byte[] GetEncoded()
        {
            MemoryStream bOut = new MemoryStream();
			Encode(bOut);
			return bOut.ToArray();
        }
		public void Encode(
            Stream outStream)
        {
            BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStream);
			bcpgOut.WritePacket(sigPck);
			if (trustPck != null)
            {
                bcpgOut.WritePacket(trustPck);
            }
        }
		private byte[] GetEncodedPublicKey(
			PgpPublicKey pubKey) 
		{
			try
			{
				return pubKey.publicPk.GetEncodedContents();
			}
			catch (IOException e)
			{
				throw new PgpException("exception preparing key.", e);
			}
		}
    }
}