// 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;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// Options that can be used to modify the symbol lookup mechanism. 
    /// </summary>
    /// <remarks>
    /// Multiple options can be combined together.  LookupOptions.AreValid checks for valid combinations.
    /// </remarks>
    [Flags]
    internal enum LookupOptions
    {
        /// <summary>
        /// Consider all symbols, using normal accessibility rules.
        /// </summary>
        Default = 0,

        /// <summary>
        /// Consider only namespace aliases and extern aliases.
        /// </summary>
        NamespaceAliasesOnly = 1 << 1,

        /// <summary>
        /// Consider only namespaces and types.
        /// </summary>
        NamespacesOrTypesOnly = 1 << 2,

        /// <summary>
        /// Consider non-members, plus invocable members.
        /// </summary>
        MustBeInvocableIfMember = 1 << 3,

        /// <summary>
        /// Consider only symbols that are instance members. Valid with IncludeExtensionMethods
        /// since extension methods are invoked on an instance.
        /// </summary>
        MustBeInstance = 1 << 4,

        /// <summary>
        /// Do not consider symbols that are instance members.
        /// </summary>
        MustNotBeInstance = 1 << 5,

        /// <summary>
        /// Do not consider symbols that are namespaces.
        /// </summary>
        MustNotBeNamespace = 1 << 6,

        /// <summary>
        /// Consider methods of any arity when arity zero is specified. Because type parameters can be inferred, it is
        /// often desired to consider generic methods when no type arguments were present.
        /// </summary>
        AllMethodsOnArityZero = 1 << 7,

        /// <summary>
        /// Look only for label symbols.  This must be exclusive of all other options.
        /// </summary>
        LabelsOnly = 1 << 8,

        /// <summary>
        /// Usually, when determining if a member is accessible, both the type of the receiver
        /// and the type containing the access are used.  If this flag is specified, then only
        /// the containing type will be used (i.e. as if you've written base.XX).
        /// </summary>
        UseBaseReferenceAccessibility = 1 << 9,

        /// <summary>
        /// Include extension methods.
        /// </summary>
        IncludeExtensionMethods = 1 << 10,

        /// <summary>
        /// Consider only attribute types.
        /// </summary>
        AttributeTypeOnly = (1 << 11) | NamespacesOrTypesOnly,

        /// <summary>
        /// Consider lookup name to be a verbatim identifier.
        /// If this flag is specified, then only one lookup is performed for attribute name: lookup with the given name,
        /// and attribute name lookup with "Attribute" suffix is skipped.
        /// </summary>
        VerbatimNameAttributeTypeOnly = (1 << 12) | AttributeTypeOnly,

        /// <summary>
        /// Consider named types of any arity when arity zero is specified. It is specifically desired for nameof in such situations: nameof(System.Collections.Generic.List)
        /// </summary>
        AllNamedTypesOnArityZero = 1 << 13,

        /// <summary>
        /// Do not consider symbols that are method type parameters.
        /// </summary>
        MustNotBeMethodTypeParameter = 1 << 14,
    }

    internal static class LookupOptionExtensions
    {
        /// <summary>
        /// Are these options valid in their current combination?
        /// </summary>
        /// <remarks>
        /// Some checks made here:
        /// 
        /// - Default is valid.
        /// - If LabelsOnly is set, it must be the only option.
        /// - If one of MustBeInstance or MustNotBeInstance are set, the other one must not be set.
        /// - If any of MustNotBeInstance, MustBeInstance, or MustNotBeNonInvocableMember are set,
        ///   the options are considered valid.
        /// - If MustNotBeNamespace is set, neither NamespaceAliasesOnly nor NamespacesOrTypesOnly must be set.
        /// - Otherwise, only one of NamespaceAliasesOnly, NamespacesOrTypesOnly, or AllMethodsOnArityZero must be set.
        /// </remarks>
        internal static bool AreValid(this LookupOptions options)
        {
            if (options == LookupOptions.Default)
            {
                return true;
            }

            if ((options & LookupOptions.LabelsOnly) != 0)
            {
                return options == LookupOptions.LabelsOnly;
            }

            // These are exclusive; both must not be present.
            LookupOptions mustBeAndNotBeInstance = (LookupOptions.MustBeInstance | LookupOptions.MustNotBeInstance);
            if ((options & mustBeAndNotBeInstance) == mustBeAndNotBeInstance)
            {
                return false;
            }

            // If MustNotBeNamespace or MustNotBeMethodTypeParameter is set, neither NamespaceAliasesOnly nor NamespacesOrTypesOnly must be set.
            if ((options & (LookupOptions.MustNotBeNamespace | LookupOptions.MustNotBeMethodTypeParameter)) != 0 &&
                (options & (LookupOptions.NamespaceAliasesOnly | LookupOptions.NamespacesOrTypesOnly)) != 0)
            {
                return false;
            }

            LookupOptions onlyOptions = options &
                (LookupOptions.NamespaceAliasesOnly
                 | LookupOptions.NamespacesOrTypesOnly
                 | LookupOptions.AllMethodsOnArityZero);

            return OnlyOneBitSet(onlyOptions);
        }

        internal static void ThrowIfInvalid(this LookupOptions options)
        {
            if (!options.AreValid())
            {
                throw new ArgumentException(CSharpResources.LookupOptionsHasInvalidCombo);
            }
        }

        private static bool OnlyOneBitSet(LookupOptions o)
        {
            return (o & (o - 1)) == 0;
        }

        internal static bool CanConsiderMembers(this LookupOptions options)
        {
            return (options & (LookupOptions.NamespaceAliasesOnly | LookupOptions.NamespacesOrTypesOnly | LookupOptions.LabelsOnly)) == 0;
        }

        internal static bool CanConsiderLocals(this LookupOptions options)
        {
            return (options & (LookupOptions.NamespaceAliasesOnly | LookupOptions.NamespacesOrTypesOnly | LookupOptions.LabelsOnly)) == 0;
        }

        internal static bool CanConsiderTypes(this LookupOptions options)
        {
            return (options & (LookupOptions.NamespaceAliasesOnly | LookupOptions.MustBeInvocableIfMember | LookupOptions.MustBeInstance | LookupOptions.LabelsOnly)) == 0;
        }

        internal static bool CanConsiderNamespaces(this LookupOptions options)
        {
            return (options & (LookupOptions.MustNotBeNamespace | LookupOptions.MustBeInvocableIfMember | LookupOptions.MustBeInstance | LookupOptions.LabelsOnly)) == 0;
        }

        internal static bool IsAttributeTypeLookup(this LookupOptions options)
        {
            return (options & LookupOptions.AttributeTypeOnly) == LookupOptions.AttributeTypeOnly;
        }

        internal static bool IsVerbatimNameAttributeTypeLookup(this LookupOptions options)
        {
            return (options & LookupOptions.VerbatimNameAttributeTypeOnly) == LookupOptions.VerbatimNameAttributeTypeOnly;
        }
    }
}
