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

using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CSharp.RuntimeBinder.Syntax;

namespace Microsoft.CSharp.RuntimeBinder.Semantics
{
    internal partial class CMemberLookupResults
    {
        public class CMethodIterator
        {
            // Inputs.
            private readonly AggregateSymbol _context;
            private readonly TypeArray _containingTypes;
            private readonly CType _qualifyingType;
            private readonly Name _name;
            private readonly int _arity;
            private readonly symbmask_t _mask;
            private readonly EXPRFLAG _flags;
            private readonly ArgInfos _nonTrailingNamedArguments;
            // Internal state.
            private int _currentTypeIndex;

            public CMethodIterator(Name name, TypeArray containingTypes, CType qualifyingType, AggregateSymbol context, int arity, EXPRFLAG flags, symbmask_t mask, ArgInfos nonTrailingNamedArguments)
            {
                Debug.Assert(name != null);
                Debug.Assert(containingTypes != null);
                Debug.Assert(containingTypes.Count != 0);

                _name = name;
                _containingTypes = containingTypes;
                _qualifyingType = qualifyingType;
                _context = context;
                _arity = arity;
                _flags = flags;
                _mask = mask;
                _nonTrailingNamedArguments = nonTrailingNamedArguments;
            }

            public MethodOrPropertySymbol CurrentSymbol { get; private set; }

            public AggregateType CurrentType { get; private set; }

            public bool IsCurrentSymbolInaccessible { get; private set; }

            public bool IsCurrentSymbolBogus { get; private set; }

            public bool IsCurrentSymbolMisnamed { get; private set; }

            public bool MoveNext() => (CurrentType != null || FindNextTypeForInstanceMethods()) && FindNextMethod();

            public bool AtEnd => CurrentSymbol == null;

            public bool CanUseCurrentSymbol
            {
                get
                {
                    // Make sure that whether we're seeing a ctor is consistent with the flag.
                    // The only properties we handle are indexers.
                    if (_mask == symbmask_t.MASK_MethodSymbol && (
                            0 == (_flags & EXPRFLAG.EXF_CTOR) != !((MethodSymbol)CurrentSymbol).IsConstructor() ||
                            0 == (_flags & EXPRFLAG.EXF_OPERATOR) != !((MethodSymbol)CurrentSymbol).isOperator) ||
                        _mask == symbmask_t.MASK_PropertySymbol && !(CurrentSymbol is IndexerSymbol))
                    {
                        // Get the next symbol.
                        return false;
                    }

                    // If our arity is non-0, we must match arity with this symbol.
                    if (_arity > 0 & _mask == symbmask_t.MASK_MethodSymbol && ((MethodSymbol)CurrentSymbol).typeVars.Count != _arity)
                    {
                        return false;
                    }

                    // If this guy's not callable, no good.
                    if (!ExpressionBinder.IsMethPropCallable(CurrentSymbol, (_flags & EXPRFLAG.EXF_USERCALLABLE) != 0))
                    {
                        return false;
                    }

                    // Check access. If Sym is not accessible, then let it through and mark it.
                    IsCurrentSymbolInaccessible = !CSemanticChecker.CheckAccess(CurrentSymbol, CurrentType, _context, _qualifyingType);

                    // Check bogus. If Sym is bogus, then let it through and mark it.
                    IsCurrentSymbolBogus = CSemanticChecker.CheckBogus(CurrentSymbol);

                    IsCurrentSymbolMisnamed = CheckArgumentNames();

                    return true;
                }
            }

            private bool CheckArgumentNames()
            {
                ArgInfos args = _nonTrailingNamedArguments;
                if (args != null)
                {
                    List<Name> paramNames = ExpressionBinder.GroupToArgsBinder
                        .FindMostDerivedMethod(CurrentSymbol, _qualifyingType)
                        .ParameterNames;

                    List<Expr> argExpressions = args.prgexpr;
                    for (int i = 0; i < args.carg; i++)
                    {
                        if (argExpressions[i] is ExprNamedArgumentSpecification named)
                        {
                            // Either wrong name, or correct name but we have more params arguments to follow.
                            if (paramNames[i] != named.Name || i == paramNames.Count - 1 && i != args.carg - 1)
                            {
                                return true;
                            }
                        }
                    }
                }

                return false;
            }

            private bool FindNextMethod()
            {
                for (;;)
                {
                    CurrentSymbol = (CurrentSymbol == null
                        ? SymbolLoader.LookupAggMember(_name, CurrentType.OwningAggregate, _mask)
                        : CurrentSymbol.LookupNext(_mask)) as MethodOrPropertySymbol;

                    // If we couldn't find a sym, we look up the type chain and get the next type.
                    if (CurrentSymbol == null)
                    {
                        if (!FindNextTypeForInstanceMethods())
                        {
                            return false;
                        }
                    }
                    else
                    {
                        // Note that we do not filter the current symbol for the user. They must do that themselves.
                        // This is because for instance, BindGrpToArgs wants to filter on arguments before filtering
                        // on bogosity.

                        // If we're here, we're good to go.

                        return true;
                    }
                }
            }

            private bool FindNextTypeForInstanceMethods()
            {
                if (_currentTypeIndex >= _containingTypes.Count)
                {
                    // No more types to check.
                    CurrentType = null;
                    return false;
                }

                CurrentType = _containingTypes[_currentTypeIndex++] as AggregateType;
                return true;
            }
        }
    }
}
