// 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 System.Linq.Expressions;
using System.Reflection;

namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
    internal readonly struct MemberExpressionCacheKey
    {
        public MemberExpressionCacheKey(Type modelType, MemberExpression memberExpression)
        {
            ModelType = modelType;
            MemberExpression = memberExpression;
            Members = null;
        }

        public MemberExpressionCacheKey(Type modelType, MemberInfo[] members)
        {
            ModelType = modelType;
            Members = members;
            MemberExpression = null;
        }

        // We want to avoid caching a MemberExpression since it has references to other instances in the expression tree.
        // We instead store it as a series of MemberInfo items that comprise of the MemberExpression going from right-most
        // expression to left.
        public MemberExpressionCacheKey MakeCacheable()
        {
            var members = new List<MemberInfo>();
            foreach (var member in this)
            {
                members.Add(member);
            }

            return new MemberExpressionCacheKey(ModelType, members.ToArray());
        }

        public MemberExpression MemberExpression { get; }

        public Type ModelType { get; }

        public MemberInfo[] Members { get; }

        public Enumerator GetEnumerator() => new Enumerator(this);

        public struct Enumerator
        {
            private readonly MemberInfo[] _members;
            private int _index;
            private MemberExpression _memberExpression;

            public Enumerator(in MemberExpressionCacheKey key)
            {
                Current = null;
                _members = key.Members;
                _memberExpression = key.MemberExpression;
                _index = -1;
            }

            public MemberInfo Current { get; private set; }

            public bool MoveNext()
            {
                if (_members != null)
                {
                    _index++;
                    if (_index >= _members.Length)
                    {
                        return false;
                    }

                    Current = _members[_index];
                    return true;
                }

                if (_memberExpression == null)
                {
                    return false;
                }

                Current = _memberExpression.Member;
                _memberExpression = _memberExpression.Expression as MemberExpression;
                return true;
            }
        }
    }
}
