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

namespace System.Reflection.Runtime.General
{
    //
    // Struct-based list builder that's special cased to avoid allocations for lists of one element.
    //
    internal struct ListBuilder<T> where T : class
    {
        public ListBuilder(int capacity)
        {
            _items = null;
            _item = null;
            _count = 0;
            _capacity = capacity;
#if DEBUG
            _toArrayAlreadyCalled = false;
#endif // DEBUG
        }

        public T this[int index]
        {
            get
            {
                Debug.Assert(index < Count);
                return (_items != null) ? _items[index] : _item;
            }
        }

        public T[] ToArray()
        {
#if DEBUG
            // ListBuilder does not always allocate a new array, though the ToArray() name connotates that it does (and in fact, we do pass the results
            // of this method across api boundaries.) The minimizing of allocations is desirable, however, so instead, we restrict you to one call per ListBuilder.
            Debug.Assert(!_toArrayAlreadyCalled, "Cannot call ListBuilder.ToArray() a second time. Copy the one you already got.");
#endif // DEBUG
            if (_count == 0)
                return Array.Empty<T>();
            if (_count == 1)
                return new T[1] { _item };

            Array.Resize(ref _items, _count);

#if DEBUG
            _toArrayAlreadyCalled = true;
#endif // DEBUG

            return _items;
        }

        public void CopyTo(Object[] array, int index)
        {
            if (_count == 0)
                return;

            if (_count == 1)
            {
                array[index] = _item;
                return;
            }

            Array.Copy(_items, 0, array, index, _count);
        }

        public int Count
        {
            get
            {
                return _count;
            }
        }

        public void Add(T item)
        {
            if (_count == 0)
            {
                _item = item;
            }
            else
            {
                if (_count == 1)
                {
                    if (_capacity < 2)
                        _capacity = 4;
                    _items = new T[_capacity];
                    _items[0] = _item;
                }
                else if (_capacity == _count)
                {
                    int newCapacity = 2 * _capacity;
                    Array.Resize(ref _items, newCapacity);
                    _capacity = newCapacity;
                }

                _items[_count] = item;
            }
            _count++;
        }

        private T[] _items;
        private T _item;
        private int _count;
        private int _capacity;
#if DEBUG
        private bool _toArrayAlreadyCalled;
#endif // DEBUG
    }
}
