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

using System.Windows.Forms.Layout;

namespace System.Windows.Forms;

public sealed partial class Application
{
    /// <summary>
    ///  This class embodies our parking window, which we create when the
    ///  first message loop is pushed onto the thread.
    /// </summary>
    internal sealed class ParkingWindow : ContainerControl, IArrangedElement
    {
        // In .NET 2.0 we now aggressively tear down the parking window
        //   when the last control has been removed off of it.

        private const int WM_CHECKDESTROY = (int)PInvoke.WM_USER + 0x01;

        private int _childCount;

        public ParkingWindow()
        {
            SetExtendedState(ExtendedStates.InterestedInUserPreferenceChanged, false);
            SetState(States.TopLevel, true);
            Text = "WindowsFormsParkingWindow";
            Visible = false;
        }

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;

                // Message only windows are cheaper and have fewer issues than full blown invisible windows.
                cp.Parent = HWND.HWND_MESSAGE;
                return cp;
            }
        }

        internal override void AddReflectChild()
        {
            if (_childCount < 0)
            {
                Debug.Fail("How did parkingwindow childcount go negative???");
                _childCount = 0;
            }

            _childCount++;
        }

        internal override void RemoveReflectChild()
        {
            if (--_childCount < 0)
            {
                Debug.Fail("How did parkingwindow childcount go negative???");
                _childCount = 0;
            }

            if (_childCount != 0 || !IsHandleCreated)
            {
                return;
            }

            // Check to see if we are running on the thread that owns the parkingwindow.
            // If so, we can destroy immediately.
            //
            // This is important for scenarios where apps leak controls until after the
            // messagepump is gone and then decide to clean them up.  We should clean
            // up the parkingwindow in this case and a postmessage won't do it.
            uint id = PInvoke.GetWindowThreadProcessId(HWNDInternal, out _);
            ThreadContext? context = ThreadContext.FromId(id);

            // We only do this if the ThreadContext tells us that we are currently handling a window message.
            if (context is null || !ReferenceEquals(context, ThreadContext.FromCurrent()))
            {
                PInvoke.PostMessage(HWNDInternal, WM_CHECKDESTROY);
            }
            else
            {
                CheckDestroy();
            }
        }

        private void CheckDestroy()
        {
            if (_childCount != 0)
                return;

            HWND hwndChild = PInvoke.GetWindow(this, GET_WINDOW_CMD.GW_CHILD);
            if (hwndChild.IsNull)
            {
                DestroyHandle();
            }
        }

        public void Destroy()
        {
            DestroyHandle();
        }

        /// <summary>
        ///  "Parks" the given HWND to a temporary HWND. This allows WS_CHILD windows to be parked.
        /// </summary>
        internal void ParkHandle<T>(T handle) where T : IHandle<HWND>
        {
            if (!IsHandleCreated)
            {
                CreateHandle();
            }

            PInvoke.SetParent(handle, (IHandle<HWND>)this);
        }

        /// <summary>
        ///  "Unparks" the given HWND to a temporary HWND. This allows WS_CHILD windows to be parked.
        /// </summary>
        internal void UnparkHandle<T>(T handle) where T : IHandle<HWND>
        {
            if (!IsHandleCreated)
            {
                return;
            }

            Debug.Assert(
                PInvoke.GetParent(handle) != HWND,
                "Always set the handle's parent to someone else before calling UnparkHandle");

            // If there are no child windows in this handle any longer, destroy the parking window.
            CheckDestroy();
        }

        // Do nothing on layout to reduce the calls into the LayoutEngine while debugging.
        protected override void OnLayout(LayoutEventArgs levent) { }
        void IArrangedElement.PerformLayout(IArrangedElement affectedElement, string? affectedProperty) { }

        protected override void WndProc(ref Message m)
        {
            if (m.MsgInternal == PInvoke.WM_SHOWWINDOW)
                return;

            base.WndProc(ref m);
            switch (m.MsgInternal)
            {
                case PInvoke.WM_PARENTNOTIFY:
                    if (m.WParamInternal.LOWORD == PInvoke.WM_DESTROY)
                    {
                        PInvoke.PostMessage(this, WM_CHECKDESTROY);
                    }

                    break;
                case WM_CHECKDESTROY:
                    CheckDestroy();
                    break;
            }
        }
    }
}
