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

#nullable disable

using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;

namespace Microsoft.CodeAnalysis.CSharp
{
    // pooled class used for input to overload resolution
    internal sealed class MethodGroup
    {
        internal BoundExpression Receiver { get; private set; }
        internal ArrayBuilder<MethodSymbol> Methods { get; }
        internal ArrayBuilder<TypeWithAnnotations> TypeArguments { get; }
        internal bool IsExtensionMethodGroup { get; private set; }
        internal DiagnosticInfo Error { get; private set; }
        internal LookupResultKind ResultKind { get; private set; }

        private MethodGroup()
        {
            this.Methods = new ArrayBuilder<MethodSymbol>();
            this.TypeArguments = new ArrayBuilder<TypeWithAnnotations>();
        }

        internal void PopulateWithSingleMethod(
            BoundExpression receiverOpt,
            MethodSymbol method,
            LookupResultKind resultKind = LookupResultKind.Viable,
            DiagnosticInfo error = null)
        {
            this.PopulateHelper(receiverOpt, resultKind, error);
            this.Methods.Add(method);
        }

        internal void PopulateWithExtensionMethods<TSymbol>(
            BoundExpression receiverOpt,
            ArrayBuilder<TSymbol> members,
            ImmutableArray<TypeWithAnnotations> typeArguments,
            LookupResultKind resultKind = LookupResultKind.Viable,
            DiagnosticInfo error = null)
            where TSymbol : Symbol
        {
            this.PopulateHelper(receiverOpt, resultKind, error);
            this.IsExtensionMethodGroup = true;

            foreach (var member in members)
            {
                if (member is MethodSymbol method)
                {
                    Debug.Assert(method.IsExtensionMethod || method.IsExtensionBlockMember());
                    this.Methods.Add(method);
                }
            }

            if (!typeArguments.IsDefault)
            {
                this.TypeArguments.AddRange(typeArguments);
            }
        }

        internal void PopulateWithNonExtensionMethods(
            BoundExpression receiverOpt,
            ImmutableArray<MethodSymbol> methods,
            ImmutableArray<TypeWithAnnotations> typeArguments,
            LookupResultKind resultKind = LookupResultKind.Viable,
            DiagnosticInfo error = null)
        {
            this.PopulateHelper(receiverOpt, resultKind, error);
            this.Methods.AddRange(methods);
            if (!typeArguments.IsDefault)
            {
                this.TypeArguments.AddRange(typeArguments);
            }
        }

        private void PopulateHelper(BoundExpression receiverOpt, LookupResultKind resultKind, DiagnosticInfo error)
        {
            VerifyClear();
            this.Receiver = receiverOpt;
            this.Error = error;
            this.ResultKind = resultKind;
        }

        public void Clear()
        {
            this.Receiver = null;
            this.Methods.Clear();
            this.TypeArguments.Clear();
            this.IsExtensionMethodGroup = false;
            this.Error = null;
            this.ResultKind = LookupResultKind.Empty;

            VerifyClear();
        }

        public string Name
        {
            get
            {
                return this.Methods.Count > 0 ? this.Methods[0].Name : null;
            }
        }

        public BoundExpression InstanceOpt
        {
            get
            {
                if (this.Receiver == null)
                {
                    return null;
                }

                if (this.Receiver.Kind == BoundKind.TypeExpression)
                {
                    return null;
                }

                return this.Receiver;
            }
        }

        [Conditional("DEBUG")]
        private void VerifyClear()
        {
            Debug.Assert(this.Receiver == null);
            Debug.Assert(this.Methods.Count == 0);
            Debug.Assert(this.TypeArguments.Count == 0);
            Debug.Assert(!this.IsExtensionMethodGroup);
            Debug.Assert(this.Error == null);
            Debug.Assert(this.ResultKind == LookupResultKind.Empty);
        }

        #region "Poolable"

        public static MethodGroup GetInstance()
        {
            return Pool.Allocate();
        }

        public void Free()
        {
            this.Clear();
            Pool.Free(this);
        }

        //2) Expose the pool or the way to create a pool or the way to get an instance.
        //       for now we will expose both and figure which way works better
        public static readonly ObjectPool<MethodGroup> Pool = CreatePool();

        private static ObjectPool<MethodGroup> CreatePool()
        {
            ObjectPool<MethodGroup> pool = null;
            pool = new ObjectPool<MethodGroup>(() => new MethodGroup(), 10);
            return pool;
        }

        #endregion
    }
}
