// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;

namespace System.Windows.Forms.Design;

/// <summary>
///  Provides a user interface for <see cref="WindowsFormsComponentEditor"/>.
/// </summary>
[ToolboxItem(false)]
public partial class ComponentEditorForm : Form
{
    private readonly IComponent _component;
    private readonly Type[] _pageTypes;
    private ComponentEditorPageSite[] _pageSites;
    private Size _maxSize = Size.Empty;
    private int _initialActivePage;
    private int _activePage;
    private bool _dirty;
    private bool _firstActivate;

    private readonly Panel _pageHost = new();
    private PageSelector _selector;
    private ImageList _selectorImageList;
    private Button _okButton;
    private Button _cancelButton;
    private Button _applyButton;
    private Button _helpButton;

    private const int BUTTON_WIDTH = 80;
    private const int BUTTON_HEIGHT = 23;
    private const int BUTTON_PAD = 6;
    private const int MIN_SELECTOR_WIDTH = 90;
    private const int SELECTOR_PADDING = 10;
    private const int STRIP_HEIGHT = 4;

    /// <summary>
    ///  Initializes a new instance of the <see cref="ComponentEditorForm"/> class.
    /// </summary>
    public ComponentEditorForm(object component, Type[] pageTypes) : base()
    {
        if (component is not IComponent)
        {
            throw new ArgumentException(SR.ComponentEditorFormBadComponent, nameof(component));
        }

        _component = (IComponent)component;
        _pageTypes = pageTypes;
        _dirty = false;
        _firstActivate = true;
        _activePage = -1;
        _initialActivePage = 0;

        FormBorderStyle = FormBorderStyle.FixedDialog;
        MinimizeBox = false;
        MaximizeBox = false;
        ShowInTaskbar = false;
        Icon = null;
        StartPosition = FormStartPosition.CenterParent;

        OnNewObjects();
        OnConfigureUI();
    }

    /// <summary>
    ///  Applies any changes in the set of ComponentPageControl to the actual component.
    /// </summary>
    internal virtual void ApplyChanges(bool lastApply)
    {
        if (!_dirty)
        {
            return;
        }

        if (_component.Site.TryGetService(out IComponentChangeService? changeService))
        {
            try
            {
                changeService.OnComponentChanging(_component, null);
            }
            catch (CheckoutException e) when (e == CheckoutException.Canceled)
            {
                return;
            }
        }

        for (int n = 0; n < _pageSites.Length; n++)
        {
            if (_pageSites[n].Dirty)
            {
                _pageSites[n].GetPageControl().ApplyChanges();
                _pageSites[n].Dirty = false;
            }
        }

        changeService?.OnComponentChanged(_component);

        _applyButton.Enabled = false;
        _cancelButton.Text = SR.CloseCaption;
        _dirty = false;

        if (!lastApply)
        {
            for (int n = 0; n < _pageSites.Length; n++)
            {
                _pageSites[n].GetPageControl().OnApplyComplete();
            }
        }
    }

