using System;
using System.Collections;
using System.IO;
using System.Text;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Misc;
using Org.BouncyCastle.Asn1.Utilities;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Security.Certificates;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Encoders;
using Org.BouncyCastle.X509.Extension;

namespace Org.BouncyCastle.X509
{
    /// <summary>
    /// An Object representing an X509 Certificate.
    /// Has static methods for loading Certificates encoded in many forms that return X509Certificate Objects.
    /// </summary>
    public class X509Certificate
		:	X509ExtensionBase
//		, PKCS12BagAttributeCarrier
    {
        private readonly X509CertificateStructure c;
//        private Hashtable pkcs12Attributes = new Hashtable();
//        private ArrayList pkcs12Ordering = new ArrayList();
		private readonly BasicConstraints basicConstraints;
		private readonly bool[] keyUsage;

		protected X509Certificate()
		{
		}

		public X509Certificate(
			X509CertificateStructure c)
		{
			this.c = c;

			try
			{
				Asn1OctetString str = this.GetExtensionValue(new DerObjectIdentifier("2.5.29.19"));

				if (str != null)
				{
					basicConstraints = BasicConstraints.GetInstance(
						X509ExtensionUtilities.FromExtensionValue(str));
				}
			}
			catch (Exception e)
			{
				throw new CertificateParsingException("cannot construct BasicConstraints: " + e);
			}

			try
			{
				Asn1OctetString str = this.GetExtensionValue(new DerObjectIdentifier("2.5.29.15"));

				if (str != null)
				{
					DerBitString bits = DerBitString.GetInstance(
						X509ExtensionUtilities.FromExtensionValue(str));

					byte[] bytes = bits.GetBytes();
					int length = (bytes.Length * 8) - bits.PadBits;

					keyUsage = new bool[(length < 9) ? 9 : length];

					for (int i = 0; i != length; i++)
					{
//						keyUsage[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
						keyUsage[i] = (bytes[i / 8] & (0x80 >> (i % 8))) != 0;
					}
				}
				else
				{
					keyUsage = null;
				}
			}
			catch (Exception e)
			{
				throw new CertificateParsingException("cannot construct KeyUsage: " + e);
			}
		}

//		internal X509Certificate(
//			Asn1Sequence seq)
//        {
//            this.c = X509CertificateStructure.GetInstance(seq);
//        }

//		/// <summary>
//        /// Load certificate from byte array.
//        /// </summary>
//        /// <param name="encoded">Byte array containing encoded X509Certificate.</param>
//        public X509Certificate(
//            byte[] encoded)
//			: this((Asn1Sequence) new Asn1InputStream(encoded).ReadObject())
//		{
//        }
//
//        /// <summary>
//        /// Load certificate from Stream.
//        /// Must be positioned at start of certificate.
//        /// </summary>
//        /// <param name="input"></param>
//        public X509Certificate(
//            Stream input)
//			: this((Asn1Sequence) new Asn1InputStream(input).ReadObject())
//        {
//        }

		/// <summary>
        /// Return true if the current time is within the start and end times nominated on the certificate.
        /// </summary>
        /// <returns>true id certificate is valid for the current time.</returns>
        public virtual bool IsValidNow
        {
			get { return IsValid(DateTime.UtcNow); }
        }

		/// <summary>
        /// Return true if the nominated time is within the start and end times nominated on the certificate.
        /// </summary>
        /// <param name="time">The time to test validity against.</param>
        /// <returns>True if certificate is valid for nominated time.</returns>
        public virtual bool IsValid(
			DateTime time)
        {
            return time.CompareTo(NotBefore) >= 0 && time.CompareTo(NotAfter) <= 0;
        }

		/// <summary>
		/// Checks if the current date is within certificate's validity period.
		/// </summary>
		public virtual void CheckValidity()
		{
			this.CheckValidity(DateTime.UtcNow);
		}

		/// <summary>
		/// Checks if the given date is within certificate's validity period.
		/// </summary>
		/// <exception cref="CertificateExpiredException">if the certificate is expired by given date</exception>
		/// <exception cref="CertificateNotYetValidException">if the certificate is not yet valid on given date</exception>
		public virtual void CheckValidity(
			DateTime time)
		{
			if (time.CompareTo(NotAfter) > 0)
				throw new CertificateExpiredException("certificate expired on " + c.EndDate.GetTime());
			if (time.CompareTo(NotBefore) < 0)
				throw new CertificateNotYetValidException("certificate not valid until " + c.StartDate.GetTime());
		}

		/// <summary>
        /// Return the certificate's version.
        /// </summary>
        /// <returns>An integer whose value Equals the version of the cerficate.</returns>
        public virtual int Version
        {
            get { return c.Version; }
        }

		/// <summary>
        /// Return a <see cref="Org.BouncyCastle.Math.BigInteger">BigInteger</see> containing the serial number.
        /// </summary>
        /// <returns>The Serial number.</returns>
        public virtual BigInteger SerialNumber
        {
            get { return c.SerialNumber.Value; }
        }

		/// <summary>
        /// Get the Issuer Distinguished Name. (Who signed the certificate.)
        /// </summary>
        /// <returns>And X509Object containing name and value pairs.</returns>
//        public IPrincipal IssuerDN
		public virtual X509Name IssuerDN
		{
            get { return c.Issuer; }
        }

		/// <summary>
        /// Get the subject of this certificate.
        /// </summary>
        /// <returns>An X509Name object containing name and value pairs.</returns>
//        public IPrincipal SubjectDN
		public virtual X509Name SubjectDN
		{
            get { return c.Subject; }
        }

		/// <summary>
		/// The time that this certificate is valid from.
		/// </summary>
		/// <returns>A DateTime object representing that time in the local time zone.</returns>
		public virtual DateTime NotBefore
		{
			get { return c.StartDate.ToDateTime(); }
		}

		/// <summary>
        /// The time that this certificate is valid up to.
        /// </summary>
        /// <returns>A DateTime object representing that time in the local time zone.</returns>
        public virtual DateTime NotAfter
        {
			get { return c.EndDate.ToDateTime(); }
        }

		/// <summary>
		/// Return the Der encoded TbsCertificate data.
		/// This is the certificate component less the signature.
		/// To Get the whole certificate call the GetEncoded() member.
		/// </summary>
		/// <returns>A byte array containing the Der encoded Certificate component.</returns>
		public virtual byte[] GetTbsCertificate()
		{
			return c.TbsCertificate.GetDerEncoded();
		}

		/// <summary>
		/// The signature.
		/// </summary>
		/// <returns>A byte array containg the signature of the certificate.</returns>
		public virtual byte[] GetSignature()
		{
			return c.Signature.GetBytes();
		}

		/// <summary>
		/// A meaningful version of the Signature Algorithm. (EG SHA1WITHRSA)
		/// </summary>
		/// <returns>A sting representing the signature algorithm.</returns>
		public virtual string SigAlgName
		{
			get { return SignerUtilities.GetEncodingName(c.SignatureAlgorithm.ObjectID); }
		}

		/// <summary>
		/// Get the Signature Algorithms Object ID.
		/// </summary>
		/// <returns>A string containg a '.' separated object id.</returns>
		public virtual string SigAlgOid
		{
			get { return c.SignatureAlgorithm.ObjectID.Id; }
		}

		/// <summary>
		/// Get the signature algorithms parameters. (EG DSA Parameters)
		/// </summary>
		/// <returns>A byte array containing the Der encoded version of the parameters or null if there are none.</returns>
		public virtual byte[] GetSigAlgParams()
		{
			if (c.SignatureAlgorithm.Parameters != null)
			{
				return c.SignatureAlgorithm.Parameters.GetDerEncoded();
			}

			return null;
		}

		/// <summary>
		/// Get the issuers UID.
		/// </summary>
		/// <returns>A DerBitString.</returns>
		public virtual DerBitString IssuerUniqueID
		{
			get { return c.TbsCertificate.IssuerUniqueID; }
		}

		/// <summary>
		/// Get the subjects UID.
		/// </summary>
		/// <returns>A DerBitString.</returns>
		public virtual DerBitString SubjectUniqueID
		{
			get { return c.TbsCertificate.SubjectUniqueID; }
		}

		/// <summary>
		/// Get a key usage guidlines.
		/// </summary>
		public virtual bool[] GetKeyUsage()
		{
			return keyUsage == null ? null : (bool[]) keyUsage.Clone();
		}

		// TODO Replace with something that returns a list of DerObjectIdentifier
		public virtual IList GetExtendedKeyUsage()
		{
			Asn1OctetString str = this.GetExtensionValue(new DerObjectIdentifier("2.5.29.37"));

			if (str == null)
				return null;

			try
			{
				Asn1Sequence seq = Asn1Sequence.GetInstance(
					X509ExtensionUtilities.FromExtensionValue(str));

				ArrayList list = new ArrayList();

				foreach (DerObjectIdentifier oid in seq)
				{
					list.Add(oid.Id);
				}

				return list;
			}
			catch (Exception e)
			{
				throw new CertificateParsingException("error processing extended key usage extension", e);
			}
		}

		public virtual int GetBasicConstraints()
		{
			if (basicConstraints != null && basicConstraints.IsCA())
			{
				if (basicConstraints.PathLenConstraint == null)
				{
					return int.MaxValue;
				}

				return basicConstraints.PathLenConstraint.IntValue;
			}

			return -1;
		}

		public virtual ICollection GetSubjectAlternativeNames()
		{
			return GetAlternativeNames("2.5.29.17");
		}

		public virtual ICollection GetIssuerAlternativeNames()
		{
			return GetAlternativeNames("2.5.29.18");
		}

		protected virtual ICollection GetAlternativeNames(
			string oid)
		{
			Asn1OctetString altNames = GetExtensionValue(new DerObjectIdentifier(oid));

			if (altNames == null)
				return null;

			Asn1Object asn1Object = X509ExtensionUtilities.FromExtensionValue(altNames);

			GeneralNames gns = GeneralNames.GetInstance(asn1Object);

			ArrayList result = new ArrayList();
			foreach (GeneralName gn in gns.GetNames())
			{
				ArrayList entry = new ArrayList();
				entry.Add(gn.TagNo);
				entry.Add(gn.Name.ToString());
				result.Add(entry);
			}
			return result;
		}

		protected override X509Extensions GetX509Extensions()
		{
			return c.Version == 3
				?	c.TbsCertificate.Extensions
				:	null;
		}

		/// <summary>
		/// Get the public key of the subject of the certificate.
		/// </summary>
		/// <returns>The public key parameters.</returns>
		public virtual AsymmetricKeyParameter GetPublicKey()
		{
			return PublicKeyFactory.CreateKey(c.SubjectPublicKeyInfo);
		}

		/// <summary>
		/// Return a Der encoded version of this certificate.
		/// </summary>
		/// <returns>A byte array.</returns>
		public virtual byte[] GetEncoded()
		{
			return c.GetDerEncoded();
		}

		public override bool Equals(
			object obj)
		{
			if (obj == this)
				return true;

			X509Certificate other = obj as X509Certificate;

			if (other == null)
				return false;

			return c.Equals(other.c);

			// NB: May prefer this implementation of Equals if more than one certificate implementation in play
//			return Arrays.AreEqual(this.GetEncoded(), other.GetEncoded());
		}

		public override int GetHashCode()
		{
			return c.GetHashCode();
		}

//		public void setBagAttribute(
//			DERObjectIdentifier oid,
//			DEREncodable        attribute)
//		{
//			pkcs12Attributes.put(oid, attribute);
//			pkcs12Ordering.addElement(oid);
//		}
//
//		public DEREncodable getBagAttribute(
//			DERObjectIdentifier oid)
//		{
//			return (DEREncodable)pkcs12Attributes.get(oid);
//		}
//
//		public Enumeration getBagAttributeKeys()
//		{
//			return pkcs12Ordering.elements();
//		}

		public override string ToString()
		{
			StringBuilder buf = new StringBuilder();
			string nl = Platform.NewLine;

			buf.Append("  [0]         Version: ").Append(this.Version).Append(nl);
			buf.Append("         SerialNumber: ").Append(this.SerialNumber).Append(nl);
			buf.Append("             IssuerDN: ").Append(this.IssuerDN).Append(nl);
			buf.Append("           Start Date: ").Append(this.NotBefore).Append(nl);
			buf.Append("           Final Date: ").Append(this.NotAfter).Append(nl);
			buf.Append("            SubjectDN: ").Append(this.SubjectDN).Append(nl);
			buf.Append("           Public Key: ").Append(this.GetPublicKey()).Append(nl);
			buf.Append("  Signature Algorithm: ").Append(this.SigAlgName).Append(nl);

			byte[] sig = this.GetSignature();
			byte[] hex = Hex.Encode(sig, 0, 20);
			string ascii = Encoding.ASCII.GetString(hex, 0, hex.Length);
			buf.Append("            Signature: ").Append(ascii).Append(nl);

			for (int i = 20; i < sig.Length; i += 20)
			{
				int len = System.Math.Min(20, sig.Length - i);
				hex = Hex.Encode(sig, i, len);
				ascii = Encoding.ASCII.GetString(hex, 0, hex.Length);
				buf.Append("                       ").Append(ascii).Append(nl);
			}

			X509Extensions extensions = c.TbsCertificate.Extensions;

			if (extensions != null)
			{
				IEnumerator e = extensions.ExtensionOids.GetEnumerator();

				if (e.MoveNext())
				{
					buf.Append("       Extensions: \n");
				}

				do
				{
					DerObjectIdentifier oid = (DerObjectIdentifier)e.Current;
					X509Extension ext = extensions.GetExtension(oid);

					if (ext.Value != null)
					{
						byte[] octs = ext.Value.GetOctets();
						Asn1Object obj = Asn1Object.FromByteArray(octs);
						buf.Append("                       critical(").Append(ext.IsCritical).Append(") ");
						try
						{
							if (oid.Equals(X509Extensions.BasicConstraints))
							{
								buf.Append(BasicConstraints.GetInstance(obj));
							}
							else if (oid.Equals(X509Extensions.KeyUsage))
							{
								buf.Append(KeyUsage.GetInstance(obj));
							}
							else if (oid.Equals(MiscObjectIdentifiers.NetscapeCertType))
							{
								buf.Append(new NetscapeCertType((DerBitString) obj));
							}
							else if (oid.Equals(MiscObjectIdentifiers.NetscapeRevocationUrl))
							{
								buf.Append(new NetscapeRevocationUrl((DerIA5String) obj));
							}
							else if (oid.Equals(MiscObjectIdentifiers.VerisignCzagExtension))
							{
								buf.Append(new VerisignCzagExtension((DerIA5String) obj));
							}
							else
							{
								buf.Append(oid.Id);
								buf.Append(" value = ").Append(Asn1Dump.DumpAsString(obj));
								//buf.Append(" value = ").Append("*****").Append(nl);
							}
						}
						catch (Exception)
						{
							buf.Append(oid.Id);
							//buf.Append(" value = ").Append(new string(Hex.encode(ext.getValue().getOctets()))).Append(nl);
							buf.Append(" value = ").Append("*****");
						}
					}

					buf.Append(nl);
				}
				while (e.MoveNext());
			}

			return buf.ToString();
		}

		/// <summary>
		/// Verify the certificate's signature using the nominated public key.
		/// </summary>
		/// <param name="key">An appropriate public key parameter object, RsaPublicKeyParameters, DsaPublicKeyParameters or ECDsaPublicKeyParameters</param>
		/// <returns>True if the signature is valid.</returns>
		/// <exception cref="Exception">If key submitted is not of the above nominated types.</exception>
		public virtual void Verify(
			AsymmetricKeyParameter key)
		{
			string sigName = X509SignatureUtilities.GetSignatureName(c.SignatureAlgorithm);
			ISigner signature = SignerUtilities.GetSigner(sigName);

			CheckSignature(key, signature);
		}

		protected virtual void CheckSignature(
			AsymmetricKeyParameter	publicKey,
			ISigner					signature)
		{
			if (!c.SignatureAlgorithm.Equals(c.TbsCertificate.Signature))
			{
				throw new CertificateException("signature algorithm in TBS cert not same as outer cert");
			}

			Asn1Encodable parameters = c.SignatureAlgorithm.Parameters;

			X509SignatureUtilities.SetSignatureParameters(signature, parameters);

			signature.Init(false, publicKey);

			byte[] b = this.GetTbsCertificate();
			signature.BlockUpdate(b, 0, b.Length);

			byte[] sig = this.GetSignature();
			if (!signature.VerifySignature(sig))
			{
				throw new InvalidKeyException("Public key presented not for certificate signature");
			}
		}
	}
}
