// 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.Diagnostics;
using static Interop;

namespace System.Windows.Forms.Design
{
    public partial class ControlDesigner
    {
        private class ChildWindowTarget : IWindowTarget, IDesignerTarget
        {
            private readonly ControlDesigner _designer;
            private readonly Control _childControl;
            private IntPtr _handle = IntPtr.Zero;

            public ChildWindowTarget(ControlDesigner designer, Control childControl, IWindowTarget oldWindowTarget)
            {
                _designer = designer;
                _childControl = childControl;
                OldWindowTarget = oldWindowTarget;
            }

            public IWindowTarget OldWindowTarget { get; }

            public void DefWndProc(ref Message m) => OldWindowTarget.OnMessage(ref m);

            public void Dispose()
            {
                // Do nothing. We will pick this up through a null DesignerTarget property when we come out of the message loop.
            }

            public void OnHandleChange(IntPtr newHandle)
            {
                _handle = newHandle;
                OldWindowTarget.OnHandleChange(newHandle);
            }

            public void OnMessage(ref Message m)
            {
                // If the designer has jumped ship, the continue partying on messages, but send them back to the original control.
                if (_designer.Component is null)
                {
                    OldWindowTarget.OnMessage(ref m);
                    return;
                }

                // We want these messages to go through the designer's WndProc method, and we want people to be able
                // to do default processing with the designer's DefWndProc.  So, we stuff the old window target into
                // the designer's target and then call their WndProc.
                IDesignerTarget designerTarget = _designer.DesignerTarget;
                _designer.DesignerTarget = this;

                try
                {
                    _designer.WndProc(ref m);
                }
                catch (Exception ex)
                {
                    _designer.SetUnhandledException(_childControl, ex);
                }
                finally
                {
                    // If the designer disposed us, then we should follow suit.
                    if (_designer.DesignerTarget is null)
                    {
                        designerTarget.Dispose();
                    }
                    else
                    {
                        _designer.DesignerTarget = designerTarget;
                    }

                    // Controls (primarily RichEdit) will register themselves as drag-drop source/targets when they
                    // are instantiated. Normally, when they are being designed, we will RevokeDragDrop() in their
                    // designers. The problem occurs when these controls are inside a UserControl. At that time, we
                    // do not have a designer for these controls, and they prevent the ParentControlDesigner's
                    // drag-drop from working. What we do is to loop through all child controls that do not have a
                    // designer (in HookChildControls()), and RevokeDragDrop() after their handles have been created.
                    if (m.Msg == (int)User32.WM.CREATE)
                    {
                        Debug.Assert(_handle != IntPtr.Zero, "Handle for control not created");
                        Ole32.RevokeDragDrop(_handle);
                    }
                }
            }
        }
    }
}
