// 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;
using System.Windows.Forms.VisualStyles;

namespace System.Windows.Forms;

/// <summary>
///  Provides methods used to render a button control with or without visual styles.
/// </summary>
public static class ButtonRenderer
{
    // Make this per-thread, so that different threads can safely use these methods.
    [ThreadStatic]
    private static VisualStyleRenderer? t_visualStyleRenderer;
    private static readonly VisualStyleElement s_buttonElement = VisualStyleElement.Button.PushButton.Normal;

    /// <summary>
    ///  Gets or sets a value indicating whether the renderer uses the application state to determine rendering style.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   If the <see cref="RenderMatchingApplicationState"/> property is <see langword="true"/>, the renderer
    ///   uses the setting from the <see cref="Application.RenderWithVisualStyles"/> property to determine the
    ///   rendering style. If <see cref="RenderMatchingApplicationState"/> property is <see langword="false"/>,
    ///   the renderer will always render using visual styles.
    ///  </para>
    /// </remarks>
    public static bool RenderMatchingApplicationState { get; set; } = true;

    private static bool RenderWithVisualStyles => !RenderMatchingApplicationState || Application.RenderWithVisualStyles;

    /// <summary>
    ///  Indicates whether the background has semitransparent or alpha-blended pieces.
    /// </summary>
    public static bool IsBackgroundPartiallyTransparent(PushButtonState state)
    {
        if (RenderWithVisualStyles)
        {
            InitializeRenderer((int)state);
            return t_visualStyleRenderer.IsBackgroundPartiallyTransparent();
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    ///  Draws the background of a control's parent in the specified area.
    /// </summary>
    public static void DrawParentBackground(Graphics g, Rectangle bounds, Control childControl)
        => DrawParentBackground((IDeviceContext)g, bounds, childControl);

    internal static void DrawParentBackground(IDeviceContext dc, Rectangle bounds, Control childControl)
    {
        if (RenderWithVisualStyles)
        {
            InitializeRenderer(0);
            t_visualStyleRenderer.DrawParentBackground(dc, bounds, childControl);
        }
    }

    /// <inheritdoc cref="DrawButton(Graphics, Rectangle, string?, Font?, TextFormatFlags, Image, Rectangle, bool, PushButtonState)"/>
    public static void DrawButton(Graphics g, Rectangle bounds, PushButtonState state) =>
        DrawButton((IDeviceContext)g, bounds, state);

    internal static void DrawButton(IDeviceContext deviceContext, Rectangle bounds, PushButtonState state)
    {
        if (RenderWithVisualStyles)
        {
            InitializeRenderer((int)state);
            t_visualStyleRenderer.DrawBackground(deviceContext, bounds);
        }
        else
        {
            Graphics? graphics = deviceContext.TryGetGraphics(create: true);
            if (graphics is not null)
            {
                ControlPaint.DrawButton(graphics, bounds, ConvertToButtonState(state));
            }
        }
    }

    internal static void DrawButtonForHandle(
        IDeviceContext deviceContext,
        Rectangle bounds,
        bool focused,
        PushButtonState state,
        HWND hwnd)
    {
        Rectangle contentBounds;

        if (RenderWithVisualStyles)
        {
            InitializeRenderer((int)state);

            using DeviceContextHdcScope hdc = deviceContext.ToHdcScope();
            t_visualStyleRenderer.DrawBackground(hdc, bounds, hwnd);
            contentBounds = t_visualStyleRenderer.GetBackgroundContentRectangle(hdc, bounds);
        }
        else
        {
            Graphics? graphics = deviceContext.TryGetGraphics(create: true);
            if (graphics is not null)
            {
                ControlPaint.DrawButton(graphics, bounds, ConvertToButtonState(state));
            }

            contentBounds = Rectangle.Inflate(bounds, -3, -3);
        }

        if (focused)
        {
            Graphics? graphics = deviceContext.TryGetGraphics(create: true);
            if (graphics is not null)
            {
                ControlPaint.DrawFocusRectangle(graphics, contentBounds);
            }
        }
    }

    /// <inheritdoc cref="DrawButton(Graphics, Rectangle, string?, Font?, TextFormatFlags, Image, Rectangle, bool, PushButtonState)"/>
    public static void DrawButton(Graphics g, Rectangle bounds, bool focused, PushButtonState state) =>
        DrawButtonForHandle(g, bounds, focused, state, HWND.Null);

    /// <inheritdoc cref="DrawButton(Graphics, Rectangle, string?, Font?, TextFormatFlags, Image, Rectangle, bool, PushButtonState)"/>
    public static void DrawButton(Graphics g, Rectangle bounds, string? buttonText, Font? font, bool focused, PushButtonState state)
    {
        DrawButton(
            g,
            bounds,
            buttonText,
            font,
            TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine,
            focused,
            state);
    }

    /// <inheritdoc cref="DrawButton(Graphics, Rectangle, string?, Font?, TextFormatFlags, Image, Rectangle, bool, PushButtonState)"/>
    public static void DrawButton(Graphics g, Rectangle bounds, string? buttonText, Font? font, TextFormatFlags flags, bool focused, PushButtonState state)
    {
        Rectangle contentBounds;
        Color textColor;

        if (RenderWithVisualStyles)
        {
            InitializeRenderer((int)state);

            t_visualStyleRenderer.DrawBackground(g, bounds);
            contentBounds = t_visualStyleRenderer.GetBackgroundContentRectangle(g, bounds);
            textColor = t_visualStyleRenderer.GetColor(ColorProperty.TextColor);
        }
        else
        {
            ControlPaint.DrawButton(g, bounds, ConvertToButtonState(state));
            contentBounds = Rectangle.Inflate(bounds, -3, -3);
            textColor = SystemColors.ControlText;
        }

        TextRenderer.DrawText(g, buttonText, font, contentBounds, textColor, flags);

        if (focused)
        {
            ControlPaint.DrawFocusRectangle(g, contentBounds);
        }
    }

    /// <inheritdoc cref="DrawButton(Graphics, Rectangle, string?, Font?, TextFormatFlags, Image, Rectangle, bool, PushButtonState)"/>

    public static void DrawButton(Graphics g, Rectangle bounds, Image image, Rectangle imageBounds, bool focused, PushButtonState state)
    {
        Rectangle contentBounds;

        if (RenderWithVisualStyles)
        {
            InitializeRenderer((int)state);

            t_visualStyleRenderer.DrawBackground(g, bounds);
            t_visualStyleRenderer.DrawImage(g, imageBounds, image);
            contentBounds = t_visualStyleRenderer.GetBackgroundContentRectangle(g, bounds);
        }
        else
        {
            ControlPaint.DrawButton(g, bounds, ConvertToButtonState(state));
            g.DrawImage(image, imageBounds);
            contentBounds = Rectangle.Inflate(bounds, -3, -3);
        }

        if (focused)
        {
            ControlPaint.DrawFocusRectangle(g, contentBounds);
        }
    }

    /// <inheritdoc cref="DrawButton(Graphics, Rectangle, string?, Font?, TextFormatFlags, Image, Rectangle, bool, PushButtonState)"/>
    public static void DrawButton(
        Graphics g,
        Rectangle bounds,
        string? buttonText,
        Font? font,
        Image image,
        Rectangle imageBounds,
        bool focused,
        PushButtonState state) => DrawButton(
            g,
            bounds,
            buttonText,
            font,
            TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine,
            image,
            imageBounds,
            focused,
            state);

    /// <summary>
    ///  Draws a button control.
    /// </summary>
    public static void DrawButton(
        Graphics g,
        Rectangle bounds,
        string? buttonText,
        Font? font,
        TextFormatFlags flags,
        Image image,
        Rectangle imageBounds,
        bool focused,
        PushButtonState state)
        => DrawButton((IDeviceContext)g, bounds, buttonText, font, flags, image, imageBounds, focused, state);

    internal static void DrawButton(
        IDeviceContext deviceContext,
        Rectangle bounds,
        string? buttonText,
        Font? font,
        TextFormatFlags flags,
        Image image,
        Rectangle imageBounds,
        bool focused,
        PushButtonState state)
    {
        Rectangle contentBounds;
        Color textColor;

        Graphics? graphics = deviceContext.TryGetGraphics(create: true);

        if (RenderWithVisualStyles)
        {
            InitializeRenderer((int)state);

            t_visualStyleRenderer.DrawBackground(deviceContext, bounds);
            if (graphics is not null)
            {
                t_visualStyleRenderer.DrawImage(graphics, imageBounds, image);
            }

            contentBounds = t_visualStyleRenderer.GetBackgroundContentRectangle(deviceContext, bounds);
            textColor = t_visualStyleRenderer.GetColor(ColorProperty.TextColor);
        }
        else
        {
            if (graphics is not null)
            {
                ControlPaint.DrawButton(graphics, bounds, ConvertToButtonState(state));
                graphics.DrawImage(image, imageBounds);
            }

            contentBounds = Rectangle.Inflate(bounds, -3, -3);
            textColor = SystemColors.ControlText;
        }

        TextRenderer.DrawText(deviceContext, buttonText, font, contentBounds, textColor, flags);

        if (focused && graphics is not null)
        {
            ControlPaint.DrawFocusRectangle(graphics, contentBounds);
        }
    }

    internal static ButtonState ConvertToButtonState(PushButtonState state) => state switch
    {
        PushButtonState.Pressed => ButtonState.Pushed,
        PushButtonState.Disabled => ButtonState.Inactive,
        _ => ButtonState.Normal,
    };

    [MemberNotNull(nameof(t_visualStyleRenderer))]
    private static void InitializeRenderer(int state)
    {
        if (t_visualStyleRenderer is null)
        {
            t_visualStyleRenderer = new VisualStyleRenderer(s_buttonElement.ClassName, s_buttonElement.Part, state);
        }
        else
        {
            t_visualStyleRenderer.SetParameters(s_buttonElement.ClassName, s_buttonElement.Part, state);
        }
    }
}
