using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;

namespace Sonex.Client.UserControls.DataGridView
{
    public sealed partial class DataGridView
    {
        public sealed class IntBitSet : IEnumerable<int>
        {
            private readonly ulong[] _words;

            public int MaxValue { get; }
            public int Length => MaxValue + 1;

            public IntBitSet(int maxValueInclusive)
            {
                if (maxValueInclusive < 0)
                    throw new ArgumentOutOfRangeException(nameof(maxValueInclusive));

                MaxValue = maxValueInclusive;

                int bitCount = maxValueInclusive + 1;
                int wordCount = (bitCount + 63) >> 6;
                _words = new ulong[wordCount];
            }

            // INDEXER
            public bool this[int index]
            {
                [MethodImpl(MethodImplOptions.AggressiveInlining)]
                get => Has(index);

                [MethodImpl(MethodImplOptions.AggressiveInlining)]
                set
                {
                    if (!InRange(index)) return;
                    if (value) Add(index);
                    else Remove(index);
                }
            }

            // SINGLE
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public void Add(int index)
            {
                if (!InRange(index)) return;
                _words[index >> 6] |= 1UL << (index & 63);
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public void Remove(int index)
            {
                if (!InRange(index)) return;
                _words[index >> 6] &= ~(1UL << (index & 63));
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public bool Has(int index)
            {
                if (!InRange(index)) return false;
                return (_words[index >> 6] & (1UL << (index & 63))) != 0;
            }

            // RANGE
            public void AddRange(int start, int endInclusive) => SetRangeClamped(start, endInclusive, true);
            public void RemoveRange(int start, int endInclusive) => SetRangeClamped(start, endInclusive, false);

            // GLOBAL
            public void Clear() => Array.Clear(_words, 0, _words.Length);

            public void SelectAll()
            {
                Array.Fill(_words, ulong.MaxValue);
                MaskUnusedHighBits();
            }

            public void Invert()
            {
                for (int i = 0; i < _words.Length; i++)
                    _words[i] = ~_words[i];

                MaskUnusedHighBits();
            }
            public void Invert(int row)
            {
                this[row] = !this[row];
            }

            public int CountSelected()
            {
                int count = 0;
                for (int i = 0; i < _words.Length; i++)
                    count += BitOperations.PopCount(_words[i]);

                return count;
            }

            // ENUMERATOR — foreach zwraca zaznaczone indexy
            public IEnumerator<int> GetEnumerator()
            {
                for (int wi = 0; wi < _words.Length; wi++)
                {
                    ulong word = _words[wi];

                    while (word != 0)
                    {
                        int bit = BitOperations.TrailingZeroCount(word);
                        int index = (wi << 6) + bit;

                        if (index > MaxValue)
                            yield break;

                        yield return index;
                        word &= word - 1;
                    }
                }
            }

            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

            // INTERNALS
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            private bool InRange(int index) => (uint)index <= (uint)MaxValue;

            private void SetRangeClamped(int start, int endInclusive, bool set)
            {
                if (endInclusive < start)
                    (start, endInclusive) = (endInclusive, start);

                // całkiem poza zakresem?
                if (endInclusive < 0 || start > MaxValue)
                    return;

                // clamp do [0..MaxValue]
                if (start < 0) start = 0;
                if (endInclusive > MaxValue) endInclusive = MaxValue;

                SetRangeCore(start, endInclusive, set);
            }

            private void SetRangeCore(int start, int endInclusive, bool set)
            {
                int startW = start >> 6;
                int endW = endInclusive >> 6;
                int startB = start & 63;
                int endB = endInclusive & 63;

                if (startW == endW)
                {
                    ulong mask = RangeMask(startB, endB);
                    if (set) _words[startW] |= mask;
                    else _words[startW] &= ~mask;
                    return;
                }

                ulong startMask = ulong.MaxValue << startB;
                if (set) _words[startW] |= startMask;
                else _words[startW] &= ~startMask;

                if (startW + 1 <= endW - 1)
                {
                    ulong fill = set ? ulong.MaxValue : 0UL;
                    for (int w = startW + 1; w <= endW - 1; w++)
                        _words[w] = fill;
                }

                ulong endMask = (endB == 63) ? ulong.MaxValue : ((1UL << (endB + 1)) - 1UL);
                if (set) _words[endW] |= endMask;
                else _words[endW] &= ~endMask;

                MaskUnusedHighBits();
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            private static ulong RangeMask(int startBit, int endBit)
            {
                ulong left = ulong.MaxValue << startBit;
                ulong right = (endBit == 63) ? ulong.MaxValue : ((1UL << (endBit + 1)) - 1UL);
                return left & right;
            }

            private void MaskUnusedHighBits()
            {
                int usedBitsLast = (Length & 63);
                if (usedBitsLast == 0) return;

                ulong mask = (1UL << usedBitsLast) - 1UL;
                _words[^1] &= mask;
            }
        }
    }
}