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

#nullable disable

using System;
using System.Linq;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Shell;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options
{
    [System.ComponentModel.DesignerCategory("code")] // this must be fully qualified
    internal abstract class AbstractOptionPage : UIElementDialogPage
    {
        private static OptionStore s_optionStore;
        private static bool s_needsToUpdateOptionStore = true;

        private bool _needsLoadOnNextActivate = true;

        protected abstract AbstractOptionPageControl CreateOptionPage(IServiceProvider serviceProvider, OptionStore optionStore);

        protected AbstractOptionPageControl pageControl;

        private void EnsureOptionPageCreated()
        {
            if (s_optionStore == null)
            {
                var componentModel = (IComponentModel)this.Site.GetService(typeof(SComponentModel));
                s_optionStore = new OptionStore(componentModel.GetService<IGlobalOptionService>());
            }

            // Use a single option store for all option pages so that changes are accumulated
            // together and, in the case of the same option appearing on two pages, the changes
            // are kept in sync.
            pageControl ??= CreateOptionPage(this.Site, s_optionStore);
        }

        protected override System.Windows.UIElement Child
        {
            get
            {
                EnsureOptionPageCreated();
                return pageControl;
            }
        }

        protected override void OnActivate(System.ComponentModel.CancelEventArgs e)
        {
            EnsureOptionPageCreated();

            if (s_needsToUpdateOptionStore)
            {
                // Reset the option store to the current state of the options.
                s_optionStore.Clear();

                s_needsToUpdateOptionStore = false;
            }

            if (_needsLoadOnNextActivate)
            {
                // For pages that don't use option bindings we need to load setting changes.
                pageControl.OnLoad();

                _needsLoadOnNextActivate = false;
            }
        }

        public override void LoadSettingsFromStorage()
        {
            // This gets called in two situations:
            //
            // 1) during the initial page load when you first activate the page, before OnActivate
            //    is called.
            // 2) during the closing of the dialog via Cancel/close when options don't need to be
            //    saved. The intent here is the settings get reloaded so the next time you open the
            //    page they are properly populated.
            //
            // We need to ignore the first case since the option store is static and shared among 
            // pages. Each page will get this same call so we should ensure that our page has been
            // created first.
            //
            // This second one is tricky, because we don't actually want to update our controls
            // right then, because they'd be wrong the next time the page opens -- it's possible
            // they may have been changed programmatically. Therefore, we'll set a flag so we load
            // next time

            // When pageControl is null we know that Activation has not happened for this page.
            // We only need to update the OptionStore after a cancel or close click (2).
            s_needsToUpdateOptionStore = pageControl != null;

            // For pages that don't use option bindings we need to load settings when it is
            // activated next.
            _needsLoadOnNextActivate = true;
        }

        public override void SaveSettingsToStorage()
        {
            EnsureOptionPageCreated();

            // Allow page controls to perist their settings to the option store before updating the
            // option service.
            pageControl.OnSave();

            var changedOptions = s_optionStore.GetChangedOptions();
            OptionLogger.Log(changedOptions);

            s_optionStore.GlobalOptions.SetGlobalOptions(changedOptions.SelectAsArray(entry => KeyValuePairUtil.Create(entry.key, entry.newValue)));

            // Make sure we load the next time a page is activated, in case that options changed
            // programmatically between now and the next time the page is activated
            s_needsToUpdateOptionStore = true;

            // For pages that don't use option bindings we need to load settings when it is
            // activated next.
            _needsLoadOnNextActivate = true;
        }

        protected override void SearchStringChanged(string searchString)
        {
            pageControl.OnSearch(searchString);
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);

            if (pageControl != null)
            {
                // Clear the search because we don't recreate controls
                pageControl.OnSearch(string.Empty);
                pageControl.Close();
            }
        }
    }
}
