// HtmlAgilityPack V1.0 - Simon Mourier <simon underscore mourier at hotmail dot com>
using System;
using System.Collections;

namespace HtmlAgilityPack
{
    /// <summary>
    /// Represents a combined list and collection of HTML nodes.
    /// </summary>
    public class HtmlNodeCollection : IEnumerable
    {
        private ArrayList _items = new ArrayList();
        private HtmlNode _parentnode;

        internal HtmlNodeCollection(HtmlNode parentnode)
        {
            _parentnode = parentnode; // may be null
        }

        /// <summary>
        /// Gets the number of elements actually contained in the list.
        /// </summary>
        public int Count
        {
            get
            {
                return _items.Count;
            }
        }

        internal void Clear()
        {
            foreach (HtmlNode node in _items)
            {
                node._parentnode = null;
                node._nextnode = null;
                node._prevnode = null;
            }
            _items.Clear();
        }

        internal void Remove(int index)
        {
            HtmlNode next = null;
            HtmlNode prev = null;
            HtmlNode oldnode = (HtmlNode)_items[index];

            if (index > 0)
            {
                prev = (HtmlNode)_items[index - 1];
            }

            if (index < (_items.Count - 1))
            {
                next = (HtmlNode)_items[index + 1];
            }

            _items.RemoveAt(index);

            if (prev != null)
            {
                if (next == prev)
                {
                    throw new InvalidProgramException("Unexpected error.");
                }
                prev._nextnode = next;
            }

            if (next != null)
            {
                next._prevnode = prev;
            }

            oldnode._prevnode = null;
            oldnode._nextnode = null;
            oldnode._parentnode = null;
        }

        internal void Replace(int index, HtmlNode node)
        {
            HtmlNode next = null;
            HtmlNode prev = null;
            HtmlNode oldnode = (HtmlNode)_items[index];

            if (index > 0)
            {
                prev = (HtmlNode)_items[index - 1];
            }

            if (index < (_items.Count - 1))
            {
                next = (HtmlNode)_items[index + 1];
            }

            _items[index] = node;

            if (prev != null)
            {
                if (node == prev)
                {
                    throw new InvalidProgramException("Unexpected error.");
                }
                prev._nextnode = node;
            }

            if (next != null)
            {
                next._prevnode = node;
            }

            node._prevnode = prev;
            if (next == node)
            {
                throw new InvalidProgramException("Unexpected error.");
            }
            node._nextnode = next;
            node._parentnode = _parentnode;

            oldnode._prevnode = null;
            oldnode._nextnode = null;
            oldnode._parentnode = null;
        }

        internal void Insert(int index, HtmlNode node)
        {
            HtmlNode next = null;
            HtmlNode prev = null;

            if (index > 0)
            {
                prev = (HtmlNode)_items[index - 1];
            }

            if (index < _items.Count)
            {
                next = (HtmlNode)_items[index];
            }

            _items.Insert(index, node);

            if (prev != null)
            {
                if (node == prev)
                {
                    throw new InvalidProgramException("Unexpected error.");
                }
                prev._nextnode = node;
            }

            if (next != null)
            {
                next._prevnode = node;
            }

            node._prevnode = prev;

            if (next == node)
            {
                throw new InvalidProgramException("Unexpected error.");
            }

            node._nextnode = next;
            node._parentnode = _parentnode;
        }

        internal void Append(HtmlNode node)
        {
            HtmlNode last = null;
            if (_items.Count > 0)
            {
                last = (HtmlNode)_items[_items.Count - 1];
            }

            _items.Add(node);
            node._prevnode = last;
            node._nextnode = null;
            node._parentnode = _parentnode;
            if (last != null)
            {
                if (last == node)
                {
                    throw new InvalidProgramException("Unexpected error.");
                }
                last._nextnode = node;
            }
        }

        internal void Prepend(HtmlNode node)
        {
            HtmlNode first = null;
            if (_items.Count > 0)
            {
                first = (HtmlNode)_items[0];
            }

            _items.Insert(0, node);

            if (node == first)
            {
                throw new InvalidProgramException("Unexpected error.");
            }
            node._nextnode = first;
            node._prevnode = null;
            node._parentnode = _parentnode;
            if (first != null)
            {
                first._prevnode = node;
            }
        }

        internal void Add(HtmlNode node)
        {
            _items.Add(node);
        }

        /// <summary>
        /// Gets the node at the specified index.
        /// </summary>
        public HtmlNode this[int index]
        {
            get
            {
                return _items[index] as HtmlNode;
            }
        }

        internal int GetNodeIndex(HtmlNode node)
        {
            // TODO: should we rewrite this? what would be the key of a node?
            for (int i = 0; i < _items.Count; i++)
            {
                if (node == ((HtmlNode)_items[i]))
                {
                    return i;
                }
            }
            return -1;
        }

        /// <summary>
        /// Gets a given node from the list.
        /// </summary>
        public int this[HtmlNode node]
        {
            get
            {
                int index = GetNodeIndex(node);
                if (index == -1)
                {
                    throw new ArgumentOutOfRangeException("node", "Node \"" + node.CloneNode(false).OuterHtml + "\" was not found in the collection");
                }
                return index;
            }
        }

        /// <summary>
        /// Returns an enumerator that can iterate through the list.
        /// </summary>
        /// <returns>An IEnumerator for the entire list.</returns>
        public HtmlNodeEnumerator GetEnumerator()
        {
            return new HtmlNodeEnumerator(_items);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Represents an enumerator that can iterate through the list.
        /// </summary>
        public class HtmlNodeEnumerator : IEnumerator
        {
            int _index;
            ArrayList _items;

            internal HtmlNodeEnumerator(ArrayList items)
            {
                _items = items;
                _index = -1;
            }

            /// <summary>
            /// Sets the enumerator to its initial position, which is before the first element in the collection.
            /// </summary>
            public void Reset()
            {
                _index = -1;
            }

            /// <summary>
            /// Advances the enumerator to the next element of the collection.
            /// </summary>
            /// <returns>true if the enumerator was successfully advanced to the next element, false if the enumerator has passed the end of the collection.</returns>
            public bool MoveNext()
            {
                _index++;
                return (_index < _items.Count);
            }

            /// <summary>
            /// Gets the current element in the collection.
            /// </summary>
            public HtmlNode Current
            {
                get
                {
                    return (HtmlNode)(_items[_index]);
                }
            }

            /// <summary>
            /// Gets the current element in the collection.
            /// </summary>
            object IEnumerator.Current
            {
                get
                {
                    return (Current);
                }
            }
        }
    }

}
