// 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.Drawing;
using Microsoft.Win32;

namespace System.Windows.Forms;

public partial class ToolStripTextBox
{
    private class ToolStripTextBoxControl : TextBox
    {
        private bool _mouseIsOver;
        private bool _isFontSet = true;
        private bool _alreadyHooked;

        public ToolStripTextBoxControl()
        {
            // required to make the text box height match the combo.
            Font = ToolStripManager.DefaultFont;
            _isFontSet = false;
        }

        // returns the distance from the client rect to the upper left hand corner of the control
        private RECT AbsoluteClientRECT
        {
            get
            {
                RECT rect = default;
                CreateParams cp = CreateParams;

                AdjustWindowRectExForControlDpi(ref rect, (WINDOW_STYLE)cp.Style, false, (WINDOW_EX_STYLE)cp.ExStyle);

                // the coordinates we get back are negative, we need to translate this back to positive.
                int offsetX = -rect.left; // one to get back to 0,0, another to translate
                int offsetY = -rect.top;

                // fetch the client rect, then apply the offset.
                PInvokeCore.GetClientRect(this, out var clientRect);

                clientRect.left += offsetX;
                clientRect.right += offsetX;
                clientRect.top += offsetY;
                clientRect.bottom += offsetY;

                return clientRect;
            }
        }

        private Rectangle AbsoluteClientRectangle => AbsoluteClientRECT;

        private ProfessionalColorTable ColorTable
        {
            get
            {
                if (Owner is not null)
                {
                    if (Owner.Renderer is ToolStripProfessionalRenderer renderer)
                    {
                        return renderer.ColorTable;
                    }
                }

                return ProfessionalColors.ColorTable;
            }
        }

        private bool IsPopupTextBox
        {
            get
            {
                return ((BorderStyle == BorderStyle.Fixed3D) &&
                         (Owner is not null && (Owner.Renderer is ToolStripProfessionalRenderer)));
            }
        }

        internal bool MouseIsOver
        {
            get { return _mouseIsOver; }
            set
            {
                if (_mouseIsOver != value)
                {
                    _mouseIsOver = value;
                    if (!Focused)
                    {
                        InvalidateNonClient();
                    }
                }
            }
        }

        [AllowNull]
        public override Font Font
        {
            get => base.Font;
            set
            {
                base.Font = value;
                _isFontSet = ShouldSerializeFont();
            }
        }

        public ToolStripTextBox? Owner { get; set; }

        private unsafe void InvalidateNonClient()
        {
            if (!IsPopupTextBox)
            {
                return;
            }

            var absoluteClientRectangle = AbsoluteClientRECT;

            // Get the total client area, then exclude the client by using XOR
            using RegionScope hTotalRegion = new(0, 0, Width, Height);
            using RegionScope hClientRegion = new(
                absoluteClientRectangle.left,
                absoluteClientRectangle.top,
                absoluteClientRectangle.right,
                absoluteClientRectangle.bottom);
            using RegionScope hNonClientRegion = new(0, 0, 0, 0);

            PInvokeCore.CombineRgn(hNonClientRegion, hTotalRegion, hClientRegion, RGN_COMBINE_MODE.RGN_XOR);

            // Call RedrawWindow with the region.
            PInvoke.RedrawWindow(
                this,
                lprcUpdate: null,
                hNonClientRegion,
                REDRAW_WINDOW_FLAGS.RDW_INVALIDATE | REDRAW_WINDOW_FLAGS.RDW_ERASE | REDRAW_WINDOW_FLAGS.RDW_UPDATENOW
                    | REDRAW_WINDOW_FLAGS.RDW_ERASENOW | REDRAW_WINDOW_FLAGS.RDW_FRAME);
        }

        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);
            InvalidateNonClient();
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);
            InvalidateNonClient();
        }

        protected override void OnMouseEnter(EventArgs e)
        {
            base.OnMouseEnter(e);
            MouseIsOver = true;
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            MouseIsOver = false;
        }

        private void HookStaticEvents(bool hook)
        {
            if (hook)
            {
                if (!_alreadyHooked)
                {
                    try
                    {
                        SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
                    }
                    finally
                    {
                        _alreadyHooked = true;
                    }
                }
            }
            else if (_alreadyHooked)
            {
                try
                {
                    SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
                }
                finally
                {
                    _alreadyHooked = false;
                }
            }
        }

        private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
        {
            if (e.Category == UserPreferenceCategory.Window)
            {
                if (!_isFontSet)
                {
                    Font = ToolStripManager.DefaultFont;
                }
            }
        }

        protected override void OnVisibleChanged(EventArgs e)
        {
            base.OnVisibleChanged(e);
            if (!Disposing && !IsDisposed)
            {
                HookStaticEvents(Visible);
            }
        }

        protected override AccessibleObject CreateAccessibilityInstance()
            => new ToolStripTextBoxControlAccessibleObject(this);

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                HookStaticEvents(false);
            }

            base.Dispose(disposing);
        }

        private void WmNCPaint(ref Message m)
        {
            if (!IsPopupTextBox)
            {
                base.WndProc(ref m);
                return;
            }

            // Paint over the edges of the text box.

            // Note that GetWindowDC just calls GetDCEx with DCX_WINDOW | DCX_USESTYLE.

            using GetDcScope hdc = new(m.HWND, HRGN.Null, GET_DCX_FLAGS.DCX_WINDOW | (GET_DCX_FLAGS)0x00010000 /* DCX_USESTYLE */);
            if (hdc.IsNull)
            {
                throw new Win32Exception();
            }

            // Don't set the clipping region based on the WParam
            // - windows seems to take out the two pixels intended for the non-client border.

            bool focused = MouseIsOver || Focused;
            Color outerBorderColor = focused ? ColorTable.TextBoxBorder : BackColor;
            Color innerBorderColor = SystemInformation.HighContrast && !focused ? ColorTable.MenuBorder : BackColor;

            if (!Enabled)
            {
                outerBorderColor = SystemColors.ControlDark;
                innerBorderColor = SystemColors.Control;
            }

            using Graphics g = hdc.CreateGraphics();
            Rectangle clientRect = AbsoluteClientRectangle;

            // Could have set up a clip and fill-rectangled, thought this would be faster.
            using var brush = innerBorderColor.GetCachedSolidBrushScope();
            g.FillRectangle(brush, 0, 0, Width, clientRect.Top);                                // top border
            g.FillRectangle(brush, 0, 0, clientRect.Left, Height);                              // left border
            g.FillRectangle(brush, 0, clientRect.Bottom, Width, Height - clientRect.Height);    // bottom border
            g.FillRectangle(brush, clientRect.Right, 0, Width - clientRect.Right, Height);      // right border

            // Paint the outside rect.
            using var pen = outerBorderColor.GetCachedPenScope();
            g.DrawRectangle(pen, 0, 0, Width - 1, Height - 1);

            // We've handled WM_NCPAINT.
            m.ResultInternal = (LRESULT)0;
        }

        protected override void WndProc(ref Message m)
        {
            if (m.MsgInternal == PInvokeCore.WM_NCPAINT)
            {
                WmNCPaint(ref m);
                return;
            }
            else
            {
                base.WndProc(ref m);
            }
        }
    }
}
