// 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.

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using static System.Windows.Forms.ItemArray;

namespace System.Windows.Forms
{
    /// <summary>
    ///  This is similar to ArrayList except that it also
    ///  mantains a bit-flag based state element for each item
    ///  in the array.
    ///
    ///  The methods to enumerate, count and get data support
    ///  virtualized indexes.  Indexes are virtualized according
    ///  to the state mask passed in.  This allows ItemArray
    ///  to be the backing store for one read-write "master"
    ///  collection and serveral read-only collections based
    ///  on masks.  ItemArray supports up to 31 masks.
    /// </summary>
    internal partial class ItemArray : IComparer<Entry>
    {
        private static int s_lastMask = 1;

        private readonly ListControl _listControl;
        private List<Entry> _entries;

        public ItemArray(ListControl listControl)
        {
            _listControl = listControl;
            _entries = new List<Entry>();
        }

        internal IReadOnlyList<Entry?> Entries => _entries;

        /// <summary>
        ///  The version of this array.  This number changes with each
        ///  change to the item list.
        /// </summary>
        public int Version { get; private set; }

        /// <summary>
        ///  Adds the given item to the array.  The state is initially
        ///  zero.
        /// </summary>
        public object? Add(object item)
        {
            Entry entry = new Entry(item);
            _entries.Add(entry);
            Version++;
            return entry;
        }

        /// <summary>
        ///  Clears this array.
        /// </summary>
        public void Clear()
        {
            _entries.Clear();
            Version++;
        }

        /// <summary>
        ///  Allocates a new bitmask for use.
        /// </summary>
        public static int CreateMask()
        {
            int mask = s_lastMask;
            s_lastMask <<= 1;
            Debug.Assert(s_lastMask > mask, "We have overflowed our state mask.");
            return mask;
        }

        /// <summary>
        ///  Turns a virtual index into an actual index.
        /// </summary>
        public int GetActualIndex(int virtualIndex, int stateMask)
        {
            if (stateMask == 0)
            {
                return virtualIndex;
            }

            // More complex; we must compute this index.
            int calcIndex = -1;

            for (int i = 0; i < Count; i++)
            {
                if ((_entries[i].State & stateMask) != 0)
                {
                    calcIndex++;
                    if (calcIndex == virtualIndex)
                    {
                        return i;
                    }
                }
            }

            return -1;
        }

        /// <summary>
        ///  Gets the main count
        /// </summary>
        public int Count => _entries.Count;

        /// <summary>
        ///  Gets the count of items matching the given mask.
        /// </summary>
        public int GetCount(int stateMask = 0)
        {
            // If mask is zero, then just give the main count
            if (stateMask == 0)
            {
                return Count;
            }

            // more complex:  must provide a count of items
            // based on a mask.
            int filteredCount = 0;

            foreach (Entry entry in _entries)
            {
                if ((entry.State & stateMask) != 0)
                {
                    filteredCount++;
                }
            }

            return filteredCount;
        }

        /// <summary>
        ///  Retrieves an enumerator that will enumerate based on
        ///  the given mask.
        /// </summary>
        public IEnumerator GetEnumerator(int stateMask = 0)
        {
            return GetEnumerator(stateMask, anyBit: false);
        }

        /// <summary>
        ///  Retrieves an enumerator that will enumerate based on
        ///  the given mask.
        /// </summary>
        public IEnumerator GetEnumerator(int stateMask, bool anyBit)
        {
            return new EntryEnumerator(this, stateMask, anyBit);
        }

        /// <summary>
        ///  Gets the item at the given index.  The index is
        ///  virtualized against the given mask value.
        /// </summary>
        public object GetItem(int virtualIndex, int stateMask = 0)
        {
            int actualIndex = GetActualIndex(virtualIndex, stateMask);
            if (actualIndex == -1)
            {
                throw new IndexOutOfRangeException();
            }

            return _entries[actualIndex].Item;
        }

        /// <summary>
        ///  Gets the item at the given index.  The index is
        ///  virtualized against the given mask value.
        /// </summary>
        internal Entry GetEntryObject(int virtualIndex, int stateMask = 0)
        {
            int actualIndex = GetActualIndex(virtualIndex, stateMask);
            if (actualIndex == -1)
            {
                throw new IndexOutOfRangeException();
            }

            return _entries[actualIndex];
        }

        /// <summary>
        ///  Returns true if the requested state mask is set.
        ///  The index is the actual index to the array.
        /// </summary>
        public bool GetState(int index, int stateMask)
        {
            return (_entries[index].State & stateMask) == stateMask;
        }

        /// <summary>
        ///  Returns the virtual index of the item based on the
        ///  state mask.
        /// </summary>
        public int IndexOf(object item, int stateMask = 0)
        {
            int virtualIndex = -1;

            foreach (Entry entry in _entries)
            {
                if (stateMask == 0 || (entry.State & stateMask) != 0)
                {
                    virtualIndex++;
                    if ((item is Entry itemEntry && entry == itemEntry) || entry.Item.Equals(item))
                    {
                        return virtualIndex;
                    }
                }
            }

            return -1;
        }

        /// <summary>
        ///  Inserts item at the given index.  The index
        ///  is not virtualized.
        /// </summary>
        public void Insert(int index, object item)
        {
            InsertEntry(index, new Entry(item));
        }

        public void InsertEntry(int index, Entry item)
        {
            if (item is null)
            {
                throw new ArgumentNullException(nameof(item));
            }

            _entries.Insert(index, item);
            Version++;
        }

        /// <summary>
        ///  Removes the given item from the array.  If
        ///  the item is not in the array, this does nothing.
        /// </summary>
        public void Remove(object item)
        {
            int index = IndexOf(item);
            if (index != -1)
            {
                RemoveAt(index);
            }
        }

        /// <summary>
        ///  Removes the item at the given index.
        /// </summary>
        public void RemoveAt(int index)
        {
            _entries.RemoveAt(index);
            Version++;
        }

        /// <summary>
        ///  Sets the item at the given index to a new value.
        /// </summary>
        public void SetItem(int index, object item)
        {
            _entries[index].Item = item;
        }

        /// <summary>
        ///  Sets the state data for the given index.
        /// </summary>
        public void SetState(int index, int stateMask, bool value)
        {
            if (value)
            {
                _entries[index].State |= stateMask;
            }
            else
            {
                _entries[index].State &= ~stateMask;
            }

            Version++;
        }

        public Entry GetEntry(object element)
        {
            return element is Entry entryElement ? entryElement : new Entry(element);
        }

        /// <summary>
        ///  Find element in sorted array. If element is not found returns a binary complement of index for inserting
        /// </summary>
        public int BinarySearch(Entry element)
        {
            return _entries.BinarySearch(index: 0, Count, element, this);
        }

        /// <summary>
        ///  Sorts our array.
        /// </summary>
        public void Sort()
        {
            _entries.Sort(this);
        }

        int IComparer<Entry>.Compare(Entry? entry1, Entry? entry2)
        {
            if (entry1 is null)
            {
                if (entry2 is null)
                {
                    return 0; //both null, then they are equal
                }

                return -1; //item1 is null, but item2 is valid (greater)
            }

            if (entry2 is null)
            {
                return 1; //item2 is null, so item 1 is greater
            }

            string itemName1 = _listControl.GetItemText(entry1.Item);
            string itemName2 = _listControl.GetItemText(entry2.Item);

            CompareInfo compInfo = Application.CurrentCulture.CompareInfo;
            return compInfo.Compare(itemName1, itemName2, CompareOptions.StringSort);
        }
    }
}
