//------------------------------------------------------------------------------
// <copyright file="WhitespaceRuleLookup.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">[....]</owner>
//------------------------------------------------------------------------------
using System;
using System.Xml;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using MS.Internal.Xml;
using System.Xml.Xsl.Qil;

namespace System.Xml.Xsl.Runtime {

    /// <summary>
    /// This class keeps a list of whitespace rules in order to determine whether whitespace children of particular
    /// elements should be stripped.
    /// </summary>
    internal class WhitespaceRuleLookup {
        private Hashtable qnames;
        private ArrayList wildcards;
        private InternalWhitespaceRule ruleTemp;
        private XmlNameTable nameTable;

        public WhitespaceRuleLookup() {
            this.qnames = new Hashtable();
            this.wildcards = new ArrayList();
        }

        /// <summary>
        /// Create a new lookup internal class from the specified WhitespaceRules. 
        /// </summary>
        public WhitespaceRuleLookup(IList<WhitespaceRule> rules) : this() {
            WhitespaceRule rule;
            InternalWhitespaceRule ruleInternal;
            Debug.Assert(rules != null);

            for (int i = rules.Count - 1; i >= 0; i--) {
                // Make a copy of each rule
                rule = rules[i];
                ruleInternal = new InternalWhitespaceRule(rule.LocalName, rule.NamespaceName, rule.PreserveSpace, -i);

                if (rule.LocalName == null || rule.NamespaceName == null) {
                    // Wildcard, so add to wildcards array
                    this.wildcards.Add(ruleInternal);
                }
                else {
                    // Exact name, so add to hashtable
                    this.qnames[ruleInternal] = ruleInternal;
                }
            }

            // Create a temporary (not thread-safe) InternalWhitespaceRule used for lookups
            this.ruleTemp = new InternalWhitespaceRule();
        }

        /// <summary>
        /// Atomize all names contained within the whitespace rules with respect to "nameTable".
        /// </summary>
        public void Atomize(XmlNameTable nameTable) {
            // If names are already atomized with respect to "nameTable", no need to do it again
            if (nameTable != this.nameTable) {
                this.nameTable = nameTable;

                foreach (InternalWhitespaceRule rule in this.qnames.Values)
                    rule.Atomize(nameTable);

                foreach (InternalWhitespaceRule rule in this.wildcards)
                    rule.Atomize(nameTable);
            }
        }

        /// <summary>
        /// Return true if elements of the specified name should have whitespace children stripped.
        /// NOTE: This method is not thread-safe.  Different threads should create their own copy of the
        /// WhitespaceRuleLookup object.  This allows all names to be atomized according to a private NameTable.
        /// </summary>
        public bool ShouldStripSpace(string localName, string namespaceName) {
            InternalWhitespaceRule qnameRule, wildcardRule;
            Debug.Assert(this.nameTable != null && this.ruleTemp != null);
            Debug.Assert(localName != null && (object) this.nameTable.Get(localName) == (object) localName);
            Debug.Assert(namespaceName != null && (object) this.nameTable.Get(namespaceName) == (object) namespaceName);

            this.ruleTemp.Init(localName, namespaceName, false, 0);

            // Lookup name in qnames table
            // If found, the name will be stripped unless there is a preserve wildcard with higher priority
            qnameRule = this.qnames[this.ruleTemp] as InternalWhitespaceRule;

            for (int pos = this.wildcards.Count; pos-- != 0;) {
                wildcardRule = this.wildcards[pos] as InternalWhitespaceRule;

                if (qnameRule != null) {
                    // If qname priority is greater than any subsequent wildcard's priority, then we're done
                    if (qnameRule.Priority > wildcardRule.Priority)
                        return !qnameRule.PreserveSpace;

                    // Don't bother to consider wildcards with the same PreserveSpace flag
                    if (qnameRule.PreserveSpace == wildcardRule.PreserveSpace)
                        continue;
                }

                if (wildcardRule.LocalName == null || (object) wildcardRule.LocalName == (object) localName) {
                    if (wildcardRule.NamespaceName == null || (object) wildcardRule.NamespaceName == (object) namespaceName) {
                        // Found wildcard match, so we're done (since wildcards are in priority order)
                        return !wildcardRule.PreserveSpace;
                    }
                }
            }

            return (qnameRule != null && !qnameRule.PreserveSpace);
        }

        private class InternalWhitespaceRule : WhitespaceRule {
            private int priority;       // Relative priority of this test
            private int hashCode;       // Cached hashcode

            public InternalWhitespaceRule() {
            }

            public InternalWhitespaceRule(string localName, string namespaceName, bool preserveSpace, int priority) {
                Init(localName, namespaceName, preserveSpace, priority);
            }

            public void Init(string localName, string namespaceName, bool preserveSpace, int priority) {
                base.Init(localName, namespaceName, preserveSpace);
                this.priority = priority;

                if (localName != null && namespaceName != null) {
                    this.hashCode = localName.GetHashCode();
                }
            }

            public void Atomize(XmlNameTable nameTable) {
                if (LocalName != null)
                    LocalName = nameTable.Add(LocalName);

                if (NamespaceName != null)
                    NamespaceName = nameTable.Add(NamespaceName);
            }

            public int Priority {
                get { return this.priority; }
            }

            public override int GetHashCode() {
                return this.hashCode;
            }

            public override bool Equals(object obj) {
                Debug.Assert(obj is InternalWhitespaceRule);
                InternalWhitespaceRule that = obj as InternalWhitespaceRule;

                Debug.Assert(LocalName != null && that.LocalName != null);
                Debug.Assert(NamespaceName != null && that.NamespaceName != null);
                
                // string == operator compares object references first and if they are not the same compares contents 
                // of the compared strings. As a result we do not have to cast strings to objects to force reference 
                // comparison for atomized LocalNames and NamespaceNames.
                return LocalName == that.LocalName && NamespaceName == that.NamespaceName;
            }
        }
    }
}