    /// <summary>
    ///  Hide the property
    /// </summary>
    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public override bool AutoSize
    {
        get => base.AutoSize;
        set => base.AutoSize = value;
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public new event EventHandler? AutoSizeChanged
    {
        add => base.AutoSizeChanged += value;
        remove => base.AutoSizeChanged -= value;
    }

    /// <summary>
    ///  Handles ok/cancel/apply/help button click events
    /// </summary>
    private void OnButtonClick(object? sender, EventArgs e)
    {
        if (sender == _okButton)
        {
            ApplyChanges(true);
            DialogResult = DialogResult.OK;
        }
        else if (sender == _cancelButton)
        {
            DialogResult = DialogResult.Cancel;
        }
        else if (sender == _applyButton)
        {
            ApplyChanges(false);
        }
        else if (sender == _helpButton)
        {
            ShowPageHelp();
        }
    }

    /// <summary>
    ///  Lays out the UI of the form.
    /// </summary>
    [MemberNotNull(nameof(_okButton))]
    [MemberNotNull(nameof(_cancelButton))]
    [MemberNotNull(nameof(_applyButton))]
    [MemberNotNull(nameof(_helpButton))]
    [MemberNotNull(nameof(_selectorImageList))]
    [MemberNotNull(nameof(_selector))]
    private void OnConfigureUI()
    {
        Font? uiFont = DefaultFont;
        if (_component.Site?.TryGetService(out IUIService? uiService) == true)
        {
            uiFont = (Font?)uiService.Styles["DialogFont"];
        }

        Font = uiFont;

        _okButton = new Button();
        _cancelButton = new Button();
        _applyButton = new Button();
        _helpButton = new Button();

        _selectorImageList = new ImageList
        {
            ImageSize = new Size(16, 16)
        };
        _selector = new PageSelector
        {
            ImageList = _selectorImageList
        };
        _selector.AfterSelect += OnSelChangeSelector;

        Label grayStrip = new Label
        {
            BackColor = SystemColors.ControlDark
        };

        int selectorWidth = MIN_SELECTOR_WIDTH;

        if (_pageSites is not null && _pageSites.Length != 0)
        {
            using Graphics graphics = CreateGraphicsInternal();

            // Add the nodes corresponding to the pages
            for (int n = 0; n < _pageSites.Length; n++)
            {
                ComponentEditorPage page = _pageSites[n].GetPageControl();

                string title = page.Title;
                int titleWidth = (int)graphics.MeasureString(title, Font).Width;
                _selectorImageList.Images.Add(page.Icon.ToBitmap());

                _selector.Nodes.Add(new TreeNode(title, n, n));
                if (titleWidth > selectorWidth)
                {
                    selectorWidth = titleWidth;
                }
            }
        }

        selectorWidth += SELECTOR_PADDING;

        string caption = string.Empty;
        ISite? site = _component.Site;
        if (site is not null)
        {
            caption = string.Format(SR.ComponentEditorFormProperties, site.Name);
        }
        else
        {
            caption = SR.ComponentEditorFormPropertiesNoName;
        }

        Text = caption;

        Rectangle pageHostBounds = new(2 * BUTTON_PAD + selectorWidth, 2 * BUTTON_PAD + STRIP_HEIGHT,
                                                 _maxSize.Width, _maxSize.Height);
        _pageHost.Bounds = pageHostBounds;
        grayStrip.Bounds = new Rectangle(pageHostBounds.X, BUTTON_PAD,
                                         pageHostBounds.Width, STRIP_HEIGHT);

        if (_pageSites is not null)
        {
            Rectangle pageBounds = new(0, 0, pageHostBounds.Width, pageHostBounds.Height);
            for (int n = 0; n < _pageSites.Length; n++)
            {
                ComponentEditorPage page = _pageSites[n].GetPageControl();
                page.GetControl().Bounds = pageBounds;
            }
        }

        int xFrame = SystemInformation.FixedFrameBorderSize.Width;
        Rectangle bounds = pageHostBounds;
        Size size = new(bounds.Width + 3 * (BUTTON_PAD + xFrame) + selectorWidth,
                               bounds.Height + STRIP_HEIGHT + 4 * BUTTON_PAD + BUTTON_HEIGHT +
                               2 * xFrame + SystemInformation.CaptionHeight);
        Size = size;

        _selector.Bounds = new Rectangle(BUTTON_PAD, BUTTON_PAD,
                                        selectorWidth, bounds.Height + STRIP_HEIGHT + 2 * BUTTON_PAD + BUTTON_HEIGHT);

        bounds.X = bounds.Width + bounds.X - BUTTON_WIDTH;
        bounds.Y = bounds.Height + bounds.Y + BUTTON_PAD;
        bounds.Width = BUTTON_WIDTH;
        bounds.Height = BUTTON_HEIGHT;

        _helpButton.Bounds = bounds;
        _helpButton.Text = SR.HelpCaption;
        _helpButton.Click += OnButtonClick;
        _helpButton.Enabled = false;
        _helpButton.FlatStyle = FlatStyle.System;

        bounds.X -= (BUTTON_WIDTH + BUTTON_PAD);
        _applyButton.Bounds = bounds;
        _applyButton.Text = SR.ApplyCaption;
        _applyButton.Click += OnButtonClick;
        _applyButton.Enabled = false;
        _applyButton.FlatStyle = FlatStyle.System;

        bounds.X -= (BUTTON_WIDTH + BUTTON_PAD);
        _cancelButton.Bounds = bounds;
        _cancelButton.Text = SR.CancelCaption;
        _cancelButton.Click += OnButtonClick;
        _cancelButton.FlatStyle = FlatStyle.System;
        CancelButton = _cancelButton;

        bounds.X -= (BUTTON_WIDTH + BUTTON_PAD);
        _okButton.Bounds = bounds;
        _okButton.Text = SR.OKCaption;
        _okButton.Click += OnButtonClick;
        _okButton.FlatStyle = FlatStyle.System;
        AcceptButton = _okButton;

        Controls.Clear();
        Controls.AddRange(
        [
            _selector,
            grayStrip,
            _pageHost,
            _okButton,
            _cancelButton,
            _applyButton,
            _helpButton
        ]);

        // Continuing with the old autoscale base size stuff, it works, and is currently set to a non-standard height.
        AutoScaleBaseSize = new Size(5, 14);
#pragma warning disable CS0618 // Type or member is obsolete
        ApplyAutoScaling();
#pragma warning restore CS0618
    }

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

        if (_firstActivate)
        {
            _firstActivate = false;

            _selector.SelectedNode = _selector.Nodes[_initialActivePage];
            _pageSites[_initialActivePage].Active = true;
            _activePage = _initialActivePage;

            _helpButton.Enabled = _pageSites[_activePage].GetPageControl().SupportsHelp();
        }
    }

