//-----------------------------------------------------------------------
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//-----------------------------------------------------------------------
namespace System.IdentityModel.Tokens
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IdentityModel.Selectors;
    using System.Security.Claims;
    using System.Xml;
    /// 
    /// Defines a collection of SecurityTokenHandlers.
    /// 
    public class SecurityTokenHandlerCollection : Collection
    {
        internal static int defaultHandlerCollectionCount = 8;
        private Dictionary handlersByIdentifier = new Dictionary();
        private Dictionary handlersByType = new Dictionary();
        private SecurityTokenHandlerConfiguration configuration;
        private KeyInfoSerializer keyInfoSerializer;
        /// 
        /// Creates an instance of .
        /// Creates an empty set.
        /// 
        public SecurityTokenHandlerCollection()
            : this(new SecurityTokenHandlerConfiguration())
        {
        }
        /// 
        /// Creates an instance of .
        /// Creates an empty set.
        /// 
        /// The configuration to associate with the collection.
        public SecurityTokenHandlerCollection(SecurityTokenHandlerConfiguration configuration)
        {
            if (configuration == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("configuration");
            }
            this.configuration = configuration;
            this.keyInfoSerializer = new KeyInfoSerializer(true);
        }
        /// 
        /// Creates an instance of 
        /// 
        /// List of SecurityTokenHandlers to initialize from.
        /// 
        /// Do not use this constructor to attempt to clone an instance of a SecurityTokenHandlerCollection,
        /// use the Clone method instead.
        /// 
        public SecurityTokenHandlerCollection(IEnumerable handlers)
            : this(handlers, new SecurityTokenHandlerConfiguration())
        {
        }
        /// 
        /// Creates an instance of 
        /// 
        /// List of SecurityTokenHandlers to initialize from.
        /// The  in effect.
        /// 
        /// Do not use this constructor to attempt to clone an instance of a SecurityTokenHandlerCollection,
        /// use the Clone method instead.
        /// 
        public SecurityTokenHandlerCollection(IEnumerable handlers, SecurityTokenHandlerConfiguration configuration)
            : this(configuration)
        {
            if (handlers == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("handlers");
            }
            foreach (SecurityTokenHandler handler in handlers)
            {
                Add(handler);
            }
        }
        /// 
        /// Gets an instance of 
        /// 
        public SecurityTokenHandlerConfiguration Configuration
        {
            get { return this.configuration; }
        }
        /// 
        /// Gets the List of System.Type of the Token Handlers in this collection.
        /// 
        public IEnumerable TokenTypes
        {
            get { return this.handlersByType.Keys; }
        }
        /// 
        /// Gets the list of Token type Identifier of the Token Handlers.
        /// 
        public IEnumerable TokenTypeIdentifiers
        {
            get
            {
                return this.handlersByIdentifier.Keys;
            }
        }
        /// 
        /// Gets a Token Handler by its Token Type Identifier.
        /// 
        /// The Token Type Identfier string to search for.
        /// Instance of a SecurityTokenHandler.
        public SecurityTokenHandler this[string tokenTypeIdentifier]
        {
            get
            {
                if (string.IsNullOrEmpty(tokenTypeIdentifier))
                {
                    return null;
                }
                SecurityTokenHandler handler;
                this.handlersByIdentifier.TryGetValue(tokenTypeIdentifier, out handler);
                return handler;
            }
        }
        /// 
        /// Gets a Token Handler that can handle a given SecurityToken.
        /// 
        /// SecurityToken for which a Token Handler is requested.
        /// Instance of SecurityTokenHandler.
        public SecurityTokenHandler this[SecurityToken token]
        {
            get
            {
                if (null == token)
                {
                    return null;
                }
                return this[token.GetType()];
            }
        }
        /// 
        /// Gets a Token Handler based on the System.Type of the token.
        /// 
        /// System.Type of the Token that needs to be handled.
        /// Instance of SecurityTokenHandler.
        public SecurityTokenHandler this[Type tokenType]
        {
            get
            {
                SecurityTokenHandler handler = null;
                if (tokenType != null)
                {
                    this.handlersByType.TryGetValue(tokenType, out handler);
                }
                return handler;
            }
        }
        /// 
        /// Creates a system default collection of basic SecurityTokenHandlers, each of which has the system default configuration.
        /// The SecurityTokenHandlers in this collection must be configured with service specific data before they can be used.
        /// 
        /// A SecurityTokenHandlerCollection with default basic SecurityTokenHandlers.
        public static SecurityTokenHandlerCollection CreateDefaultSecurityTokenHandlerCollection()
        {
            return CreateDefaultSecurityTokenHandlerCollection(new SecurityTokenHandlerConfiguration());
        }
        /// 
        /// Creates a system default collection of basic SecurityTokenHandlers, each of which has the system default configuration.
        /// The SecurityTokenHandlers in this collection must be configured with service specific data before they can be used.
        /// 
        /// The configuration to associate with the collection.
        /// A SecurityTokenHandlerCollection with default basic SecurityTokenHandlers.
        public static SecurityTokenHandlerCollection CreateDefaultSecurityTokenHandlerCollection(SecurityTokenHandlerConfiguration configuration)
        {
            SecurityTokenHandlerCollection collection = new SecurityTokenHandlerCollection(new SecurityTokenHandler[] {
                                                                                                 new KerberosSecurityTokenHandler(),
                                                                                                 new RsaSecurityTokenHandler(),
                                                                                                 new SamlSecurityTokenHandler(), 
                                                                                                 new Saml2SecurityTokenHandler(),
                                                                                                 new WindowsUserNameSecurityTokenHandler(),
                                                                                                 new X509SecurityTokenHandler(),
                                                                                                 new EncryptedSecurityTokenHandler(),
                                                                                                 new SessionSecurityTokenHandler(),
                                                                                                },
                                                                                             configuration);
            defaultHandlerCollectionCount = collection.Count;
            return collection;
        }
        internal SecurityTokenSerializer KeyInfoSerializer
        {
            get { return this.keyInfoSerializer; }
        }
        /// 
        /// Adds a new handler or replace the existing handler with the same token type identifier 
        /// with with the new handler.
        /// 
        /// The SecurityTokenHandler to add or replace
        /// When the input parameter is null.
        public void AddOrReplace(SecurityTokenHandler handler)
        {
            if (handler == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("handler");
            }
            // Remove the old one if it exists
            Type tokenType = handler.TokenType;
            if (tokenType != null && this.handlersByType.ContainsKey(tokenType))
            {
                Remove(this[tokenType]);
            }
            else
            {
                string[] identifiers = handler.GetTokenTypeIdentifiers();
                if (identifiers != null)
                {
                    foreach (string tokenTypeIdentifier in identifiers)
                    {
                        if (tokenTypeIdentifier != null && this.handlersByIdentifier.ContainsKey(tokenTypeIdentifier))
                        {
                            Remove(this[tokenTypeIdentifier]);
                            break;
                        }
                    }
                }
            }
            // Add the new handler in the collection
            Add(handler);
        }
        /// 
        /// Checks if a token can be read using the SecurityTokenHandlers.
        /// 
        /// XmlReader pointing at token.
        /// True if the token can be read, false otherwise
        /// The input argument 'reader' is null.
        public bool CanReadToken(XmlReader reader)
        {
            if (reader == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
            }
            foreach (SecurityTokenHandler handler in this)
            {
                if (null != handler && handler.CanReadToken(reader))
                {
                    return true;
                }
            }
            return false;
        }
        /// 
        /// Checks if a token can be read using the SecurityTokenHandlers.
        /// 
        /// The token string thats needs to be read.
        /// True if the token can be read, false otherwise
        /// The input argument 'tokenString' is null or empty.
        public bool CanReadToken(string tokenString)
        {
            if (String.IsNullOrEmpty(tokenString))
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("tokenString");
            }
            foreach (SecurityTokenHandler handler in this)
            {
                if (null != handler && handler.CanReadToken(tokenString))
                {
                    return true;
                }
            }
            return false;
        }
        
        /// 
        /// Checks if a token can be written using the SecurityTokenHandlers.
        /// 
        /// SecurityToken to be written out.
        /// True if the token can be written, false otherwise
        /// The input argument 'token' is null.
        public bool CanWriteToken(SecurityToken token)
        {
            if (token == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
            }
            SecurityTokenHandler handler = this[token];
            if (null != handler && handler.CanWriteToken)
            {
                return true;
            }
            return false;
        }
        /// 
        /// Creates a SecurityToken from the given SecurityTokenDescriptor using the list of
        /// SecurityTokenHandlers.
        /// 
        /// SecurityTokenDescriptor for the token to be created.
        /// Instance of 
        /// The input argument 'tokenDescriptor' is null.
        public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor)
        {
            if (tokenDescriptor == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
            }
            SecurityTokenHandler handler = this[tokenDescriptor.TokenType];
            if (null == handler)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4020, tokenDescriptor.TokenType)));
            }
            return handler.CreateToken(tokenDescriptor);
        }
        /// 
        /// Validates a given token using the SecurityTokenHandlers.
        /// 
        /// The SecurityToken to be validated.
        /// A  of  representing the identities contained in the token.
        /// The input argument 'token' is null.
        /// A  cannot be found that can validate the .                
        public ReadOnlyCollection ValidateToken(SecurityToken token)
        {
            if (token == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
            }
            SecurityTokenHandler handler = this[token];
            if (null == handler || !handler.CanValidateToken)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4011, token.GetType())));
            }
            return handler.ValidateToken(token);
        }
        /// 
        /// Reads a token using the TokenHandlers.
        /// 
        /// XmlReader pointing at token.
        /// Instance of 
        /// The input argument 'reader' is null.
        public SecurityToken ReadToken(XmlReader reader)
        {
            if (reader == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
            }
            foreach (SecurityTokenHandler handler in this)
            {
                if (null != handler && handler.CanReadToken(reader))
                {
                    return handler.ReadToken(reader);
                }
            }
            return null;
        }
        /// 
        /// Reads a token using the TokenHandlers.
        /// 
        /// The token string to be deserialized.
        /// Instance of 
        /// The input argument 'tokenString' is null or empty.
        public SecurityToken ReadToken(string tokenString)
        {
            if (String.IsNullOrEmpty(tokenString))
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("tokenString");
            }
            foreach (SecurityTokenHandler handler in this)
            {
                if (null != handler && handler.CanReadToken(tokenString))
                {
                    return handler.ReadToken(tokenString);
                }
            }
            return null;
        }
        /// 
        /// Writes a given SecurityToken to the XmlWriter.
        /// 
        /// XmlWriter to write the token into.
        /// SecurityToken to be written out.
        /// The input argument 'writer' or 'token' is null.
        public void WriteToken(XmlWriter writer, SecurityToken token)
        {
            if (writer == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
            }
            if (token == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
            }
            SecurityTokenHandler handler = this[token];
            if (null == handler || !handler.CanWriteToken)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4010, token.GetType())));
            }
            handler.WriteToken(writer, token);
        }
        /// 
        /// Writes a given SecurityToken to a string.
        /// 
        /// SecurityToken to be written out.
        /// The serialized token.
        /// The input argument 'token' is null.
        public string WriteToken(SecurityToken token)
        {
            if (token == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
            }
            SecurityTokenHandler handler = this[token];
            if (null == handler || !handler.CanWriteToken)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4010, token.GetType())));
            }
            return handler.WriteToken(token);
        }
        /// 
        /// Override. (Inherited from Collection<T>"/>
        /// 
        protected override void ClearItems()
        {
            base.ClearItems();
            this.handlersByIdentifier.Clear();
            this.handlersByType.Clear();
        }
        /// 
        /// Override. (Inherited from Collection<T>"/>
        /// 
        /// The zero-based index at which item should be inserted.
        /// The object to insert. The value can be null for reference types.
        protected override void InsertItem(int index, SecurityTokenHandler item)
        {
            base.InsertItem(index, item);
            try
            {
                this.AddToDictionaries(item);
            }
            catch
            {
                base.RemoveItem(index);
                throw;
            }
        }
        /// 
        /// Override. (Inherited from Collection<T>"/>
        /// 
        /// The zero-based index of the element to remove.
        protected override void RemoveItem(int index)
        {
            SecurityTokenHandler removedItem = Items[index];
            base.RemoveItem(index);
            this.RemoveFromDictionaries(removedItem);
        }
        /// 
        /// Override. (Inherited from Collection<T>"/>
        /// 
        /// The zero-based index of the element to replace.
        /// The new value for the element at the specified index. The value can be null for reference types.
        protected override void SetItem(int index, SecurityTokenHandler item)
        {
            SecurityTokenHandler replaced = Items[index];
            base.SetItem(index, item);
            this.RemoveFromDictionaries(replaced);
            try
            {
                this.AddToDictionaries(item);
            }
            catch
            {
                base.SetItem(index, replaced);
                this.AddToDictionaries(replaced);
                throw;
            }
        }
        public bool CanReadKeyIdentifierClause(XmlReader reader)
        {
            if (reader == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
            }
            return CanReadKeyIdentifierClauseCore(reader);
        }
        /// 
        /// Checks if the wrapped SecurityTokenHandler or the base WSSecurityTokenSerializer can read the 
        /// SecurityKeyIdentifierClause.
        /// 
        /// Reader to a SecurityKeyIdentifierClause.
        /// 'True' if the SecurityKeyIdentifierCause can be read.
        /// The input parameter 'reader' is null.
        protected virtual bool CanReadKeyIdentifierClauseCore(XmlReader reader)
        {
            if (reader == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
            }
            foreach (SecurityTokenHandler securityTokenHandler in this)
            {
                if (securityTokenHandler.CanReadKeyIdentifierClause(reader))
                {
                    return true;
                }
            }
            return false;
        }
        public SecurityKeyIdentifierClause ReadKeyIdentifierClause(XmlReader reader)
        {
            if (reader == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
            }
            return ReadKeyIdentifierClauseCore(reader);
        }
        /// 
        /// Deserializes a SecurityKeyIdentifierClause from the given reader.
        /// 
        /// XmlReader to a SecurityKeyIdentifierClause.
        /// The deserialized SecurityKeyIdentifierClause.
        /// The input parameter 'reader' is null.
        protected virtual SecurityKeyIdentifierClause ReadKeyIdentifierClauseCore(XmlReader reader)
        {
            if (reader == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
            }
            foreach (SecurityTokenHandler securityTokenHandler in this)
            {
                if (securityTokenHandler.CanReadKeyIdentifierClause(reader))
                {
                    return securityTokenHandler.ReadKeyIdentifierClause(reader);
                }
            }
            return this.keyInfoSerializer.ReadKeyIdentifierClause(reader);
        }
        public void WriteKeyIdentifierClause(XmlWriter writer, SecurityKeyIdentifierClause keyIdentifierClause)
        {
            if (writer == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
            }
            if (keyIdentifierClause == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("keyIdentifierClause");
            }
            WriteKeyIdentifierClauseCore(writer, keyIdentifierClause);
        }
        /// 
        /// Serializes the given SecurityKeyIdentifierClause in a XmlWriter.
        /// 
        /// XmlWriter to write into.
        /// SecurityKeyIdentifierClause to be written.
        /// The input parameter 'writer' or 'keyIdentifierClause' is null.
        protected virtual void WriteKeyIdentifierClauseCore(XmlWriter writer, SecurityKeyIdentifierClause keyIdentifierClause)
        {
            if (writer == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
            }
            if (keyIdentifierClause == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("keyIdentifierClause");
            }
            foreach (SecurityTokenHandler securityTokenHandler in this)
            {
                if (securityTokenHandler.CanWriteKeyIdentifierClause(keyIdentifierClause))
                {
                    securityTokenHandler.WriteKeyIdentifierClause(writer, keyIdentifierClause);
                    return;
                }
            }
            this.keyInfoSerializer.WriteKeyIdentifierClause(writer, keyIdentifierClause);
        }
        private void AddToDictionaries(SecurityTokenHandler handler)
        {
            if (handler == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("handler");
            }
            bool firstSucceeded = false;
            string[] identifiers = handler.GetTokenTypeIdentifiers();
            if (identifiers != null)
            {
                foreach (string typeId in identifiers)
                {
                    if (typeId != null)
                    {
                        this.handlersByIdentifier.Add(typeId, handler);
                        firstSucceeded = true;
                    }
                }
            }
            Type type = handler.TokenType;
            if (handler.TokenType != null)
            {
                try
                {
                    this.handlersByType.Add(type, handler);
                }
                catch
                {
                    if (firstSucceeded)
                    {
                        this.RemoveFromDictionaries(handler);
                    }
                    throw;
                }
            }
            // Ensure that the handler knows which collection it is in.
            handler.ContainingCollection = this;
            // Propagate this collection's STH configuration to the handler
            // if the handler's configuration is unset.
            if (handler.Configuration == null)
            {
                handler.Configuration = this.configuration;
            }
        }
        private void RemoveFromDictionaries(SecurityTokenHandler handler)
        {
            string[] identifiers = handler.GetTokenTypeIdentifiers();
            if (identifiers != null)
            {
                foreach (string typeId in identifiers)
                {
                    if (typeId != null)
                    {
                        this.handlersByIdentifier.Remove(typeId);
                    }
                }
            }
            Type type = handler.TokenType;
            if (type != null && this.handlersByType.ContainsKey(type))
            {
                this.handlersByType.Remove(type);
            }
            // Ensure that the handler knows that it is no longer
            // in a collection.
            handler.ContainingCollection = null;
            handler.Configuration = null;
        }
    }
}