using System;
using System.Collections;
using System.Globalization;
using System.IO;
using Org.BouncyCastle.Utilities.Collections;
namespace Org.BouncyCastle.Bcpg.OpenPgp
{
	/// 
	/// Often a PGP key ring file is made up of a succession of master/sub-key key rings.
	/// If you want to read an entire public key file in one hit this is the class for you.
	/// 
    public class PgpPublicKeyRingBundle
    {
        private readonly IDictionary pubRings;
        private readonly ArrayList order;
		private PgpPublicKeyRingBundle(
            IDictionary	pubRings,
            ArrayList	order)
        {
            this.pubRings = pubRings;
            this.order = order;
        }
		public PgpPublicKeyRingBundle(
            byte[] encoding)
            : this(new MemoryStream(encoding, false))
        {
        }
		/// Build a PgpPubliKeyRingBundle from the passed in input stream.
		/// Input stream containing data.
		/// If a problem parsing the stream occurs.
		/// If an object is encountered which isn't a PgpPublicKeyRing.
		public PgpPublicKeyRingBundle(
            Stream inputStream)
			: this(new PgpObjectFactory(inputStream).AllPgpObjects())
		{
        }
		public PgpPublicKeyRingBundle(
            IEnumerable e)
        {
			this.pubRings = new Hashtable();
			this.order = new ArrayList();
			foreach (object obj in e)
            {
				PgpPublicKeyRing pgpPub = obj as PgpPublicKeyRing;
				if (pgpPub == null)
				{
					throw new PgpException(obj.GetType().FullName + " found where PgpPublicKeyRing expected");
				}
				long key = pgpPub.GetPublicKey().KeyId;
                pubRings.Add(key, pgpPub);
				order.Add(key);
            }
        }
		[Obsolete("Use 'Count' property instead")]
		public int Size
		{
			get { return order.Count; }
		}
		/// Return the number of rings in this collection.
        public int Count
        {
			get { return order.Count; }
        }
		/// Allow enumeration of the public key rings making up this collection.
        public IEnumerable GetKeyRings()
        {
			return new EnumerableProxy(pubRings.Values);
        }
		/// Allow enumeration of the key rings associated with the passed in userId.
		/// The user ID to be matched.
		/// An IEnumerable of key rings which matched (possibly none).
		public IEnumerable GetKeyRings(
			string userId)
		{
			return GetKeyRings(userId, false, false);
		}
		/// Allow enumeration of the key rings associated with the passed in userId.
		/// The user ID to be matched.
		/// If true, userId need only be a substring of an actual ID string to match.
		/// An IEnumerable of key rings which matched (possibly none).
        public IEnumerable GetKeyRings(
            string	userId,
            bool	matchPartial)
        {
			return GetKeyRings(userId, matchPartial, false);
        }
		/// Allow enumeration of the key rings associated with the passed in userId.
		/// The user ID to be matched.
		/// If true, userId need only be a substring of an actual ID string to match.
		/// If true, case is ignored in user ID comparisons.
		/// An IEnumerable of key rings which matched (possibly none).
		public IEnumerable GetKeyRings(
			string	userId,
			bool	matchPartial,
			bool	ignoreCase)
		{
			IList rings = new ArrayList();
			if (ignoreCase)
			{
				userId = userId.ToLower(CultureInfo.InvariantCulture);
			}
			foreach (PgpPublicKeyRing pubRing in GetKeyRings())
			{
				foreach (string nextUserID in pubRing.GetPublicKey().GetUserIds())
				{
					string next = nextUserID;
					if (ignoreCase)
					{
						next = next.ToLower(CultureInfo.InvariantCulture);
					}
					if (matchPartial)
					{
						if (next.IndexOf(userId) > -1)
						{
							rings.Add(pubRing);
						}
					}
					else
					{
						if (next.Equals(userId))
						{
							rings.Add(pubRing);
						}
					}
				}
			}
			return new EnumerableProxy(rings);
		}
		/// Return the PGP public key associated with the given key id.
		/// The ID of the public key to return.
        public PgpPublicKey GetPublicKey(
            long keyId)
        {
            foreach (PgpPublicKeyRing pubRing in GetKeyRings())
            {
                PgpPublicKey pub = pubRing.GetPublicKey(keyId);
				if (pub != null)
                {
                    return pub;
                }
            }
			return null;
        }
		/// Return the public key ring which contains the key referred to by keyId
		/// The ID of the public key
        public PgpPublicKeyRing GetPublicKeyRing(
            long keyId)
        {
            if (pubRings.Contains(keyId))
            {
                return (PgpPublicKeyRing)pubRings[keyId];
            }
			foreach (PgpPublicKeyRing pubRing in GetKeyRings())
            {
                PgpPublicKey pub = pubRing.GetPublicKey(keyId);
                if (pub != null)
                {
                    return pubRing;
                }
            }
			return null;
        }
		public byte[] GetEncoded()
        {
            MemoryStream bOut = new MemoryStream();
			Encode(bOut);
			return bOut.ToArray();
        }
		public void Encode(
            Stream outStr)
        {
			BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
			foreach (long key in order)
            {
                PgpPublicKeyRing sec = (PgpPublicKeyRing) pubRings[key];
				sec.Encode(bcpgOut);
            }
        }
		/// 
		/// Return a new bundle containing the contents of the passed in bundle and
		/// the passed in public key ring.
		/// 
		/// The PgpPublicKeyRingBundle the key ring is to be added to.
		/// The key ring to be added.
		/// A new PgpPublicKeyRingBundle merging the current one with the passed in key ring.
		/// If the keyId for the passed in key ring is already present.
        public static PgpPublicKeyRingBundle AddPublicKeyRing(
            PgpPublicKeyRingBundle  bundle,
            PgpPublicKeyRing        publicKeyRing)
        {
            long key = publicKeyRing.GetPublicKey().KeyId;
			if (bundle.pubRings.Contains(key))
            {
                throw new ArgumentException("Bundle already contains a key with a keyId for the passed in ring.");
            }
			IDictionary newPubRings = new Hashtable(bundle.pubRings);
            ArrayList newOrder = new ArrayList(bundle.order);
			newPubRings[key] = publicKeyRing;
			newOrder.Add(key);
			return new PgpPublicKeyRingBundle(newPubRings, newOrder);
        }
		/// 
		/// Return a new bundle containing the contents of the passed in bundle with
		/// the passed in public key ring removed.
		/// 
		/// The PgpPublicKeyRingBundle the key ring is to be removed from.
		/// The key ring to be removed.
		/// A new PgpPublicKeyRingBundle not containing the passed in key ring.
		/// If the keyId for the passed in key ring is not present.
        public static PgpPublicKeyRingBundle RemovePublicKeyRing(
            PgpPublicKeyRingBundle	bundle,
            PgpPublicKeyRing		publicKeyRing)
        {
            long key = publicKeyRing.GetPublicKey().KeyId;
			if (!bundle.pubRings.Contains(key))
            {
                throw new ArgumentException("Bundle does not contain a key with a keyId for the passed in ring.");
            }
			IDictionary newPubRings = new Hashtable(bundle.pubRings);
            ArrayList newOrder = new ArrayList(bundle.order);
			newPubRings.Remove(key);
			newOrder.Remove(key);
			return new PgpPublicKeyRingBundle(newPubRings, newOrder);
        }
    }
}