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

using System.Drawing;

namespace System.Windows.Forms.Design;

public partial class ComponentEditorForm
{
    // This should be moved into a shared location
    //  Its a duplication of what exists in the StyleBuilder.
    internal sealed class PageSelector : TreeView
    {
        private const int PADDING_VERT = 3;
        private const int PADDING_HORZ = 4;

        private const int SIZE_ICON_X = 16;
        private const int SIZE_ICON_Y = 16;

        private const int STATE_NORMAL = 0;
        private const int STATE_SELECTED = 1;
        private const int STATE_HOT = 2;

        private HBRUSH _hbrushDither;

        public PageSelector()
        {
            HotTracking = true;
            HideSelection = false;
            BackColor = SystemColors.Control;
            Indent = 0;
            LabelEdit = false;
            Scrollable = false;
            ShowLines = false;
            ShowPlusMinus = false;
            ShowRootLines = false;
            BorderStyle = BorderStyle.None;
            Indent = 0;
            FullRowSelect = true;
        }

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

                cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_STATICEDGE;
                return cp;
            }
        }

        private unsafe void CreateDitherBrush()
        {
            Debug.Assert(_hbrushDither.IsNull, "Brush should not be recreated.");

            short* patternBits = stackalloc short[]
            {
                unchecked((short)0xAAAA),
                unchecked(0x5555),
                unchecked((short)0xAAAA),
                unchecked(0x5555),
                unchecked((short)0xAAAA),
                unchecked(0x5555),
                unchecked((short)0xAAAA),
                unchecked(0x5555)
            };

            HBITMAP hbitmapTemp = PInvokeCore.CreateBitmap(8, 8, 1, 1, patternBits);
            Debug.Assert(
                !hbitmapTemp.IsNull,
                "could not create dither bitmap. Page selector UI will not be correct");

            if (!hbitmapTemp.IsNull)
            {
                _hbrushDither = PInvoke.CreatePatternBrush(hbitmapTemp);

                Debug.Assert(
                    !_hbrushDither.IsNull,
                    "Unable to created dithered brush. Page selector UI will not be correct");

                PInvokeCore.DeleteObject(hbitmapTemp);
            }
        }

        private unsafe void DrawTreeItem(
            string itemText,
            int imageIndex,
            HDC dc,
            RECT rcIn,
            int state,
            COLORREF backColor,
            COLORREF textColor)
        {
            Size size = default;
            RECT rc2 = default;
            RECT rc = rcIn;
            ImageList? imageList = ImageList;

            // Select the font of the dialog, so we don't get the underlined font
            // when the item is being tracked
            using SelectObjectScope fontSelection = new(
                dc,
                (state & STATE_HOT) != 0 ? (HGDIOBJ)Parent!.FontHandle : default);

            GC.KeepAlive(Parent);

            // Fill the background
            if (((state & STATE_SELECTED) != 0) && !_hbrushDither.IsNull)
            {
                FillRectDither(dc, rcIn);
                PInvokeCore.SetBkMode(dc, BACKGROUND_MODE.TRANSPARENT);
            }
            else
            {
                PInvokeCore.SetBkColor(dc, backColor);
                PInvoke.ExtTextOut(dc, 0, 0, ETO_OPTIONS.ETO_CLIPPED | ETO_OPTIONS.ETO_OPAQUE, &rc, lpString: null, 0, lpDx: null);
            }

            fixed (char* pItemText = itemText)
            {
                // Get the height of the font
                PInvoke.GetTextExtentPoint32W(dc, pItemText, itemText.Length, (SIZE*)(void*)&size);
            }

            // Draw the caption
            rc2.left = rc.left + SIZE_ICON_X + 2 * PADDING_HORZ;
            rc2.top = rc.top + (((rc.bottom - rc.top) - size.Height) >> 1);
            rc2.bottom = rc2.top + size.Height;
            rc2.right = rc.right;
            PInvokeCore.SetTextColor(dc, textColor);

            fixed (char* t = itemText)
            {
                PInvoke.DrawText(
                    dc,
                    t,
                    itemText.Length,
                    ref rc2,
                    DRAW_TEXT_FORMAT.DT_LEFT | DRAW_TEXT_FORMAT.DT_VCENTER | DRAW_TEXT_FORMAT.DT_END_ELLIPSIS | DRAW_TEXT_FORMAT.DT_NOPREFIX);
            }

            if (imageList is not null)
            {
                PInvoke.ImageList.Draw(
                    imageList,
                    imageIndex,
                    dc,
                    PADDING_HORZ,
                    rc.top + (((rc.bottom - rc.top) - SIZE_ICON_Y) >> 1),
                    IMAGE_LIST_DRAW_STYLE.ILD_TRANSPARENT);
            }

            // Draw the hot-tracking border if needed
            if ((state & STATE_HOT) != 0)
            {
                COLORREF savedColor;

                // top left
                savedColor = PInvokeCore.SetBkColor(dc, (COLORREF)(uint)ColorTranslator.ToWin32(SystemColors.ControlLightLight));
                rc2.left = rc.left;
                rc2.top = rc.top;
                rc2.bottom = rc.top + 1;
                rc2.right = rc.right;
                PInvoke.ExtTextOut(dc, 0, 0, ETO_OPTIONS.ETO_OPAQUE, &rc2, lpString: null, 0, lpDx: null);
                rc2.bottom = rc.bottom;
                rc2.right = rc.left + 1;
                PInvoke.ExtTextOut(dc, 0, 0, ETO_OPTIONS.ETO_OPAQUE, &rc2, lpString: null, 0, lpDx: null);

                // bottom right
                PInvokeCore.SetBkColor(dc, (COLORREF)(uint)ColorTranslator.ToWin32(SystemColors.ControlDark));
                rc2.left = rc.left;
                rc2.right = rc.right;
                rc2.top = rc.bottom - 1;
                rc2.bottom = rc.bottom;
                PInvoke.ExtTextOut(dc, 0, 0, ETO_OPTIONS.ETO_OPAQUE, &rc2, lpString: null, 0, lpDx: null);
                rc2.left = rc.right - 1;
                rc2.top = rc.top;
                PInvoke.ExtTextOut(dc, 0, 0, ETO_OPTIONS.ETO_OPAQUE, &rc2, lpString: null, 0, lpDx: null);

                PInvokeCore.SetBkColor(dc, savedColor);
            }
        }

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

            int itemHeight = (int)PInvokeCore.SendMessage(this, PInvoke.TVM_GETITEMHEIGHT);
            itemHeight += 2 * PADDING_VERT;
            PInvokeCore.SendMessage(this, PInvoke.TVM_SETITEMHEIGHT, (WPARAM)itemHeight);

            if (_hbrushDither.IsNull)
            {
                CreateDitherBrush();
            }
        }

        private unsafe void OnCustomDraw(ref Message m)
        {
            NMTVCUSTOMDRAW* nmtvcd = (NMTVCUSTOMDRAW*)(nint)m.LParamInternal;

            switch (nmtvcd->nmcd.dwDrawStage)
            {
                case NMCUSTOMDRAW_DRAW_STAGE.CDDS_PREPAINT:
                    m.ResultInternal = (LRESULT)(nint)(PInvoke.CDRF_NOTIFYITEMDRAW | PInvoke.CDRF_NOTIFYPOSTPAINT);
                    break;
                case NMCUSTOMDRAW_DRAW_STAGE.CDDS_ITEMPREPAINT:
                    {
                        TreeNode? itemNode = TreeNode.FromHandle(this, (nint)nmtvcd->nmcd.dwItemSpec);
                        if (itemNode is not null)
                        {
                            int state = STATE_NORMAL;
                            NMCUSTOMDRAW_DRAW_STATE_FLAGS itemState = nmtvcd->nmcd.uItemState;
                            if (((itemState & NMCUSTOMDRAW_DRAW_STATE_FLAGS.CDIS_HOT) != 0) ||
                                ((itemState & NMCUSTOMDRAW_DRAW_STATE_FLAGS.CDIS_FOCUS) != 0))
                            {
                                state |= STATE_HOT;
                            }

                            if ((itemState & NMCUSTOMDRAW_DRAW_STATE_FLAGS.CDIS_SELECTED) != 0)
                            {
                                state |= STATE_SELECTED;
                            }

                            DrawTreeItem(
                                itemNode.Text,
                                itemNode.ImageIndex,
                                nmtvcd->nmcd.hdc,
                                nmtvcd->nmcd.rc,
                                state,
                                (COLORREF)(uint)ColorTranslator.ToWin32(SystemColors.Control),
                                (COLORREF)(uint)ColorTranslator.ToWin32(SystemColors.ControlText));
                        }

                        m.ResultInternal = (LRESULT)(nint)PInvoke.CDRF_SKIPDEFAULT;
                    }

                    break;
                case NMCUSTOMDRAW_DRAW_STAGE.CDDS_POSTPAINT:
                    m.ResultInternal = (LRESULT)(nint)PInvoke.CDRF_SKIPDEFAULT;
                    break;
                default:
                    m.ResultInternal = (LRESULT)(nint)PInvoke.CDRF_DODEFAULT;
                    break;
            }
        }

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

            if (!RecreatingHandle && !_hbrushDither.IsNull)
            {
                PInvokeCore.DeleteObject(_hbrushDither);
                _hbrushDither = default;
            }
        }

        private void FillRectDither(HDC dc, RECT rc)
        {
            HGDIOBJ hbrushOld = PInvokeCore.SelectObject(dc, _hbrushDither);

            if (!hbrushOld.IsNull)
            {
                COLORREF oldTextColor = PInvokeCore.SetTextColor(dc, (COLORREF)(uint)ColorTranslator.ToWin32(SystemColors.ControlLightLight));
                COLORREF oldBackColor = PInvokeCore.SetBkColor(dc, (COLORREF)(uint)ColorTranslator.ToWin32(SystemColors.Control));

                PInvoke.PatBlt(dc, rc.left, rc.top, rc.Width, rc.Height, ROP_CODE.PATCOPY);
                PInvokeCore.SetTextColor(dc, oldTextColor);
                PInvokeCore.SetBkColor(dc, oldBackColor);
            }
        }

        protected override unsafe void WndProc(ref Message m)
        {
            if (m.MsgInternal == MessageId.WM_REFLECT_NOTIFY)
            {
                NMHDR* nmhdr = (NMHDR*)(nint)m.LParamInternal;
                if (nmhdr->code == PInvoke.NM_CUSTOMDRAW)
                {
                    OnCustomDraw(ref m);
                    return;
                }
            }

            base.WndProc(ref m);
        }
    }
}
