﻿// 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.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.MoveStaticMembers
{
    internal class StaticMemberSelectionViewModel : AbstractNotifyPropertyChanged
    {
        private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor;
        private readonly ImmutableDictionary<ISymbol, Task<ImmutableArray<ISymbol>>> _symbolToDependentsMap;
        private readonly ImmutableDictionary<ISymbol, SymbolViewModel<ISymbol>> _symbolToMemberViewMap;

        public StaticMemberSelectionViewModel(
            IUIThreadOperationExecutor uiThreadOperationExecutor,
            ImmutableArray<SymbolViewModel<ISymbol>> members,
            ImmutableDictionary<ISymbol, Task<ImmutableArray<ISymbol>>> dependentsMap)
        {
            _uiThreadOperationExecutor = uiThreadOperationExecutor;
            _members = members;
            _symbolToDependentsMap = dependentsMap;
            _symbolToMemberViewMap = members.ToImmutableDictionary(memberViewModel => memberViewModel.Symbol);
        }

        public ImmutableArray<SymbolViewModel<ISymbol>> CheckedMembers => Members.WhereAsArray(m => m.IsChecked);

        private ImmutableArray<SymbolViewModel<ISymbol>> _members;
        public ImmutableArray<SymbolViewModel<ISymbol>> Members
        {
            get => _members;
            set => SetProperty(ref _members, value);
        }

        public void SelectAll()
            => SelectMembers(Members);

        internal void DeselectAll()
            => SelectMembers(Members, isChecked: false);

        public void SelectDependents()
        {
            var checkedMembers = Members
                .WhereAsArray(member => member.IsChecked);

            var result = _uiThreadOperationExecutor.Execute(
                title: ServicesVSResources.Move_static_members_to_another_type_colon,
                defaultDescription: ServicesVSResources.Calculating_dependents,
                allowCancellation: true,
                showProgress: true,
                context =>
                {
                    foreach (var member in Members)
                    {
                        _symbolToDependentsMap[member.Symbol].Wait(context.UserCancellationToken);
                    }
                });

            if (result == UIThreadOperationStatus.Completed)
            {
                foreach (var member in checkedMembers)
                {
                    var membersToSelected = FindDependents(member.Symbol).SelectAsArray(symbol => _symbolToMemberViewMap[symbol]);
                    SelectMembers(membersToSelected);
                }
            }
        }

        private static void SelectMembers(ImmutableArray<SymbolViewModel<ISymbol>> members, bool isChecked = true)
        {
            foreach (var member in members)
            {
                member.IsChecked = isChecked;
            }
        }

        private ImmutableHashSet<ISymbol> FindDependents(ISymbol member)
        {
            var queue = new Queue<ISymbol>();
            // Under situation like two methods call each other, this hashset is used to 
            // prevent the infinity loop.
            var visited = new HashSet<ISymbol>();
            var result = new HashSet<ISymbol>();
            queue.Enqueue(member);
            visited.Add(member);
            while (!queue.IsEmpty())
            {
                var currentMember = queue.Dequeue();
                result.Add(currentMember);
                visited.Add(currentMember);
                foreach (var dependent in _symbolToDependentsMap[currentMember].Result)
                {
                    if (!visited.Contains(dependent))
                    {
                        queue.Enqueue(dependent);
                    }
                }
            }

            return result.ToImmutableHashSet();
        }
    }
}