    //
    protected override void OnHelpRequested(HelpEventArgs e)
    {
        base.OnHelpRequested(e);
        ShowPageHelp();
    }

    /// <summary>
    ///  Called to initialize this form with the new component.
    /// </summary>
    [MemberNotNull(nameof(_pageSites))]
    private void OnNewObjects()
    {
        _maxSize = new Size(3 * (BUTTON_WIDTH + BUTTON_PAD), 24 * _pageTypes.Length);

        _pageSites = new ComponentEditorPageSite[_pageTypes.Length];

        // create sites for them
        for (int n = 0; n < _pageTypes.Length; n++)
        {
            _pageSites[n] = new ComponentEditorPageSite(_pageHost, _pageTypes[n], _component, this);
            ComponentEditorPage page = _pageSites[n].GetPageControl();

            Size pageSize = page.Size;
            if (pageSize.Width > _maxSize.Width)
            {
                _maxSize.Width = pageSize.Width;
            }

            if (pageSize.Height > _maxSize.Height)
            {
                _maxSize.Height = pageSize.Height;
            }
        }

        // and set them all to an ideal size
        for (int n = 0; n < _pageSites.Length; n++)
        {
            _pageSites[n].GetPageControl().Size = _maxSize;
        }
    }

    /// <summary>
    ///  Handles switching between pages.
    /// </summary>
    protected virtual void OnSelChangeSelector(object? source, TreeViewEventArgs e)
    {
        if (_firstActivate)
        {
            // treeview seems to fire a change event when it is first setup before
            // the form is activated
            return;
        }

        int newPage = _selector.SelectedNode!.Index;
        Debug.Assert((newPage >= 0) && (newPage < _pageSites.Length),
                     "Invalid page selected");

        if (newPage == _activePage)
        {
            return;
        }

        if (_activePage != -1)
        {
            if (_pageSites[_activePage].AutoCommit)
            {
                ApplyChanges(false);
            }

            _pageSites[_activePage].Active = false;
        }

        _activePage = newPage;
        _pageSites[_activePage].Active = true;
        _helpButton.Enabled = _pageSites[_activePage].GetPageControl().SupportsHelp();
    }

    /// <summary>
    ///  Provides a method to override in order to pre-process input messages before
    ///  they are dispatched.
    /// </summary>
    public override bool PreProcessMessage(ref Message msg)
    {
        if (_pageSites is not null && _pageSites[_activePage].GetPageControl().IsPageMessage(ref msg))
        {
            return true;
        }

        return base.PreProcessMessage(ref msg);
    }

    /// <summary>
    ///  Sets the controls of the form to dirty. This enables the "apply"
    ///  button.
    /// </summary>
    internal virtual void SetDirty()
    {
        _dirty = true;
        _applyButton.Enabled = true;
        _cancelButton.Text = SR.CancelCaption;
    }

    /// <summary>
    ///  Shows the form. The form will have no owner window.
    /// </summary>
    public virtual DialogResult ShowForm()
    {
        return ShowForm(null, 0);
    }

    /// <summary>
    ///  Shows the form and the specified page. The form will have no owner window.
    /// </summary>
    public virtual DialogResult ShowForm(int page)
    {
        return ShowForm(null, page);
    }

    /// <summary>
    ///  Shows the form with the specified owner.
    /// </summary>
    public virtual DialogResult ShowForm(IWin32Window? owner)
    {
        return ShowForm(owner, 0);
    }

    /// <summary>
    ///  Shows the form and the specified page with the specified owner.
    /// </summary>
    public virtual DialogResult ShowForm(IWin32Window? owner, int page)
    {
        _initialActivePage = page;
        ShowDialog(owner);
        return DialogResult;
    }

    /// <summary>
    ///  Shows help for the active page.
    /// </summary>
    private void ShowPageHelp()
    {
        Debug.Assert(_activePage != -1);

        if (_pageSites[_activePage].GetPageControl().SupportsHelp())
        {
            _pageSites[_activePage].GetPageControl().ShowHelp();
        }
    }
}
