// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System.Collections;
using System.ComponentModel;
using System.Windows.Forms.Layout;

namespace System.Windows.Forms
{
    public partial class LinkLabel
    {
        public class LinkCollection : IList
        {
            private readonly LinkLabel _owner;

            ///  A caching mechanism for key accessor
            ///  We use an index here rather than control so that we don't have lifetime
            ///  issues by holding on to extra references.
            ///  Note this is not Thread Safe - but WinForms has to be run in a STA anyways.
            private int lastAccessedIndex = -1;

            public LinkCollection(LinkLabel owner)
            {
                _owner = owner ?? throw new ArgumentNullException(nameof(owner));
            }

            public virtual Link this[int index]
            {
                get
                {
                    return (Link)_owner._links[index];
                }
                set
                {
                    _owner._links[index] = value;

                    _owner._links.Sort(s_linkComparer);

                    _owner.InvalidateTextLayout();
                    _owner.Invalidate();
                }
            }

            object IList.this[int index]
            {
                get => this[index];
                set
                {
                    if (value is Link link)
                    {
                        this[index] = link;
                    }
                    else
                    {
                        throw new ArgumentException(SR.LinkLabelBadLink, nameof(value));
                    }
                }
            }

            /// <summary>
            ///  Retrieves the child control with the specified key.
            /// </summary>
            public virtual Link this[string key]
            {
                get
                {
                    // We do not support null and empty string as valid keys.
                    if (string.IsNullOrEmpty(key))
                    {
                        return null;
                    }

                    // Search for the key in our collection
                    int index = IndexOfKey(key);
                    if (IsValidIndex(index))
                    {
                        return this[index];
                    }
                    else
                    {
                        return null;
                    }
                }
            }

            [Browsable(false)]
            public int Count => _owner._links.Count;

            /// <summary>
            ///  whether we have added a non-trivial link to the collection
            /// </summary>
            public bool LinksAdded { get; private set; }

            object ICollection.SyncRoot => this;

            bool ICollection.IsSynchronized => false;

            bool IList.IsFixedSize => false;

            public bool IsReadOnly => false;

            public Link Add(int start, int length)
            {
                if (length != 0)
                {
                    LinksAdded = true;
                }

                return Add(start, length, null);
            }

            public Link Add(int start, int length, object linkData)
            {
                if (length != 0)
                {
                    LinksAdded = true;
                }

                // check for the special case where the list is in the "magic"
                // state of having only the default link in it. In that case
                // we want to clear the list before adding this link.

                if (_owner._links.Count == 1
                    && this[0].Start == 0
                    && this[0]._length == -1)
                {
                    _owner._links.Clear();
                    _owner.FocusLink = null;
                }

                Link l = new Link(_owner)
                {
                    Start = start,
                    Length = length,
                    LinkData = linkData
                };
                Add(l);
                return l;
            }

            public int Add(Link value)
            {
                if (value is not null && value.Length != 0)
                {
                    LinksAdded = true;
                }

                // check for the special case where the list is in the "magic"
                // state of having only the default link in it. In that case
                // we want to clear the list before adding this link.
                //
                if (_owner._links.Count == 1
                    && this[0].Start == 0
                    && this[0]._length == -1)
                {
                    _owner._links.Clear();
                    _owner.FocusLink = null;
                }

                // Set the owner control for this link
                value.Owner = _owner;

                _owner._links.Add(value);

                if (_owner.AutoSize)
                {
                    LayoutTransaction.DoLayout(_owner.ParentInternal, _owner, PropertyNames.Links);
                    _owner.AdjustSize();
                    _owner.Invalidate();
                }

                if (_owner.Links.Count > 1)
                {
                    _owner._links.Sort(LinkLabel.s_linkComparer);
                }

                _owner.ValidateNoOverlappingLinks();
                _owner.UpdateSelectability();
                _owner.InvalidateTextLayout();
                _owner.Invalidate();

                if (_owner.Links.Count > 1)
                {
                    return IndexOf(value);
                }
                else
                {
                    return 0;
                }
            }

            int IList.Add(object value)
            {
                if (value is Link)
                {
                    return Add((Link)value);
                }
                else
                {
                    throw new ArgumentException(SR.LinkLabelBadLink, nameof(value));
                }
            }

