// AttributesImpl.java - default implementation of Attributes.
// http://www.saxproject.org
// Written by David Megginson
// NO WARRANTY!  This class is in the public domain.
// $Id: AttributesImpl.java,v 1.9 2002/01/30 20:52:24 dbrownell Exp $

using System;

namespace Sax.Helpers
{
    /// <summary>
    /// Default implementation of the <see cref="Attributes"/> interface.
    /// </summary>
    /// <remarks>
    /// <em>This module, both source code and documentation, is in the
    /// Public Domain, and comes with<strong> NO WARRANTY</strong>.</em>
    /// See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
    /// for further information.
    /// <para/>
    /// This class provides a default implementation of the SAX2
    /// <see cref="Attributes"/> interface, with the
    /// addition of manipulators so that the list can be modified or
    /// reused.
    /// <para/>There are two typical uses of this class:
    /// <list type="bullet">
    /// <item><description>to take a persistent snapshot of an Attributes object
    ///  in a <see cref="IContentHandler.StartElement(string, string, string, IAttributes)"/> event; or</description></item>
    /// <item><description>to construct or modify an Attributes object in a SAX2 driver or filter.</description></item>
    /// </list>
    /// <para/>
    /// This class replaces the now-deprecated SAX1 AttributeListImpl 
    /// class; in addition to supporting the updated Attributes
    /// interface rather than the deprecated IAttributeList 
    /// interface, it also includes a much more efficient
    /// implementation using a single array rather than a set of Vectors.
    /// </remarks>
    /// <since>SAX 2.0</since>
    /// <author>David Megginson</author>
    /// <version>2.0.1 (sax2r2)</version>
    public class Attributes : IAttributes
    {
        ////////////////////////////////////////////////////////////////////
        // Constructors.
        ////////////////////////////////////////////////////////////////////


        /// <summary>
        /// Construct a new, empty <see cref="Attributes"/> object.
        /// </summary>
        public Attributes()
        {
            length = 0;
            data = null;
        }

        /// <summary>
        /// Copy an existing Attributes object.
        /// <para/>
        /// This constructor is especially useful inside a
        /// <see cref="IContentHandler.StartElement(string, string, string, IAttributes)"/>.
        /// </summary>
        /// <param name="atts">The existing <see cref="Attributes"/> object.</param>
        public Attributes(IAttributes atts)
        {
            SetAttributes(atts);
        }

        ////////////////////////////////////////////////////////////////////
        // Implementation of org.xml.sax.Attributes.
        ////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Return the number of attributes in the list.
        /// </summary>
        /// <seealso cref="Attributes.Length"/>
        public virtual int Length => length;

        /// <summary>
        /// Return an attribute's Namespace URI.
        /// </summary>
        /// <param name="index">The attribute's index (zero-based).</param>
        /// <returns>The Namespace URI, the empty string if none is
        /// available, or null if the index is out of range.</returns>
        /// <seealso cref="Attributes.GetURI(int)"/>
        public virtual string GetURI(int index)
        {
            if (index >= 0 && index < length)
            {
                return data[index * 5];
            }
            else
            {
                return null;
            }
        }


        /// <summary>
        /// Return an attribute's local name.
        /// </summary>
        /// <param name="index">The attribute's index (zero-based).</param>
        /// <returns>The attribute's local name, the empty string if none is available, or null if the index if out of range.</returns>
        /// <seealso cref="Attributes.GetLocalName(int)"/>
        public virtual string GetLocalName(int index)
        {
            if (index >= 0 && index < length)
            {
                return data[index * 5 + 1];
            }
            else
            {
                return null;
            }
        }


        /// <summary>
        /// Return an attribute's qualified (prefixed) name.
        /// </summary>
        /// <param name="index">The attribute's index (zero-based).</param>
        /// <returns>The attribute's qualified name, the empty string if
        /// none is available, or null if the index is out of bounds.</returns>
        /// <seealso cref="Attributes.GetQName(int)"/>
        public virtual string GetQName(int index)
        {
            if (index >= 0 && index < length)
            {
                return data[index * 5 + 2];
            }
            else
            {
                return null;
            }
        }


