﻿// 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;
using System.Collections.Generic;
using System.Linq;
using EnvDTE80;

namespace Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess
{
    internal class LocalsWindow_InProc : InProcComponent
    {
        public static LocalsWindow_InProc Create() => new LocalsWindow_InProc();

        public int GetCount()
        {
            var dte = ((DTE2)GetDTE());
            if (dte.Debugger.CurrentStackFrame != null) // Ensure that debugger is running
            {
                var locals = dte.Debugger.CurrentStackFrame.Locals;
                return locals.Count;
            }

            return 0;
        }

        public Common.Expression GetEntry(params string[] entryNames)
        {
            var dte = ((DTE2)GetDTE());
            if (dte.Debugger.CurrentStackFrame == null) // Ensure that debugger is running
            {
                throw new Exception($"Could not find locals. Debugger is not running.");
            }

            var expressions = dte.Debugger.CurrentStackFrame.Locals;
            EnvDTE.Expression? entry = null;

            var i = 0;
            while (i < entryNames.Length && TryGetEntryInternal(entryNames[i], expressions, out entry))
            {
                i++;
                expressions = entry.DataMembers;
            }

            if ((i == entryNames.Length) && (entry != null))
            {
                return new Common.Expression(entry);
            }

            var localHierarchicalName = string.Join("->", entryNames);
            var allLocalsString = string.Join("\n", GetAllLocals(dte.Debugger.CurrentStackFrame.Locals));
            throw new Exception($"\nCould not find the local named {localHierarchicalName}.\nAll available locals are: \n{allLocalsString}");
        }

        private static bool TryGetEntryInternal(string entryName, EnvDTE.Expressions expressions, out EnvDTE.Expression expression)
        {
            expression = expressions.Cast<EnvDTE.Expression>().FirstOrDefault(e => e.Name == entryName);
            if (expression != null)
            {
                return true;
            }

            return false;
        }

        private static IEnumerable<string> GetAllLocals(EnvDTE.Expressions expressions)
        {
            foreach (var expression in expressions.Cast<EnvDTE.Expression>())
            {
                var expressionName = expression.Name;
                yield return expressionName;
                var nestedExpressions = expression.DataMembers;
                if (nestedExpressions != null)
                {
                    foreach (var nestedLocal in GetAllLocals(nestedExpressions))
                    {
                        yield return string.Format("{0}->{1}", expressionName, nestedLocal);
                    }
                }
            }
        }
    }
}
