// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Microsoft.Build.Collections;
using Microsoft.Build.Execution;
using Microsoft.Build.Shared;

#nullable disable

namespace Microsoft.Build.Instance.ImmutableProjectCollections
{
    /// <summary>
    /// A collection representing the set of Global ProjectPropertyInstance objects.
    /// </summary>
    /// <remarks>This class is used only when the containing ProjectInstance originates from an
    /// immutable linked project source. It's specialized in order to reduce required allocations
    /// by instead relying on the linked project source's collection of global properties
    /// (the IDictionary _globalProperties) and the ProjectInstance's collection of all
    /// properties (the PropertyDictionary _allProperties). When a property is requested,
    /// _globalProperties is checked to determine whether the named property is actually
    /// a global property and, if it is, then instance is retrieved from _allProperties.
    /// </remarks>
    internal class ImmutableGlobalPropertiesCollectionConverter :
        IRetrievableValuedEntryHashSet<ProjectPropertyInstance>,
        IValueDictionaryConverter
    {
        private readonly IDictionary<string, string> _globalProperties;
        private readonly PropertyDictionary<ProjectPropertyInstance> _allProperties;
        private readonly ValuesCollection _values;

        public ImmutableGlobalPropertiesCollectionConverter(
            IDictionary<string, string> globalProperties,
            PropertyDictionary<ProjectPropertyInstance> allProperties)
        {
            _globalProperties = globalProperties;
            _allProperties = allProperties;
            _values = new ValuesCollection(this);
        }

        public ProjectPropertyInstance this[string key]
        {
            set => throw new NotSupportedException();
            get
            {
                if (_globalProperties.ContainsKey(key))
                {
                    return _allProperties[key];
                }

                return null;
            }
        }

        public int Count => _globalProperties.Count;

        public bool IsReadOnly => true;

        public ICollection<string> Keys => _globalProperties.Keys;

        public ICollection<ProjectPropertyInstance> Values => _values;

        public void Add(ProjectPropertyInstance item) => throw new NotSupportedException();

        public void Add(string key, ProjectPropertyInstance value) => throw new NotSupportedException();

        public void Add(KeyValuePair<string, ProjectPropertyInstance> item) => throw new NotSupportedException();

        public void Clear() => throw new NotSupportedException();

        public bool Contains(ProjectPropertyInstance item) => _values.Contains(item);

        public bool Contains(KeyValuePair<string, ProjectPropertyInstance> itemKvp) => _values.Contains(itemKvp.Value);

        public bool ContainsKey(string key) => _globalProperties.ContainsKey(key);

        public void CopyTo(ProjectPropertyInstance[] array) => _values.CopyTo(array, arrayIndex: 0);

        public void CopyTo(ProjectPropertyInstance[] array, int arrayIndex) => _values.CopyTo(array, arrayIndex);

        public void CopyTo(ProjectPropertyInstance[] array, int arrayIndex, int count) => _values.CopyTo(array, arrayIndex, count);

        public void CopyTo(KeyValuePair<string, ProjectPropertyInstance>[] array, int arrayIndex)
        {
            ErrorUtilities.VerifyCollectionCopyToArguments(array, nameof(array), arrayIndex, nameof(arrayIndex), _globalProperties.Count);

            int currentIndex = arrayIndex;
            foreach (var itemKey in _globalProperties.Keys)
            {
                ProjectPropertyInstance instance = _allProperties[itemKey];
                if (instance != null)
                {
                    array[currentIndex] = new KeyValuePair<string, ProjectPropertyInstance>(itemKey, instance);
                    ++currentIndex;
                }
            }
        }

        public ProjectPropertyInstance Get(string key)
        {
            return this[key];
        }

        public ProjectPropertyInstance Get(string key, int index, int length)
        {
            // The PropertyDictionary containing all of the properties can efficiently
            // look up the requested property while honoring the specific index and length
            // constraints. We then just have to verify that it's one of the global properties.
            ProjectPropertyInstance actualProperty = _allProperties.Get(key, index, length);
            if (actualProperty != null && _globalProperties.ContainsKey(actualProperty.Name))
            {
                return actualProperty;
            }

            return null;
        }

        public IEnumerator<ProjectPropertyInstance> GetEnumerator() => _values.GetEnumerator();

        public void GetObjectData(SerializationInfo info, StreamingContext context) => throw new NotSupportedException();

        public void OnDeserialization(object sender) => throw new NotSupportedException();

        public bool Remove(ProjectPropertyInstance item) => throw new NotSupportedException();

        public bool Remove(string key) => throw new NotSupportedException();

        public bool Remove(KeyValuePair<string, ProjectPropertyInstance> item) => throw new NotSupportedException();

        public IDictionary<string, string> ToReadOnlyDictionary() => new System.Collections.ObjectModel.ReadOnlyDictionary<string, string>(_globalProperties);

        public void TrimExcess()
        {
        }

        public bool TryGetEscapedValue(string key, out string escapedValue) => _globalProperties.TryGetValue(key, out escapedValue);

        public bool TryGetValue(string key, out ProjectPropertyInstance value)
        {
            ProjectPropertyInstance instance = Get(key);
            value = instance;
            return instance != null;
        }

        public void UnionWith(IEnumerable<ProjectPropertyInstance> other) => throw new NotSupportedException();

        IEnumerator<KeyValuePair<string, ProjectPropertyInstance>> IEnumerable<KeyValuePair<string, ProjectPropertyInstance>>.GetEnumerator()
        {
            foreach (var itemKey in _globalProperties.Keys)
            {
                ProjectPropertyInstance instance = _allProperties[itemKey];
                if (instance != null)
                {
                    yield return new KeyValuePair<string, ProjectPropertyInstance>(itemKey, instance);
                }
            }
        }

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

        private class ValuesCollection : ICollection<ProjectPropertyInstance>
        {
            private readonly ImmutableGlobalPropertiesCollectionConverter _parent;

            public ValuesCollection(ImmutableGlobalPropertiesCollectionConverter parent)
            {
                _parent = parent;
            }

            public int Count => _parent._globalProperties.Count;

            public bool IsReadOnly => true;

            public void Add(ProjectPropertyInstance item) => throw new NotSupportedException();

            public void Clear() => throw new NotSupportedException();

            public bool Remove(ProjectPropertyInstance item) => throw new NotSupportedException();

            public bool Contains(ProjectPropertyInstance item)
            {
                if (!_parent._globalProperties.ContainsKey(item.Name))
                {
                    return false;
                }

                ProjectPropertyInstance actualInstance = _parent._allProperties[item.Name];

                if (actualInstance == null)
                {
                    return false;
                }

                return actualInstance.Equals(item);
            }

            public void CopyTo(ProjectPropertyInstance[] array, int arrayIndex)
            {
                CopyTo(array, arrayIndex, _parent._globalProperties.Count);
            }

            public void CopyTo(ProjectPropertyInstance[] array, int arrayIndex, int count)
            {
                ErrorUtilities.VerifyCollectionCopyToArguments(array, nameof(array), arrayIndex, nameof(arrayIndex), _parent._globalProperties.Count);

                int currentIndex = arrayIndex;
                int currentCount = 0;
                foreach (var itemKey in _parent._globalProperties.Keys)
                {
                    if (currentCount >= count)
                    {
                        return;
                    }

                    ProjectPropertyInstance instance = _parent._allProperties[itemKey];
                    if (instance != null)
                    {
                        array[currentIndex] = instance;
                        ++currentIndex;
                        ++currentCount;
                    }
                }
            }

            public IEnumerator<ProjectPropertyInstance> GetEnumerator()
            {
                foreach (var itemKey in _parent._globalProperties.Keys)
                {
                    ProjectPropertyInstance instance = _parent._allProperties[itemKey];
                    if (instance != null)
                    {
                        yield return instance;
                    }
                }
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                foreach (var itemKey in _parent._globalProperties.Keys)
                {
                    ProjectPropertyInstance instance = _parent._allProperties[itemKey];
                    if (instance != null)
                    {
                        yield return instance;
                    }
                }
            }
        }
    }
}