        /// <summary>
        /// Return an attribute's type by index.
        /// </summary>
        /// <param name="index">The attribute's index (zero-based).</param>
        /// <returns>The attribute's type, "CDATA" if the type is unknown, or null
        /// if the index is out of bounds.</returns>
        /// <seealso cref="Attributes.GetType(int)"/>
        public virtual string GetType(int index)
        {
            if (index >= 0 && index < length)
            {
                return data[index * 5 + 3];
            }
            else
            {
                return null;
            }
        }


        /// <summary>
        /// Return an attribute's value by index.
        /// </summary>
        /// <param name="index">The attribute's index (zero-based).</param>
        /// <returns>The attribute's value or null if the index is out of bounds.</returns>
        /// <seealso cref="Attributes.GetValue(int)"/>
        public virtual string GetValue(int index)
        {
            if (index >= 0 && index < length)
            {
                return data[index * 5 + 4];
            }
            else
            {
                return null;
            }
        }


        /// <summary>
        /// Look up an attribute's index by Namespace name.
        /// </summary>
        /// <remarks>In many cases, it will be more efficient to look up the name once and
        /// use the index query methods rather than using the name query methods
        /// repeatedly.</remarks>
        /// <param name="uri">The attribute's Namespace URI, or the empty
        /// string if none is available.</param>
        /// <param name="localName">The attribute's local name.</param>
        /// <returns>The attribute's index, or -1 if none matches.</returns>
        /// <seealso cref="Attributes.GetIndex(string, string)"/>
        public virtual int GetIndex(string uri, string localName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i].Equals(uri, StringComparison.Ordinal) && data[i + 1].Equals(localName, StringComparison.Ordinal))
                {
                    return i / 5;
                }
            }
            return -1;
        }


        /// <summary>
        /// Look up an attribute's index by qualified (prefixed) name.
        /// </summary>
        /// <param name="qName">The qualified name.</param>
        /// <returns>The attribute's index, or -1 if none matches.</returns>
        /// <seealso cref="Attributes.GetIndex(string)"/>
        public virtual int GetIndex(string qName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i + 2].Equals(qName, StringComparison.Ordinal))
                {
                    return i / 5;
                }
            }
            return -1;
        }


        /// <summary>
        /// Look up an attribute's type by Namespace-qualified name.
        /// </summary>
        /// <param name="uri">The Namespace URI, or the empty string for a name
        /// with no explicit Namespace URI.</param>
        /// <param name="localName">The local name.</param>
        /// <returns>The attribute's type, or null if there is no matching attribute.</returns>
        /// <seealso cref="Attributes.GetType(string, string)"/>
        public virtual string GetType(string uri, string localName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i].Equals(uri, StringComparison.Ordinal) && data[i + 1].Equals(localName, StringComparison.Ordinal))
                {
                    return data[i + 3];
                }
            }
            return null;
        }


        /// <summary>
        /// Look up an attribute's type by qualified (prefixed) name.
        /// </summary>
        /// <param name="qName">The qualified name.</param>
        /// <returns>The attribute's type, or null if there is no
        /// matching attribute.</returns>
        /// <seealso cref="Attributes.GetType(string)"/>
        public virtual string GetType(string qName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i + 2].Equals(qName, StringComparison.Ordinal))
                {
                    return data[i + 3];
                }
            }
            return null;
        }


        /// <summary>
        /// Look up an attribute's value by Namespace-qualified name.
        /// </summary>
        /// <param name="uri">The Namespace URI, or the empty string for a name
        /// with no explicit Namespace URI.</param>
        /// <param name="localName">The local name.</param>
        /// <returns>The attribute's value, or null if there is no matching attribute.</returns>
        /// <seealso cref="Attributes.GetValue(string, string)"/>
        public virtual string GetValue(string uri, string localName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i].Equals(uri, StringComparison.Ordinal) && data[i + 1].Equals(localName, StringComparison.Ordinal))
                {
                    return data[i + 4];
                }
            }
            return null;
        }


        /// <summary>
        /// Look up an attribute's value by qualified (prefixed) name.
        /// </summary>
        /// <param name="qName">The qualified name.</param>
        /// <returns>The attribute's value, or null if there is no
        /// matching attribute.</returns>
        /// <seealso cref="Attributes.GetValue(string)"/>
        public virtual string GetValue(string qName)
        {
            int max = length * 5;
            for (int i = 0; i < max; i += 5)
            {
                if (data[i + 2].Equals(qName, StringComparison.Ordinal))
                {
                    return data[i + 4];
                }
            }
            return null;
        }



        ////////////////////////////////////////////////////////////////////
        // Manipulators.
        ////////////////////////////////////////////////////////////////////


        /// <summary>
        /// Clear the attribute list for reuse.
        /// <para/>
        /// Note that little memory is freed by this call:
        /// the current array is kept so it can be 
        /// reused.
        /// </summary>
        public virtual void Clear()
        {
            if (data != null)
            {
                for (int i = 0; i < (length * 5); i++)
                    data[i] = null;
            }
            length = 0;
        }

        /// <summary>
        /// Copy an entire Attributes object.
        /// <para/>
        /// It may be more efficient to reuse an existing object
        /// rather than constantly allocating new ones.
        /// </summary>
        /// <param name="atts">The attributes to copy.</param>
        public virtual void SetAttributes(IAttributes atts)
        {
            Clear();
            length = atts.Length;
            if (length > 0)
            {
                data = new string[length * 5];
                for (int i = 0; i < length; i++)
                {
                    data[i * 5] = atts.GetURI(i);
                    data[i * 5 + 1] = atts.GetLocalName(i);
                    data[i * 5 + 2] = atts.GetQName(i);
                    data[i * 5 + 3] = atts.GetType(i);
                    data[i * 5 + 4] = atts.GetValue(i);
                }
            }
        }


        /// <summary>
        /// Add an attribute to the end of the list.
        /// <para/>For the sake of speed, this method does no checking
        /// to see if the attribute is already in the list: that is
        /// the responsibility of the application.
        /// </summary>
        /// <param name="uri">The Namespace URI, or the empty string if
        /// none is available or Namespace processing is not
        /// being performed.</param>
        /// <param name="localName">The local name, or the empty string if
        /// Namespace processing is not being performed.</param>
        /// <param name="qName">The qualified (prefixed) name, or the empty string
        /// if qualified names are not available.</param>
        /// <param name="type">The attribute type as a string.</param>
        /// <param name="value">The attribute value.</param>
        public virtual void AddAttribute(string uri, string localName, string qName,
                      string type, string value)
        {
            EnsureCapacity(length + 1);
            data[length * 5] = uri;
            data[length * 5 + 1] = localName;
            data[length * 5 + 2] = qName;
            data[length * 5 + 3] = type;
            data[length * 5 + 4] = value;
            length++;
        }


        /// <summary>
        /// Set an attribute in the list.
        /// 
        /// <para/>For the sake of speed, this method does no checking
        /// for name conflicts or well-formedness: such checks are the
        /// responsibility of the application.
        /// </summary>
        /// <param name="index">The index of the attribute (zero-based).</param>
        /// <param name="uri">The Namespace URI, or the empty string if
        /// none is available or Namespace processing is not
        /// being performed.</param>
        /// <param name="localName">The local name, or the empty string if
        /// Namespace processing is not being performed.</param>
        /// <param name="qName">The qualified name, or the empty string
        /// if qualified names are not available.</param>
        /// <param name="type">The attribute type as a string.</param>
        /// <param name="value">The attribute value.</param>
        /// <exception cref="IndexOutOfRangeException">When the
        /// supplied index does not point to an attribute
        /// in the list.</exception>    
        public virtual void SetAttribute(int index, string uri, string localName,
                      string qName, string type, string value)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5] = uri;
                data[index * 5 + 1] = localName;
                data[index * 5 + 2] = qName;
                data[index * 5 + 3] = type;
                data[index * 5 + 4] = value;
            }
            else
            {
                BadIndex(index);
            }
        }

        /// <summary>
        /// Remove an attribute from the list.
        /// </summary>
        /// <param name="index">The index of the attribute (zero-based).</param>
        /// <exception cref="IndexOutOfRangeException">When the supplied index does not point to an attribute in the list.</exception>
        public virtual void RemoveAttribute(int index)
        {
            if (index >= 0 && index < length)
            {
                if (index < length - 1)
                {
                    System.Array.Copy(data, (index + 1) * 5, data, index * 5,
                             (length - index - 1) * 5);
                }
                index = (length - 1) * 5;
                data[index++] = null;
                data[index++] = null;
                data[index++] = null;
                data[index++] = null;
                data[index] = null;
                length--;
            }
            else
            {
                BadIndex(index);
            }
        }

        /// <summary>
        /// Set the Namespace URI of a specific attribute.
        /// </summary>
        /// <param name="index">The index of the attribute (zero-based).</param>
        /// <param name="uri">The attribute's Namespace URI, or the empty
        /// string for none.</param>
        /// <exception cref="IndexOutOfRangeException">When the
        /// supplied index does not point to an attribute
        /// in the list.</exception>      
        public virtual void SetURI(int index, string uri)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5] = uri;
            }
            else
            {
                BadIndex(index);
            }
        }

        /// <summary>
        /// Set the local name of a specific attribute.
        /// </summary>
        /// <param name="index">The index of the attribute (zero-based).</param>
        /// <param name="localName">The attribute's local name, or the empty
        /// string for none.</param>
        /// <exception cref="IndexOutOfRangeException">When the
        /// supplied index does not point to an attribute
        /// in the list.</exception>         
        public virtual void SetLocalName(int index, string localName)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5 + 1] = localName;
            }
            else
            {
                BadIndex(index);
            }
        }

        /// <summary>
        /// Set the qualified name of a specific attribute.
        /// </summary>
        /// <param name="index">The index of the attribute (zero-based).</param>
        /// <param name="qName">The attribute's qualified name, or the empty
        /// string for none.</param>
        /// <exception cref="IndexOutOfRangeException">When the
        /// supplied index does not point to an attribute
        /// in the list.</exception>    
        public virtual void SetQName(int index, string qName)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5 + 2] = qName;
            }
            else
            {
                BadIndex(index);
            }
        }

        /// <summary>
        /// Set the type of a specific attribute.
        /// </summary>
        /// <param name="index">The index of the attribute (zero-based).</param>
        /// <param name="type">The attribute's type.</param>
        /// <exception cref="IndexOutOfRangeException">When the
        /// supplied index does not point to an attribute
        /// in the list.</exception>         
        public virtual void SetType(int index, string type)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5 + 3] = type;
            }
            else
            {
                BadIndex(index);
            }
        }

        /// <summary>
        /// Set the value of a specific attribute.
        /// </summary>
        /// <param name="index">The index of the attribute (zero-based).</param>
        /// <param name="value">The attribute's value.</param>
        /// <exception cref="IndexOutOfRangeException">When the
        /// supplied index does not point to an attribute
        /// in the list.</exception>   
        public virtual void SetValue(int index, string value)
        {
            if (index >= 0 && index < length)
            {
                data[index * 5 + 4] = value;
            }
            else
            {
                BadIndex(index);
            }
        }

        ////////////////////////////////////////////////////////////////////
        // Internal methods.
        ////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Ensure the internal array's capacity.
        /// </summary>
        /// <param name="n">The minimum number of attributes that the array must be able to hold.</param>
        private void EnsureCapacity(int n)
        {
            if (n <= 0)
            {
                return;
            }
            int max;
            if (data == null || data.Length == 0)
            {
                max = 25;
            }
            else if (data.Length >= n * 5)
            {
                return;
            }
            else
            {
                max = data.Length;
            }
            while (max < n * 5)
            {
                max *= 2;
            }

            string[] newData = new string[max];
            if (length > 0)
            {
                System.Array.Copy(data, 0, newData, 0, length * 5);
            }
            data = newData;
        }

        /// <summary>
        /// Report a bad array index in a manipulator.
        /// </summary>
        /// <param name="index">The index to report.</param>
        /// <exception cref="IndexOutOfRangeException">Always.</exception>
        private void BadIndex(int index)
        {
            string msg =
                "Attempt to modify attribute at illegal index: " + index;
            throw new IndexOutOfRangeException(msg);
        }


        ////////////////////////////////////////////////////////////////////
        // Internal state.
        ////////////////////////////////////////////////////////////////////

        int length;
        string[] data;
    }
}
