// 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.Linq;
using Microsoft.Build.Shared;

#nullable disable

namespace Microsoft.Build.Collections
{
    /// <summary>
    /// A special singleton enumerable that enumerates a read-only empty dictionary
    /// </summary>
    /// <typeparam name="K">Key</typeparam>
    /// <typeparam name="V">Value</typeparam>
    internal class ReadOnlyEmptyDictionary<K, V> : IDictionary<K, V>, IReadOnlyDictionary<K, V>, IDictionary
    {
        /// <summary>
        /// The single instance
        /// </summary>
        private static readonly Dictionary<K, V> s_backing = new Dictionary<K, V>();

        /// <summary>
        /// The single instance
        /// </summary>
        private static ReadOnlyEmptyDictionary<K, V> s_instance;

        /// <summary>
        /// Private default constructor as this is a singleton
        /// </summary>
        private ReadOnlyEmptyDictionary()
        {
        }

        /// <summary>
        /// Get the instance
        /// </summary>
        public static ReadOnlyEmptyDictionary<K, V> Instance
        {
            get
            {
                if (s_instance == null)
                {
                    s_instance = new ReadOnlyEmptyDictionary<K, V>();
                }

                return s_instance;
            }
        }

        /// <summary>
        /// Empty returns zero
        /// </summary>
        public int Count
        {
            get { return 0; }
        }

        /// <summary>
        /// Returns true
        /// </summary>
        public bool IsReadOnly
        {
            get { return true; }
        }

        /// <summary>
        /// Gets empty collection
        /// </summary>
        public ICollection<K> Keys =>
#if CLR2COMPATIBILITY
            new K[0];
#else
            Array.Empty<K>();
#endif

        /// <summary>
        /// Gets empty collection
        /// </summary>
        public ICollection<V> Values =>
#if CLR2COMPATIBILITY
            new V[0];
#else
            Array.Empty<V>();
#endif

        /// <summary>
        /// Is it fixed size
        /// </summary>
        public bool IsFixedSize
        {
            get { return true; }
        }

        /// <summary>
        /// Not synchronized
        /// </summary>
        public bool IsSynchronized
        {
            get { return false; }
        }

        /// <summary>
        /// No sync root
        /// </summary>
        public object SyncRoot
        {
            get { return null; }
        }

        /// <summary>
        /// Keys
        /// </summary>
        ICollection IDictionary.Keys
        {
            get { return (ICollection)((IDictionary<K, V>)this).Keys; }
        }

        /// <summary>
        /// Values
        /// </summary>
        ICollection IDictionary.Values
        {
            get { return (ICollection)((IDictionary<K, V>)this).Values; }
        }

        /// <summary>
        /// Keys
        /// </summary>
        IEnumerable<K> IReadOnlyDictionary<K, V>.Keys
        {
            get { return Keys; }
        }

        /// <summary>
        /// Values
        /// </summary>
        IEnumerable<V> IReadOnlyDictionary<K, V>.Values
        {
            get { return Values; }
        }

        /// <summary>
        /// Indexer
        /// </summary>
        public object this[object key]
        {
            get
            {
                return ((IDictionary<K, V>)this)[(K)key];
            }

            set
            {
                ((IDictionary<K, V>)this)[(K)key] = (V)value;
            }
        }

        /// <summary>
        /// Get returns null as read-only
        /// Set is prohibited and throws.
        /// </summary>
        public V this[K key]
        {
            get
            {
                // Trigger KeyNotFoundException
                return new Dictionary<K, V>()[key];
            }

            set
            {
                ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
            }
        }

        /// <summary>
        /// Pass through for underlying collection
        /// </summary>
        public void Add(K key, V value)
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
        }

        /// <summary>
        /// Empty returns false
        /// </summary>
        public bool ContainsKey(K key)
        {
            return false;
        }

        /// <summary>
        /// Prohibited on read only collection: throws
        /// </summary>
        public bool Remove(K key)
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
            return false;
        }

        /// <summary>
        /// Empty returns false
        /// </summary>
        public bool TryGetValue(K key, out V value)
        {
            value = default(V);
            return false;
        }

        /// <summary>
        /// Prohibited on read only collection: throws
        /// </summary>
        public void Add(KeyValuePair<K, V> item)
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
        }

        /// <summary>
        /// Prohibited on read only collection: throws
        /// </summary>
        public void Clear()
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
        }

        /// <summary>
        /// Empty returns false
        /// </summary>
        public bool Contains(KeyValuePair<K, V> item)
        {
            return false;
        }

        /// <summary>
        /// Empty does nothing
        /// </summary>
        public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
        {
        }

        /// <summary>
        /// Prohibited on read only collection: throws
        /// </summary>
        public bool Remove(KeyValuePair<K, V> item)
        {
            ErrorUtilities.ThrowInvalidOperation("OM_NotSupportedReadOnlyCollection");
            return false;
        }

        /// <summary>
        /// Get empty enumerator
        /// </summary>
        public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
        {
            return Enumerable.Empty<KeyValuePair<K, V>>().GetEnumerator();
        }

        /// <summary>
        /// Get empty enumerator
        /// </summary>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Add
        /// </summary>
        public void Add(object key, object value)
        {
            ((IDictionary<K, V>)this).Add((K)key, (V)value);
        }

        /// <summary>
        /// Contains
        /// </summary>
        public bool Contains(object key)
        {
            return ((IDictionary<K, V>)this).ContainsKey((K)key);
        }

        /// <summary>
        /// Enumerator
        /// </summary>
        IDictionaryEnumerator IDictionary.GetEnumerator()
        {
            return ((IDictionary)s_backing).GetEnumerator();
        }

        /// <summary>
        /// Remove
        /// </summary>
        public void Remove(object key)
        {
            ((IDictionary<K, V>)this).Remove((K)key);
        }

        /// <summary>
        /// CopyTo
        /// </summary>
        public void CopyTo(System.Array array, int index)
        {
            // Nothing to do
        }
    }
}

#if NET35
namespace System.Collections.Generic
{
    public interface IReadOnlyCollection<T> : IEnumerable<T>
    {
        int Count { get; }
    }

    public interface IReadOnlyDictionary<TKey, TValue> : IReadOnlyCollection<KeyValuePair<TKey, TValue>>
    {
        TValue this[TKey key] { get; }
        IEnumerable<TKey> Keys { get; }
        IEnumerable<TValue> Values { get; }
        bool ContainsKey(TKey key);
        bool TryGetValue(TKey key, out TValue value);
    }
}
#endif
