﻿// 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.Threading;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.BackgroundWorkIndicator;

internal partial class WpfBackgroundWorkIndicatorFactory
{
    /// <summary>
    /// Implementation of an <see cref="IUIThreadOperationScope"/> for the background work indicator. Allows for
    /// features to create nested work with descriptions/progress that will update the all-up indicator tool-tip
    /// shown to the user.
    /// </summary>
    private sealed class BackgroundWorkIndicatorScope : IUIThreadOperationScope, IProgress<ProgressInfo>
    {
        private readonly BackgroundWorkIndicatorContext _context;

        // Mutable state of this scope.  Can be mutated by a client, at which point we'll ask our owning context to
        // update the tooltip accordingly.

        private string _description;
        private ProgressInfo _progressInfo;

        public IUIThreadOperationContext Context => _context;
        public IProgress<ProgressInfo> Progress => this;

        public BackgroundWorkIndicatorScope(
            BackgroundWorkIndicatorContext indicator, string description)
        {
            _context = indicator;
            _description = description;
        }

        /// <summary>
        /// Retrieves a threadsafe snapshot of our data for our owning context to use to build the tooltip ui.
        /// </summary>
        public (string description, ProgressInfo progressInfo) ReadData_MustBeCalledUnderLock()
        {
            Contract.ThrowIfFalse(Monitor.IsEntered(_context.Gate));
            return (_description, _progressInfo);
        }

        /// <summary>
        /// On disposal, just remove ourselves from our parent context.  It will update the UI accordingly.
        /// </summary>
        void IDisposable.Dispose()
            => _context.RemoveScope(this);

        bool IUIThreadOperationScope.AllowCancellation
        {
            get => true;
            set { }
        }

        string IUIThreadOperationScope.Description
        {
            get
            {
                lock (_context.Gate)
                    return _description;
            }
            set
            {
                lock (_context.Gate)
                {
                    _description = value;
                }

                // We changed.  Enqueue work to make sure the UI reflects this.
                _context.EnqueueUIUpdate();
            }
        }

        void IProgress<ProgressInfo>.Report(ProgressInfo value)
        {
            lock (_context.Gate)
            {
                _progressInfo = value;
            }

            // We changed.  Enqueue work to make sure the UI reflects this.
            _context.EnqueueUIUpdate();
        }
    }
}
