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

using Microsoft.Build.Shared;

#nullable disable

namespace Microsoft.Build.Collections
{
    /// <summary>
    /// Implementation of a dictionary which acts as a read-only wrapper on another dictionary, but
    /// converts values as they are accessed to another type.
    /// </summary>
    /// <typeparam name="K">The backing dictionary's key type.</typeparam>
    /// <typeparam name="V">The backing dictionary's value type.</typeparam>
    /// <typeparam name="N">The desired value type.</typeparam>
    internal class ReadOnlyConvertingDictionary<K, V, N> : IDictionary<K, N>
    {
        /// <summary>
        /// The backing dictionary.
        /// </summary>
        private readonly IDictionary<K, V> _backing;

        /// <summary>
        /// The delegate used to convert values.
        /// </summary>
        private readonly Func<V, N> _converter;

        /// <summary>
        /// Constructor.
        /// </summary>
        internal ReadOnlyConvertingDictionary(IDictionary<K, V> backing, Func<V, N> converter)
        {
            ErrorUtilities.VerifyThrowArgumentNull(backing);
            ErrorUtilities.VerifyThrowArgumentNull(converter);

            _backing = backing;
            _converter = converter;
        }

        #region IDictionary<string,string> Members

        /// <summary>
        /// Returns the collection of keys in the dictionary.
        /// </summary>
        public ICollection<K> Keys => _backing.Keys;

        /// <summary>
        /// Returns the collection of values in the dictionary.
        /// </summary>
        public ICollection<N> Values
        {
            get
            {
                ErrorUtilities.ThrowInternalError("Values is not supported on ReadOnlyConvertingDictionary.");

                // Show the compiler that this always throws:
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Returns the number of items in the collection.
        /// </summary>
        public int Count => _backing.Count;

        /// <summary>
        /// Returns true if the collection is read-only.
        /// </summary>
        public bool IsReadOnly => true;

        /// <summary>
        /// Accesses the value for the specified key.
        /// </summary>
        public N this[K key]
        {
            get => _converter(_backing[key]);
            set => ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
        }

        /// <summary>
        /// Adds a value to the dictionary.
        /// </summary>
        public void Add(K key, N value)
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
        }

        /// <summary>
        /// Returns true if the dictionary contains the specified key.
        /// </summary>
        public bool ContainsKey(K key)
        {
            return _backing.ContainsKey(key);
        }

        /// <summary>
        /// Removes the entry for the specified key from the dictionary.
        /// </summary>
        public bool Remove(K key)
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
            return false;
        }

        /// <summary>
        /// Attempts to find the value for the specified key in the dictionary.
        /// </summary>
        public bool TryGetValue(K key, out N value)
        {
            if (_backing.TryGetValue(key, out V originalValue))
            {
                value = _converter(originalValue);
                return true;
            }

            value = default(N);
            return false;
        }

        #endregion

        #region ICollection<KeyValuePair<string,string>> Members

        /// <summary>
        /// Adds an item to the collection.
        /// </summary>
        public void Add(KeyValuePair<K, N> item)
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
        }

        /// <summary>
        /// Clears the collection.
        /// </summary>
        public void Clear()
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
        }

        /// <summary>
        /// Returns true ff the collection contains the specified item.
        /// </summary>
        public bool Contains(KeyValuePair<K, N> item)
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedConvertingCollectionValueToBacking");
            return false;
        }

        /// <summary>
        /// Copies all of the elements of the collection to the specified array.
        /// </summary>
        public void CopyTo(KeyValuePair<K, N>[] array, int arrayIndex)
        {
            ErrorUtilities.VerifyThrow(array.Length - arrayIndex >= _backing.Count, "Specified array size insufficient to hold the contents of the collection.");

            foreach (KeyValuePair<K, V> pair in _backing)
            {
                array[arrayIndex++] = KeyValueConverter(pair);
            }
        }

        /// <summary>
        /// Remove an item from the dictionary.
        /// </summary>
        public bool Remove(KeyValuePair<K, N> item)
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
            return false;
        }

        #endregion

        #region IEnumerable<KeyValuePair<K, N>> Members

        /// <summary>
        /// Implementation of generic IEnumerable.GetEnumerator()
        /// </summary>
        public IEnumerator<KeyValuePair<K, N>> GetEnumerator()
        {
            return new ConvertingEnumerable<KeyValuePair<K, V>, KeyValuePair<K, N>>(_backing, KeyValueConverter).GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Implementation of IEnumerable.GetEnumerator()
        /// </summary>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<KeyValuePair<K, N>>)this).GetEnumerator();
        }

        #endregion

        /// <summary>
        /// Delegate used by ConvertingEnumerable
        /// </summary>
        private KeyValuePair<K, N> KeyValueConverter(KeyValuePair<K, V> original)
        {
            return new KeyValuePair<K, N>(original.Key, _converter(original.Value));
        }
    }
}