            void IList.Insert(int index, object value)
            {
                if (value is Link)
                {
                    Add((Link)value);
                }
                else
                {
                    throw new ArgumentException(SR.LinkLabelBadLink, nameof(value));
                }
            }

            public bool Contains(Link link)
            {
                return _owner._links.Contains(link);
            }

            /// <summary>
            ///  Returns true if the collection contains an item with the specified key, false otherwise.
            /// </summary>
            public virtual bool ContainsKey(string key)
            {
                return IsValidIndex(IndexOfKey(key));
            }

            bool IList.Contains(object link)
            {
                if (link is Link)
                {
                    return Contains((Link)link);
                }
                else
                {
                    return false;
                }
            }

            public int IndexOf(Link link)
            {
                return _owner._links.IndexOf(link);
            }

            int IList.IndexOf(object link)
            {
                if (link is Link)
                {
                    return IndexOf((Link)link);
                }
                else
                {
                    return -1;
                }
            }

            /// <summary>
            ///  The zero-based index of the first occurrence of value within the entire CollectionBase, if found; otherwise, -1.
            /// </summary>
            public virtual int IndexOfKey(string key)
            {
                // Step 0 - Arg validation
                if (string.IsNullOrEmpty(key))
                {
                    return -1; // we dont support empty or null keys.
                }

                // step 1 - check the last cached item
                if (IsValidIndex(lastAccessedIndex))
                {
                    if (WindowsFormsUtils.SafeCompareStrings(this[lastAccessedIndex].Name, key, /* ignoreCase = */ true))
                    {
                        return lastAccessedIndex;
                    }
                }

                // step 2 - search for the item
                for (int i = 0; i < Count; i++)
                {
                    if (WindowsFormsUtils.SafeCompareStrings(this[i].Name, key, /* ignoreCase = */ true))
                    {
                        lastAccessedIndex = i;
                        return i;
                    }
                }

                // step 3 - we didn't find it.  Invalidate the last accessed index and return -1.
                lastAccessedIndex = -1;
                return -1;
            }

            /// <summary>
            ///  Determines if the index is valid for the collection.
            /// </summary>
            private bool IsValidIndex(int index)
            {
                return ((index >= 0) && (index < Count));
            }

            /// <summary>
            ///  Remove all links from the linkLabel.
            /// </summary>
            public virtual void Clear()
            {
                bool doLayout = _owner._links.Count > 0 && _owner.AutoSize;
                _owner._links.Clear();

                if (doLayout)
                {
                    LayoutTransaction.DoLayout(_owner.ParentInternal, _owner, PropertyNames.Links);
                    _owner.AdjustSize();
                    _owner.Invalidate();
                }

                _owner.UpdateSelectability();
                _owner.InvalidateTextLayout();
                _owner.Invalidate();
            }

            void ICollection.CopyTo(Array dest, int index)
            {
                ((ICollection)_owner._links).CopyTo(dest, index);
            }

            public IEnumerator GetEnumerator()
            {
                if (_owner._links is not null)
                {
                    return _owner._links.GetEnumerator();
                }
                else
                {
                    return Array.Empty<Link>().GetEnumerator();
                }
            }

            public void Remove(Link value)
            {
                if (value.Owner != _owner)
                {
                    return;
                }

                _owner._links.Remove(value);

                if (_owner.AutoSize)
                {
                    LayoutTransaction.DoLayout(_owner.ParentInternal, _owner, PropertyNames.Links);
                    _owner.AdjustSize();
                    _owner.Invalidate();
                }

                _owner._links.Sort(LinkLabel.s_linkComparer);

                _owner.ValidateNoOverlappingLinks();
                _owner.UpdateSelectability();
                _owner.InvalidateTextLayout();
                _owner.Invalidate();

                if (_owner.FocusLink is null && _owner._links.Count > 0)
                {
                    _owner.FocusLink = (Link)_owner._links[0];
                }
            }

            public void RemoveAt(int index)
            {
                Remove(this[index]);
            }

            /// <summary>
            ///  Removes the child control with the specified key.
            /// </summary>
            public virtual void RemoveByKey(string key)
            {
                int index = IndexOfKey(key);
                if (IsValidIndex(index))
                {
                    RemoveAt(index);
                }
            }

            void IList.Remove(object value)
            {
                if (value is Link)
                {
                    Remove((Link)value);
                }
            }
        }
    }
}
